From: Patrick Seidensal Date: Wed, 27 Nov 2019 20:34:12 +0000 (+0000) Subject: mgr/dashboard: move monitoring tabs to a single page X-Git-Tag: v15.1.0~650^2~2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=855f214b29c8ed935c8f4ba0b8a8396692f946a1;p=ceph.git mgr/dashboard: move monitoring tabs to a single page with a tab for 'active alerts', 'all alerts' and 'silences'. Due to ambiguity with existing names, `AlertListComponent` has been renamed to `ActiveAlertListComponent`. Introduces `MonitoringListComponent` as first page for monitoring concerns, using path `/monitoring`. Keeps the activated tab open, independent of the way that's used to go back to the previous page, be it the cancel button or submit button or the link on the breadcrumb. Also keeps the active tab open even when the page is reloaded. Fixes: https://tracker.ceph.com/issues/42877 Signed-off-by: Patrick Seidensal --- diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/alerts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/alerts.e2e-spec.ts deleted file mode 100644 index 51639d2fca758..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/alerts.e2e-spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AlertsPageHelper } from './alerts.po'; - -describe('Alerts page', () => { - let alerts: AlertsPageHelper; - - beforeAll(() => { - alerts = new AlertsPageHelper(); - }); - - afterEach(async () => { - await AlertsPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await alerts.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await alerts.waitTextToBePresent(alerts.getBreadcrumb(), 'Alerts'); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/alerts.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/alerts.po.ts deleted file mode 100644 index 8a50662b26d1a..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/alerts.po.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PageHelper } from '../page-helper.po'; - -export class AlertsPageHelper extends PageHelper { - pages = { index: '/#/alerts' }; -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index a483ba78bd615..2913b293dc391 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -16,9 +16,8 @@ import { MgrModuleListComponent } from './ceph/cluster/mgr-modules/mgr-module-li import { MonitorComponent } from './ceph/cluster/monitor/monitor.component'; import { OsdFormComponent } from './ceph/cluster/osd/osd-form/osd-form.component'; import { OsdListComponent } from './ceph/cluster/osd/osd-list/osd-list.component'; -import { AlertListComponent } from './ceph/cluster/prometheus/alert-list/alert-list.component'; +import { MonitoringListComponent } from './ceph/cluster/prometheus/monitoring-list/monitoring-list.component'; import { SilenceFormComponent } from './ceph/cluster/prometheus/silence-form/silence-form.component'; -import { SilenceListComponent } from './ceph/cluster/prometheus/silence-list/silence-list.component'; import { ServicesComponent } from './ceph/cluster/services/services.component'; import { DashboardComponent } from './ceph/dashboard/dashboard/dashboard.component'; import { Nfs501Component } from './ceph/nfs/nfs-501/nfs-501.component'; @@ -142,34 +141,31 @@ const routes: Routes = [ data: { breadcrumbs: 'Cluster/Logs' } }, { - path: 'alerts', - component: AlertListComponent, + path: 'monitoring', canActivate: [AuthGuardService], - data: { breadcrumbs: 'Cluster/Alerts' } - }, - { - path: 'silence', - canActivate: [AuthGuardService], - data: { breadcrumbs: 'Cluster/Silences' }, + data: { breadcrumbs: 'Cluster/Monitoring' }, children: [ - { path: '', component: SilenceListComponent }, { - path: URLVerbs.CREATE, + path: '', + component: MonitoringListComponent + }, + { + path: 'silence/' + URLVerbs.CREATE, component: SilenceFormComponent, - data: { breadcrumbs: ActionLabels.CREATE } + data: { breadcrumbs: `${ActionLabels.CREATE} Silence` } }, { - path: `${URLVerbs.CREATE}/:id`, + path: `silence/${URLVerbs.CREATE}/:id`, component: SilenceFormComponent, data: { breadcrumbs: ActionLabels.CREATE } }, { - path: `${URLVerbs.EDIT}/:id`, + path: `silence/${URLVerbs.EDIT}/:id`, component: SilenceFormComponent, data: { breadcrumbs: ActionLabels.EDIT } }, { - path: `${URLVerbs.RECREATE}/:id`, + path: `silence/${URLVerbs.RECREATE}/:id`, component: SilenceFormComponent, data: { breadcrumbs: ActionLabels.RECREATE } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts index 666b216698a01..e3bae17c0cafe 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts @@ -42,8 +42,8 @@ import { OsdRecvSpeedModalComponent } from './osd/osd-recv-speed-modal/osd-recv- import { OsdReweightModalComponent } from './osd/osd-reweight-modal/osd-reweight-modal.component'; import { OsdScrubModalComponent } from './osd/osd-scrub-modal/osd-scrub-modal.component'; import { OsdSmartListComponent } from './osd/osd-smart-list/osd-smart-list.component'; -import { AlertListComponent } from './prometheus/alert-list/alert-list.component'; -import { PrometheusTabsComponent } from './prometheus/prometheus-tabs/prometheus-tabs.component'; +import { ActiveAlertListComponent } from './prometheus/active-alert-list/active-alert-list.component'; +import { MonitoringListComponent } from './prometheus/monitoring-list/monitoring-list.component'; import { RulesListComponent } from './prometheus/rules-list/rules-list.component'; import { SilenceFormComponent } from './prometheus/silence-form/silence-form.component'; import { SilenceListComponent } from './prometheus/silence-list/silence-list.component'; @@ -101,11 +101,10 @@ import { ServicesComponent } from './services/services.component'; LogsComponent, OsdRecvSpeedModalComponent, OsdPgScrubModalComponent, - AlertListComponent, + ActiveAlertListComponent, OsdRecvSpeedModalComponent, SilenceFormComponent, SilenceListComponent, - PrometheusTabsComponent, SilenceMatcherModalComponent, ServicesComponent, InventoryComponent, @@ -117,7 +116,8 @@ import { ServicesComponent } from './services/services.component'; OsdDevicesSelectionGroupsComponent, OsdCreationPreviewModalComponent, RulesListComponent, - AlertListComponent + ActiveAlertListComponent, + MonitoringListComponent ] }) export class ClusterModule {} 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 new file mode 100644 index 0000000000000..64a446c87fb86 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + Source + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts new file mode 100644 index 0000000000000..1b2c18aced14f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts @@ -0,0 +1,107 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { TabsModule } from 'ngx-bootstrap/tabs'; +import { ToastrModule } from 'ngx-toastr'; + +import { + configureTestBed, + i18nProviders, + PermissionHelper +} from '../../../../../testing/unit-test-helper'; +import { CoreModule } from '../../../../core/core.module'; +import { TableActionsComponent } from '../../../../shared/datatable/table-actions/table-actions.component'; +import { SharedModule } from '../../../../shared/shared.module'; +import { CephModule } from '../../../ceph.module'; +import { DashboardModule } from '../../../dashboard/dashboard.module'; +import { ClusterModule } from '../../cluster.module'; +import { ActiveAlertListComponent } from './active-alert-list.component'; + +describe('ActiveAlertListComponent', () => { + let component: ActiveAlertListComponent; + let fixture: ComponentFixture; + + configureTestBed({ + imports: [ + HttpClientTestingModule, + TabsModule.forRoot(), + RouterTestingModule, + ToastrModule.forRoot(), + SharedModule, + ClusterModule, + DashboardModule, + CephModule, + CoreModule + ], + declarations: [], + providers: [i18nProviders] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ActiveAlertListComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should test all TableActions combinations', () => { + component.ngOnInit(); + const permissionHelper: PermissionHelper = new PermissionHelper(component.permission); + const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions( + component.tableActions + ); + + expect(tableActions).toEqual({ + 'create,update,delete': { + actions: ['Create Silence'], + primary: { + multiple: 'Create Silence', + executing: 'Create Silence', + single: 'Create Silence', + no: 'Create Silence' + } + }, + 'create,update': { + actions: ['Create Silence'], + primary: { + multiple: 'Create Silence', + executing: 'Create Silence', + single: 'Create Silence', + no: 'Create Silence' + } + }, + 'create,delete': { + actions: ['Create Silence'], + primary: { + multiple: 'Create Silence', + executing: 'Create Silence', + single: 'Create Silence', + no: 'Create Silence' + } + }, + create: { + actions: ['Create Silence'], + primary: { + multiple: 'Create Silence', + executing: 'Create Silence', + single: 'Create Silence', + no: 'Create Silence' + } + }, + 'update,delete': { + actions: [], + primary: { multiple: '', executing: '', single: '', no: '' } + }, + update: { actions: [], primary: { multiple: '', executing: '', single: '', no: '' } }, + delete: { actions: [], primary: { multiple: '', executing: '', single: '', no: '' } }, + 'no-permissions': { + actions: [], + primary: { multiple: '', executing: '', single: '', no: '' } + } + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts new file mode 100644 index 0000000000000..2f09b1dcb9453 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts @@ -0,0 +1,97 @@ +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { I18n } from '@ngx-translate/i18n-polyfill'; +import { CellTemplate } from '../../../../shared/enum/cell-template.enum'; +import { Icons } from '../../../../shared/enum/icons.enum'; +import { CdTableAction } from '../../../../shared/models/cd-table-action'; +import { CdTableColumn } from '../../../../shared/models/cd-table-column'; +import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; +import { Permission } from '../../../../shared/models/permissions'; +import { CdDatePipe } from '../../../../shared/pipes/cd-date.pipe'; +import { AuthStorageService } from '../../../../shared/services/auth-storage.service'; +import { PrometheusAlertService } from '../../../../shared/services/prometheus-alert.service'; +import { URLBuilderService } from '../../../../shared/services/url-builder.service'; + +const BASE_URL = 'silence'; // as only silence actions can be used + +@Component({ + selector: 'cd-active-alert-list', + providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }], + templateUrl: './active-alert-list.component.html', + styleUrls: ['./active-alert-list.component.scss'] +}) +export class ActiveAlertListComponent implements OnInit { + @ViewChild('externalLinkTpl', { static: true }) + externalLinkTpl: TemplateRef; + columns: CdTableColumn[]; + tableActions: CdTableAction[]; + permission: Permission; + selection = new CdTableSelection(); + icons = Icons; + customCss = { + 'badge badge-danger': 'active', + 'badge badge-warning': 'unprocessed', + 'badge badge-info': 'suppressed' + }; + + constructor( + // NotificationsComponent will refresh all alerts every 5s (No need to do it here as well) + private authStorageService: AuthStorageService, + public prometheusAlertService: PrometheusAlertService, + private urlBuilder: URLBuilderService, + private i18n: I18n, + private cdDatePipe: CdDatePipe + ) { + this.permission = this.authStorageService.getPermissions().prometheus; + this.tableActions = [ + { + permission: 'create', + canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection, + disable: (selection: CdTableSelection) => + !selection.hasSingleSelection || selection.first().cdExecuting, + icon: Icons.add, + routerLink: () => + '/monitoring' + this.urlBuilder.getCreateFrom(this.selection.first().fingerprint), + name: this.i18n('Create Silence') + } + ]; + } + + ngOnInit() { + this.columns = [ + { + name: this.i18n('Name'), + prop: 'labels.alertname', + flexGrow: 2 + }, + { + name: this.i18n('Job'), + prop: 'labels.job', + flexGrow: 2 + }, + { + name: this.i18n('Severity'), + prop: 'labels.severity' + }, + { + name: this.i18n('State'), + prop: 'status.state', + cellTransformation: CellTemplate.classAdding + }, + { + name: this.i18n('Started'), + prop: 'startsAt', + pipe: this.cdDatePipe + }, + { + name: this.i18n('URL'), + prop: 'generatorURL', + sortable: false, + cellTemplate: this.externalLinkTpl + } + ]; + } + + updateSelection(selection: CdTableSelection) { + this.selection = selection; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.html deleted file mode 100644 index 45c5a7ba62fdf..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.html +++ /dev/null @@ -1,41 +0,0 @@ - - -

All Alerts

- - -

Active Alerts

- - - - - - - - - - - - - Source - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.scss deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.spec.ts deleted file mode 100644 index 247c20c12847e..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; - -import { TabsModule } from 'ngx-bootstrap/tabs'; -import { ToastrModule } from 'ngx-toastr'; - -import { - configureTestBed, - i18nProviders, - PermissionHelper -} from '../../../../../testing/unit-test-helper'; -import { CoreModule } from '../../../../core/core.module'; -import { TableActionsComponent } from '../../../../shared/datatable/table-actions/table-actions.component'; -import { SharedModule } from '../../../../shared/shared.module'; -import { CephModule } from '../../../ceph.module'; -import { DashboardModule } from '../../../dashboard/dashboard.module'; -import { ClusterModule } from '../../cluster.module'; -import { AlertListComponent } from './alert-list.component'; - -describe('AlertListComponent', () => { - let component: AlertListComponent; - let fixture: ComponentFixture; - - configureTestBed({ - imports: [ - HttpClientTestingModule, - TabsModule.forRoot(), - RouterTestingModule, - ToastrModule.forRoot(), - SharedModule, - ClusterModule, - DashboardModule, - CephModule, - CoreModule - ], - declarations: [], - providers: [i18nProviders] - }); - - beforeEach(() => { - fixture = TestBed.createComponent(AlertListComponent); - component = fixture.componentInstance; - }); - - it('should create', () => { - fixture.detectChanges(); - expect(component).toBeTruthy(); - }); - - it('should test all TableActions combinations', () => { - component.ngOnInit(); - const permissionHelper: PermissionHelper = new PermissionHelper(component.permission); - const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions( - component.tableActions - ); - - expect(tableActions).toEqual({ - 'create,update,delete': { - actions: ['Create silence'], - primary: { - multiple: 'Create silence', - executing: 'Create silence', - single: 'Create silence', - no: 'Create silence' - } - }, - 'create,update': { - actions: ['Create silence'], - primary: { - multiple: 'Create silence', - executing: 'Create silence', - single: 'Create silence', - no: 'Create silence' - } - }, - 'create,delete': { - actions: ['Create silence'], - primary: { - multiple: 'Create silence', - executing: 'Create silence', - single: 'Create silence', - no: 'Create silence' - } - }, - create: { - actions: ['Create silence'], - primary: { - multiple: 'Create silence', - executing: 'Create silence', - single: 'Create silence', - no: 'Create silence' - } - }, - 'update,delete': { - actions: [], - primary: { multiple: '', executing: '', single: '', no: '' } - }, - update: { actions: [], primary: { multiple: '', executing: '', single: '', no: '' } }, - delete: { actions: [], primary: { multiple: '', executing: '', single: '', no: '' } }, - 'no-permissions': { - actions: [], - primary: { multiple: '', executing: '', single: '', no: '' } - } - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.ts deleted file mode 100644 index 2b68bae874956..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/alert-list/alert-list.component.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; -import { I18n } from '@ngx-translate/i18n-polyfill'; -import { CellTemplate } from '../../../../shared/enum/cell-template.enum'; -import { Icons } from '../../../../shared/enum/icons.enum'; -import { CdTableAction } from '../../../../shared/models/cd-table-action'; -import { CdTableColumn } from '../../../../shared/models/cd-table-column'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; -import { Permission } from '../../../../shared/models/permissions'; -import { CdDatePipe } from '../../../../shared/pipes/cd-date.pipe'; -import { AuthStorageService } from '../../../../shared/services/auth-storage.service'; -import { PrometheusAlertService } from '../../../../shared/services/prometheus-alert.service'; -import { URLBuilderService } from '../../../../shared/services/url-builder.service'; - -const BASE_URL = 'silence'; // as only silence actions can be used - -@Component({ - selector: 'cd-prometheus-list', - providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }], - templateUrl: './alert-list.component.html', - styleUrls: ['./alert-list.component.scss'] -}) -export class AlertListComponent implements OnInit { - @ViewChild('externalLinkTpl', { static: true }) - externalLinkTpl: TemplateRef; - columns: CdTableColumn[]; - tableActions: CdTableAction[]; - permission: Permission; - selection = new CdTableSelection(); - icons = Icons; - customCss = { - 'badge badge-danger': 'active', - 'badge badge-warning': 'unprocessed', - 'badge badge-info': 'suppressed' - }; - - constructor( - // NotificationsComponent will refresh all alerts every 5s (No need to do it here as well) - private authStorageService: AuthStorageService, - public prometheusAlertService: PrometheusAlertService, - private urlBuilder: URLBuilderService, - private i18n: I18n, - private cdDatePipe: CdDatePipe - ) { - this.permission = this.authStorageService.getPermissions().prometheus; - this.tableActions = [ - { - permission: 'create', - canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection, - disable: (selection: CdTableSelection) => - !selection.hasSingleSelection || selection.first().cdExecuting, - icon: Icons.add, - routerLink: () => this.urlBuilder.getCreateFrom(this.selection.first().fingerprint), - name: this.i18n('Create silence') - } - ]; - } - - ngOnInit() { - this.columns = [ - { - name: this.i18n('Name'), - prop: 'labels.alertname', - flexGrow: 2 - }, - { - name: this.i18n('Job'), - prop: 'labels.job', - flexGrow: 2 - }, - { - name: this.i18n('Severity'), - prop: 'labels.severity' - }, - { - name: this.i18n('State'), - prop: 'status.state', - cellTransformation: CellTemplate.classAdding - }, - { - name: this.i18n('Started'), - prop: 'startsAt', - pipe: this.cdDatePipe - }, - { - name: this.i18n('URL'), - prop: 'generatorURL', - sortable: false, - cellTemplate: this.externalLinkTpl - } - ]; - } - - updateSelection(selection: CdTableSelection) { - this.selection = selection; - } -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.html new file mode 100644 index 0000000000000..55f571d8427aa --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.html @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.spec.ts new file mode 100644 index 0000000000000..bf0bf6c05672e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.spec.ts @@ -0,0 +1,46 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +import { ToastrModule } from 'ngx-toastr'; + +import { i18nProviders } from '../../../../../testing/unit-test-helper'; +import { AuthModule } from '../../../../core/auth/auth.module'; +import { CoreModule } from '../../../../core/core.module'; +import { CephfsModule } from '../../../cephfs/cephfs.module'; +import { DashboardModule } from '../../../dashboard/dashboard.module'; +import { NfsModule } from '../../../nfs/nfs.module'; +import { ClusterModule } from '../../cluster.module'; +import { MonitoringListComponent } from './monitoring-list.component'; + +describe('MonitoringListComponent', () => { + let component: MonitoringListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ClusterModule, + DashboardModule, + CephfsModule, + AuthModule, + NfsModule, + CoreModule, + ToastrModule.forRoot(), + HttpClientTestingModule + ], + declarations: [], + providers: [i18nProviders] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MonitoringListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts new file mode 100644 index 0000000000000..f6f57a9b37661 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { TabDirective, TabsetComponent } from 'ngx-bootstrap/tabs'; + +import { PrometheusAlertService } from '../../../../shared/services/prometheus-alert.service'; + +@Component({ + selector: 'cd-monitoring-list', + templateUrl: './monitoring-list.component.html', + styleUrls: ['./monitoring-list.component.scss'] +}) +export class MonitoringListComponent implements OnInit { + @ViewChild('tabs', { static: true }) + tabs: TabsetComponent; + + constructor( + public prometheusAlertService: PrometheusAlertService, + private route: ActivatedRoute, + private router: Router + ) {} + + ngOnInit() { + // Activate tab according to given fragment + if (this.route.snapshot.fragment) { + const tab = this.tabs.tabs.find( + (t) => t.elementRef.nativeElement.id === this.route.snapshot.fragment + ); + if (tab) { + tab.active = true; + } + // Ensure fragment is not removed, so page can always be reloaded with the same tab open. + this.router.navigate([], { fragment: this.route.snapshot.fragment }); + } + } + + setFragment(element: TabDirective) { + this.router.navigate([], { fragment: element.id }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html deleted file mode 100644 index b73a09ff4de47..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.scss deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.spec.ts deleted file mode 100644 index 47760b549e439..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; - -import { TabsModule } from 'ngx-bootstrap/tabs'; - -import { configureTestBed } from '../../../../../testing/unit-test-helper'; -import { PrometheusTabsComponent } from './prometheus-tabs.component'; - -describe('PrometheusTabsComponent', () => { - let component: PrometheusTabsComponent; - let fixture: ComponentFixture; - let router: Router; - - const selectTab = (index) => { - fixture.debugElement.queryAll(By.css('tab'))[index].triggerEventHandler('selectTab', null); - }; - - configureTestBed({ - declarations: [PrometheusTabsComponent], - imports: [RouterTestingModule, HttpClientTestingModule, TabsModule.forRoot()] - }); - - beforeEach(() => { - fixture = TestBed.createComponent(PrometheusTabsComponent); - component = fixture.componentInstance; - router = TestBed.get(Router); - spyOn(router, 'navigate').and.stub(); - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should redirect to alert listing', () => { - selectTab(0); - expect(router.navigate).toHaveBeenCalledWith(['/alerts']); - }); - - it('should redirect to silence listing', () => { - selectTab(1); - expect(router.navigate).toHaveBeenCalledWith(['/silence']); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts deleted file mode 100644 index 5675eb710023e..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'cd-prometheus-tabs', - templateUrl: './prometheus-tabs.component.html', - styleUrls: ['./prometheus-tabs.component.scss'] -}) -export class PrometheusTabsComponent { - url: string; - - constructor(private router: Router) { - this.url = this.router.url; - } - - navigateTo(url) { - this.router.navigate([url]); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.spec.ts index cd338b4355d7b..73d5fd8a620ca 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.spec.ts @@ -45,7 +45,7 @@ describe('SilenceFormComponent', () => { let ifPrometheusSpy; // Helper let prometheus: PrometheusHelper; - let formH: FormHelper; + let formHelper: FormHelper; let fixtureH: FixtureHelper; let params; // Date mocking related @@ -86,10 +86,10 @@ describe('SilenceFormComponent', () => { const changeAction = (action: string) => { const modes = { - add: '/silence/add', - alertAdd: '/silence/add/someAlert', - recreate: '/silence/recreate/someExpiredId', - edit: '/silence/edit/someNotExpiredId' + add: '/monitoring/silence/add', + alertAdd: '/monitoring/silence/add/someAlert', + recreate: '/monitoring/silence/recreate/someExpiredId', + edit: '/monitoring/silence/edit/someNotExpiredId' }; Object.defineProperty(router, 'url', { value: modes[action] }); callInit(); @@ -138,7 +138,7 @@ describe('SilenceFormComponent', () => { fixtureH = new FixtureHelper(fixture); component = fixture.componentInstance; form = component.form; - formH = new FormHelper(form); + formHelper = new FormHelper(form); fixture.detectChanges(); }); @@ -333,7 +333,7 @@ describe('SilenceFormComponent', () => { it('should raise invalid start date error', fakeAsync(() => { changeStartDate('No valid date'); - formH.expectError('startsAt', 'bsDate'); + formHelper.expectError('startsAt', 'bsDate'); expect(form.getValue('startsAt').toString()).toBe('Invalid Date'); expect(form.getValue('endsAt')).toEqual(new Date('2022-02-22T02:00:00')); })); @@ -341,9 +341,9 @@ describe('SilenceFormComponent', () => { describe('on duration change', () => { it('changes end date if duration is changed', () => { - formH.setValue('duration', '15m'); + formHelper.setValue('duration', '15m'); expect(form.getValue('endsAt')).toEqual(new Date('2022-02-22T00:15')); - formH.setValue('duration', '5d 23h'); + formHelper.setValue('duration', '5d 23h'); expect(form.getValue('endsAt')).toEqual(new Date('2022-02-27T23:00')); }); }); @@ -363,7 +363,7 @@ describe('SilenceFormComponent', () => { it('should raise invalid end date error', fakeAsync(() => { changeEndDate('No valid date'); - formH.expectError('endsAt', 'bsDate'); + formHelper.expectError('endsAt', 'bsDate'); expect(form.getValue('endsAt').toString()).toBe('Invalid Date'); expect(form.getValue('startsAt')).toEqual(baseTime); })); @@ -371,20 +371,20 @@ describe('SilenceFormComponent', () => { }); it('should have a creator field', () => { - formH.expectValid('createdBy'); - formH.expectErrorChange('createdBy', '', 'required'); - formH.expectValidChange('createdBy', 'Mighty FSM'); + formHelper.expectValid('createdBy'); + formHelper.expectErrorChange('createdBy', '', 'required'); + formHelper.expectValidChange('createdBy', 'Mighty FSM'); }); it('should have a comment field', () => { - formH.expectError('comment', 'required'); - formH.expectValidChange('comment', 'A pretty long comment'); + formHelper.expectError('comment', 'required'); + formHelper.expectValidChange('comment', 'A pretty long comment'); }); it('should be a valid form if all inputs are filled and at least one matcher was added', () => { expect(form.valid).toBeFalsy(); - formH.expectValidChange('createdBy', 'Mighty FSM'); - formH.expectValidChange('comment', 'A pretty long comment'); + formHelper.expectValidChange('createdBy', 'Mighty FSM'); + formHelper.expectValidChange('comment', 'A pretty long comment'); addMatcher('job', 'someJob', false); expect(form.valid).toBeTruthy(); }); @@ -525,7 +525,7 @@ describe('SilenceFormComponent', () => { const fillAndSubmit = () => { ['createdBy', 'comment'].forEach((attr) => { - formH.setValue(attr, silence[attr]); + formHelper.setValue(attr, silence[attr]); }); silence.matchers.forEach((matcher) => addMatcher(matcher.name, matcher.value, matcher.isRegex) @@ -574,10 +574,10 @@ describe('SilenceFormComponent', () => { expect(router.navigate).not.toHaveBeenCalled(); }); - it('should route back to "/silence" on success', () => { + it('should route back to previous tab on success', () => { fillAndSubmit(); expect(form.valid).toBeTruthy(); - expect(router.navigate).toHaveBeenCalledWith(['/silence']); + expect(router.navigate).toHaveBeenCalledWith(['/monitoring'], { fragment: 'silences' }); }); it('should create a silence', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.ts index 8e984e907584e..d5ef6b35e6f59 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.ts @@ -96,8 +96,8 @@ export class SilenceFormComponent { } private chooseMode() { - this.edit = this.router.url.startsWith('/silence/edit'); - this.recreate = this.router.url.startsWith('/silence/recreate'); + this.edit = this.router.url.startsWith('/monitoring/silence/edit'); + this.recreate = this.router.url.startsWith('/monitoring/silence/recreate'); if (this.edit) { this.action = this.actionLabels.EDIT; } else if (this.recreate) { @@ -294,7 +294,7 @@ export class SilenceFormComponent { } this.prometheusService.setSilence(this.getSubmitData()).subscribe( (resp) => { - this.router.navigate(['/silence']); + this.router.navigate(['/monitoring'], { fragment: 'silences' }); this.notificationService.show( NotificationType.success, this.getNotificationTile(resp.body['silenceId']), 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 4d4f4d7b54373..40dc8ffc8e541 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 @@ -1,5 +1,3 @@ - - { @@ -37,7 +36,7 @@ describe('SilenceListComponent', () => { RouterTestingModule, HttpClientTestingModule ], - declarations: [SilenceListComponent, PrometheusTabsComponent], + declarations: [SilenceListComponent], providers: [i18nProviders] }); 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 50542a5a5cee7..76de16a6b49a5 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 @@ -24,7 +24,7 @@ import { AuthStorageService } from '../../../../shared/services/auth-storage.ser import { NotificationService } from '../../../../shared/services/notification.service'; import { URLBuilderService } from '../../../../shared/services/url-builder.service'; -const BASE_URL = 'silence'; +const BASE_URL = 'monitoring/silence'; @Component({ providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }], @@ -65,6 +65,7 @@ export class SilenceListComponent { permission: 'create', icon: Icons.add, routerLink: () => this.urlBuilder.getCreate(), + preserveFragment: true, canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection, name: this.actionLabels.CREATE }, @@ -79,6 +80,7 @@ export class SilenceListComponent { !selectionExpired(selection), icon: Icons.copy, routerLink: () => this.urlBuilder.getRecreate(this.selection.first().id), + preserveFragment: true, name: this.actionLabels.RECREATE }, { @@ -92,6 +94,7 @@ export class SilenceListComponent { (selection.first().cdExecuting && !selectionExpired(selection)) || selectionExpired(selection), routerLink: () => this.urlBuilder.getEdit(this.selection.first().id), + preserveFragment: true, name: this.actionLabels.EDIT }, { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.html index 8b66ec42fcb96..05232b7fa0755 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.html @@ -4,7 +4,8 @@ [ngClass]="{ 'active': last && finished }" class="breadcrumb-item"> {{ crumb.text }} + [routerLink]="crumb.path" + preserveFragment>{{ crumb.text }} {{ crumb.text }} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html index b49b33f08632e..77b30500847e8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html @@ -146,18 +146,11 @@ routerLink="/logs">Logs
  • Alerts -
  • -
  • - Silences + routerLink="/monitoring">Monitoring
  • diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts index 85154c3a7afd1..94313d5a56159 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts @@ -41,7 +41,9 @@ export class NavigationComponent implements OnInit { } this.summaryData = data; }); - this.prometheusService.ifAlertmanagerConfigured(() => (this.prometheusConfigured = true)); + this.prometheusService.ifAlertmanagerConfigured(() => { + this.prometheusConfigured = true; + }); } blockHealthColor() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html index 28992932f5264..1f20fc29f5abc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html @@ -5,7 +5,8 @@ class="btn btn-{{btnColor}}" [ngClass]="{'disabled': disableSelectionAction(action)}" (click)="useClickAction(action)" - [routerLink]="useRouterLink(action)"> + [routerLink]="useRouterLink(action)" + [preserveFragment]="action.preserveFragment ? '' : null"> {{ action.name }} @@ -28,6 +29,7 @@ {{ action.name }} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-action.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-action.ts index c5c6fab131e1d..ac8dcb61a98d0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-action.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-action.ts @@ -6,6 +6,8 @@ export class CdTableAction { // or none if it's not needed routerLink?: string | Function; + preserveFragment? = false; + // This is the function that will be triggered on a click event if defined click?: Function;