diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts
index 6752fe9e7870c..7a7e00d6648ac 100644
--- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts
@@ -2,7 +2,7 @@ import { PageHelper } from '../page-helper.po';
const pages = {
index: { url: '#/hosts', id: 'cd-hosts' },
- create: { url: '#/hosts/create', id: 'cd-host-form' }
+ add: { url: '#/hosts/(modal:add)', id: 'cd-host-form' }
};
export class HostsPageHelper extends PageHelper {
@@ -49,21 +49,20 @@ export class HostsPageHelper extends PageHelper {
});
}
- @PageHelper.restrictTo(pages.create.url)
+ @PageHelper.restrictTo(pages.add.url)
add(hostname: string, exist?: boolean, maintenance?: boolean) {
- cy.get(`${this.pages.create.id}`).within(() => {
+ cy.get(`${this.pages.add.id}`).within(() => {
cy.get('#hostname').type(hostname);
if (maintenance) {
cy.get('label[for=maintenance]').click();
}
+ if (exist) {
+ cy.get('#hostname').should('have.class', 'ng-invalid');
+ }
cy.get('cd-submit-button').click();
});
- if (exist) {
- cy.get('#hostname').should('have.class', 'ng-invalid');
- } else {
- // back to host list
- cy.get(`${this.pages.index.id}`);
- }
+ // back to host list
+ cy.get(`${this.pages.index.id}`);
}
@PageHelper.restrictTo(pages.index.url)
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts
index cf85642a1b1d2..6c79a74662dff 100644
--- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts
@@ -17,7 +17,7 @@ describe('Hosts page', () => {
it('should not add an exsiting host', function () {
const hostname = Cypress._.sample(this.hosts).name;
- hosts.navigateTo('create');
+ hosts.navigateTo('add');
hosts.add(hostname, true);
});
@@ -26,7 +26,7 @@ describe('Hosts page', () => {
hosts.delete(host);
// add it back
- hosts.navigateTo('create');
+ hosts.navigateTo('add');
hosts.add(host);
hosts.checkExist(host, true);
});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-hosts.e2e-spec.ts
index b1c8ad0bbc015..60d442b61a3f1 100644
--- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-hosts.e2e-spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-hosts.e2e-spec.ts
@@ -4,7 +4,7 @@ describe('Hosts page', () => {
const hosts = new HostsPageHelper();
const hostnames = ['ceph-node-00.cephlab.com', 'ceph-node-01.cephlab.com'];
const addHost = (hostname: string, exist?: boolean, maintenance?: boolean) => {
- hosts.navigateTo('create');
+ hosts.navigateTo('add');
hosts.add(hostname, exist, maintenance);
hosts.checkExist(hostname, true);
};
@@ -37,7 +37,7 @@ describe('Hosts page', () => {
});
it('should not add an existing host', function () {
- hosts.navigateTo('create');
+ hosts.navigateTo('add');
hosts.add(hostnames[0], true);
});
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 0c470449a7916..4ef4178e28500 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
@@ -94,6 +94,13 @@ const routes: Routes = [
path: 'create-cluster',
component: CreateClusterComponent,
canActivate: [ModuleStatusGuardService],
+ children: [
+ {
+ path: URLVerbs.ADD,
+ component: HostFormComponent,
+ outlet: 'modal'
+ }
+ ],
data: {
moduleStatusGuardConfig: {
apiPath: 'orchestrator',
@@ -105,13 +112,13 @@ const routes: Routes = [
},
{
path: 'hosts',
+ component: HostsComponent,
data: { breadcrumbs: 'Cluster/Hosts' },
children: [
- { path: '', component: HostsComponent },
{
- path: URLVerbs.CREATE,
+ path: URLVerbs.ADD,
component: HostFormComponent,
- data: { breadcrumbs: ActionLabels.CREATE }
+ outlet: 'modal'
}
]
},
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 a2c1e6d2f89ec..0c02772509360 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
@@ -106,7 +106,6 @@ import { TelemetryComponent } from './telemetry/telemetry.component';
OsdCreationPreviewModalComponent,
RulesListComponent,
ActiveAlertListComponent,
- HostFormComponent,
ServiceDetailsComponent,
ServiceDaemonListComponent,
TelemetryComponent,
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html
index 661c13fc931c9..04c8d6158d80c 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html
@@ -1,4 +1,5 @@
-
+
@@ -14,12 +15,10 @@
@@ -27,3 +26,40 @@
+
+
+
+
+
+
+
+
+
Add Hosts
+
+
+
+
+
Review
+
+
To be implemented
+
+
+
+
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss
index e69de29bb2d1d..bcbfa374927dd 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss
@@ -0,0 +1,21 @@
+@use './src/styles/vendor/variables' as vv;
+
+.container-fluid {
+ align-items: flex-start;
+ display: flex;
+ width: 100%;
+}
+
+.card-body {
+ max-width: 90%;
+}
+
+.vertical-line {
+ border-left: 1px solid vv.$gray-400;
+}
+
+cd-hosts {
+ ::ng-deep .nav {
+ display: none;
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts
index 7e061b2e25c9a..a6e67167be4ef 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts
@@ -5,7 +5,11 @@ import { RouterTestingModule } from '@angular/router/testing';
import { ToastrModule } from 'ngx-toastr';
+import { CephModule } from '~/app/ceph/ceph.module';
+import { CoreModule } from '~/app/core/core.module';
import { ClusterService } from '~/app/shared/api/cluster.service';
+import { HostService } from '~/app/shared/api/host.service';
+import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
import { CreateClusterComponent } from './create-cluster.component';
@@ -14,16 +18,26 @@ describe('CreateClusterComponent', () => {
let component: CreateClusterComponent;
let fixture: ComponentFixture
;
let clusterService: ClusterService;
+ let wizardStepService: WizardStepsService;
+ let hostService: HostService;
configureTestBed({
- declarations: [CreateClusterComponent],
- imports: [HttpClientTestingModule, RouterTestingModule, ToastrModule.forRoot(), SharedModule]
+ imports: [
+ HttpClientTestingModule,
+ RouterTestingModule,
+ ToastrModule.forRoot(),
+ SharedModule,
+ CoreModule,
+ CephModule
+ ]
});
beforeEach(() => {
fixture = TestBed.createComponent(CreateClusterComponent);
component = fixture.componentInstance;
clusterService = TestBed.inject(ClusterService);
+ wizardStepService = TestBed.inject(WizardStepsService);
+ hostService = TestBed.inject(HostService);
fixture.detectChanges();
});
@@ -42,4 +56,55 @@ describe('CreateClusterComponent', () => {
component.skipClusterCreation();
expect(clusterServiceSpy).toHaveBeenCalledTimes(1);
});
+
+ it('should show the wizard when cluster creation is started', () => {
+ component.createCluster();
+ fixture.detectChanges();
+ const nativeEl = fixture.debugElement.nativeElement;
+ expect(nativeEl.querySelector('cd-wizard')).not.toBe(null);
+ });
+
+ it('should have title Add Hosts', () => {
+ component.createCluster();
+ fixture.detectChanges();
+ const heading = fixture.debugElement.query(By.css('.title')).nativeElement;
+ expect(heading.innerHTML).toBe('Add Hosts');
+ });
+
+ it('should show the host list when cluster creation as first step', () => {
+ component.createCluster();
+ fixture.detectChanges();
+ const nativeEl = fixture.debugElement.nativeElement;
+ expect(nativeEl.querySelector('cd-hosts')).not.toBe(null);
+ });
+
+ it('should move to next step and show the second page', () => {
+ const wizardStepServiceSpy = spyOn(wizardStepService, 'moveToNextStep').and.callThrough();
+ const hostServiceSpy = spyOn(hostService, 'list').and.callThrough();
+ component.createCluster();
+ fixture.detectChanges();
+ component.onNextStep();
+ fixture.detectChanges();
+ const heading = fixture.debugElement.query(By.css('.title')).nativeElement;
+ expect(wizardStepServiceSpy).toHaveBeenCalledTimes(1);
+ expect(hostServiceSpy).toBeCalledTimes(1);
+ expect(heading.innerHTML).toBe('Review');
+ });
+
+ it('should show the button labels correctly', () => {
+ component.createCluster();
+ fixture.detectChanges();
+ let submitBtnLabel = component.showSubmitButtonLabel();
+ expect(submitBtnLabel).toEqual('Next');
+ let cancelBtnLabel = component.showCancelButtonLabel();
+ expect(cancelBtnLabel).toEqual('Cancel');
+
+ // Last page of the wizard
+ component.onNextStep();
+ fixture.detectChanges();
+ submitBtnLabel = component.showSubmitButtonLabel();
+ expect(submitBtnLabel).toEqual('Create Cluster');
+ cancelBtnLabel = component.showCancelButtonLabel();
+ expect(cancelBtnLabel).toEqual('Back');
+ });
});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts
index 239a4f13ca7f0..bdd854767aef6 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts
@@ -1,36 +1,52 @@
-import { Component } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { forkJoin, Subscription } from 'rxjs';
+import { finalize } from 'rxjs/operators';
import { ClusterService } from '~/app/shared/api/cluster.service';
-import { AppConstants } from '~/app/shared/constants/app.constants';
+import { HostService } from '~/app/shared/api/host.service';
+import { ActionLabelsI18n, AppConstants } from '~/app/shared/constants/app.constants';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
-import { Permission } from '~/app/shared/models/permissions';
+import { Permissions } from '~/app/shared/models/permissions';
+import { WizardStepModel } from '~/app/shared/models/wizard-steps';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { NotificationService } from '~/app/shared/services/notification.service';
+import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
@Component({
selector: 'cd-create-cluster',
templateUrl: './create-cluster.component.html',
styleUrls: ['./create-cluster.component.scss']
})
-export class CreateClusterComponent {
- permission: Permission;
- orchStatus = false;
- featureAvailable = false;
+export class CreateClusterComponent implements OnDestroy {
+ currentStep: WizardStepModel;
+ currentStepSub: Subscription;
+ permissions: Permissions;
projectConstants: typeof AppConstants = AppConstants;
+ hosts: Array