Deploy {{ clusterTypeDescriptorTitleCase }} Cluster
diff --git a/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.spec.ts b/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.spec.ts
index b3ad2e6bf7..2fd53c1fa2 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.spec.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.spec.ts
@@ -80,8 +80,8 @@ describe('AwsWizardComponent', () => {
}),
metadataForm: fb.group({
clusterDescription: [''],
- clusterLabels: [new Map()],
- clusterLocation: [''],
+ clusterLabels: [{key: 'a', value: '1'}],
+ clusterLocation: ['']
}),
networkForm: fb.group({
clusterPodCidr: [''],
@@ -150,10 +150,7 @@ describe('AwsWizardComponent', () => {
expect(formGroup).toBeTruthy();
formGroup.addControl(fieldName, new FormControl(desiredValue));
});
- // NOTE: because cluster labels are pulled from storage (not a DOM control) we have to put the test values in storage
- const clusterLabels = new Map([['key1', 'value1']]);
- const identifierClusterLabels = { wizard: component.wizardName, step: WizardForm.METADATA, field: 'clusterLabels'};
- AppServices.userDataService.storeMap(identifierClusterLabels, clusterLabels);
+
// NOTE: because cluster plan is pulled from storage (not a DOM control) we have to put the test values in storage
const identifierClusterPlan = { wizard: component.wizardName, step: AwsForm.NODESETTING, field: NodeSettingField.CLUSTER_PLAN };
AppServices.userDataService.store(identifierClusterPlan, { display: ClusterPlan.DEV, value: ClusterPlan.DEV });
@@ -175,9 +172,7 @@ describe('AwsWizardComponent', () => {
clusterPodCIDR: '100.96.0.0/11',
cniType: 'antrea'
});
- expect(payload.labels).toEqual({
- key1: 'value1'
- });
+ expect(payload.labels).toEqual({});
expect(payload.annotations).toEqual({
description: 'DescriptionEXAMPLE',
location: 'mylocation1'
diff --git a/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.ts b/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.ts
index 47d1a2e916..cddc72f44c 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/aws-wizard/aws-wizard.component.ts
@@ -334,18 +334,18 @@ export class AwsWizardComponent extends WizardBaseDirective implements OnInit {
return {name: AwsForm.PROVIDER, title: 'IaaS Provider',
description: 'Validate the AWS provider account for ' + this.title,
i18n: {title: 'IaaS provder step name', description: 'IaaS provder step description'},
- clazz: AwsProviderStepComponent};
+ clazz: AwsProviderStepComponent};
}
get AwsNodeSettingForm(): FormDataForHTML {
return { name: AwsForm.NODESETTING, title: FormUtility.titleCase(this.clusterTypeDescriptor) + ' Cluster Settings',
description: `Specify the resources backing the ${this.clusterTypeDescriptor} cluster`,
i18n: {title: 'IaaS provder step name', description: 'IaaS provder step description'},
- clazz: NodeSettingStepComponent};
+ clazz: NodeSettingStepComponent};
}
get AwsVpcForm(): FormDataForHTML {
return {name: AwsForm.VPC, title: 'VPC for AWS', description: 'Specify VPC settings for AWS',
- i18n: {title: 'vpc step name', description: 'vpc step description'},
- clazz: VpcStepComponent};
+ i18n: {title: 'vpc step name', description: 'vpc step description'},
+ clazz: VpcStepComponent};
}
get AwsOsImageForm(): FormDataForHTML {
return this.getOsImageForm(AwsOsImageStepComponent);
@@ -358,12 +358,12 @@ export class AwsWizardComponent extends WizardBaseDirective implements OnInit {
private subscribeToServices() {
AppServices.messenger.subscribe(TanzuEventType.AWS_REGION_CHANGED, event => {
- const region = event.payload;
- AppServices.dataServiceRegistrar.trigger([TanzuEventType.AWS_GET_OS_IMAGES], { region });
- // NOTE: even though the VPC and AZ endpoints don't take the region as a payload, they DO return different data
- // if the user logs in to AWS using a different region. Therefore, we re-fetch that data if the region changes.
- AppServices.dataServiceRegistrar.trigger([TanzuEventType.AWS_GET_EXISTING_VPCS, TanzuEventType.AWS_GET_AVAILABILITY_ZONES]);
- });
+ const region = event.payload;
+ AppServices.dataServiceRegistrar.trigger([TanzuEventType.AWS_GET_OS_IMAGES], { region });
+ // NOTE: even though the VPC and AZ endpoints don't take the region as a payload, they DO return different data
+ // if the user logs in to AWS using a different region. Therefore, we re-fetch that data if the region changes.
+ AppServices.dataServiceRegistrar.trigger([TanzuEventType.AWS_GET_EXISTING_VPCS, TanzuEventType.AWS_GET_AVAILABILITY_ZONES]);
+ });
}
private registerServices() {
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.html b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.html
index 7d3a3a1fcc..825e3ab625 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.html
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.html
@@ -33,7 +33,7 @@
-
-
- Selecting a Standalone Cluster VIP Network Name is required
- Selecting a Management VIP Network Name is required
+
+
+ Selecting a Standalone
+ Cluster VIP Network Name is required
+
+ Selecting a Management
+ VIP Network Name is required
+
-
-
- Selecting a Standalone Cluster VIP Network CIDR is required
- Selecting a Management VIP Network CIDR is required
+
+
+ Selecting a Standalone
+ Cluster VIP Network CIDR is required
+
+ Selecting a Management
+ VIP Network CIDR is required
+
-
-
- {{htmlFieldLabels['clusterLabels']}}
-
-
-
-
- By default, all clusters will have NSX Advanced Load Balancer enabled. Here you may
- optionally specify cluster labels to identify a subset of clusters that should have
- NSX
- Advanced Load Balancer enabled. Note: Ensure that these labels are present on
- individual
- clusters that should be enabled with NSX Advanced Load Balancer.
-
-
-
-
-
-
- By default, all clusters will have NSX Advanced Load Balancer enabled. Here you may optionally
- specify cluster labels to identify a subset of clusters that should have NSX Advanced Load Balancer
- enabled.
-
-
-
-
-
-
-
-
-
-
-
-
-
- :
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Workload Cluster Label Keys must start and end with an alphanumeric character, and can contain only
- letters, numbers, hyphens, underscores, and dots.
-
-
- Workload Cluster Label Keys must not include whitespace on ends.
-
-
- Workload Cluster Label Keys max length is 63 characters.
-
-
- :
-
-
-
-
-
- Workload Cluster Label Values must start and end with an alphanumeric character, and can contain only
- letters, numbers, hyphens, underscores, and dots.
-
-
- Workload Cluster Label Values must not include whitespace on ends.
-
-
- Workload Cluster Label Keys max length is 63 characters.
-
-
-
-
+
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.spec.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.spec.ts
index d507c91209..97d84498c3 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.spec.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.spec.ts
@@ -1,13 +1,11 @@
// Angular imports
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { ReactiveFormsModule } from '@angular/forms';
-import { FormBuilder } from '@angular/forms';
+import { FormArray, FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
// App imports
import { APIClient } from '../../../../../../../swagger/api-client.service';
import AppServices from 'src/app/shared/service/appServices';
-import { FieldMapUtilities } from '../../../field-mapping/FieldMapUtilities';
import { Messenger } from 'src/app/shared/service/Messenger';
import { SharedLoadBalancerStepComponent } from './load-balancer-step.component';
import { SharedModule } from '../../../../../../../shared/shared.module';
@@ -43,12 +41,20 @@ describe('SharedLoadBalancerStepComponent', () => {
const fb = new FormBuilder();
fixture = TestBed.createComponent(SharedLoadBalancerStepComponent);
component = fixture.componentInstance;
- component.formGroup = fb.group({
- });
+ component.formGroup = fb.group({});
fixture.detectChanges();
});
+ it('should initialize tkgLabelsConfig', () => {
+ component.ngOnInit();
+ const config = component.tkgLabelsConfig;
+
+ expect(config.label.title).toEqual('CLUSTER LABELS (OPTIONAL)');
+ expect(config.forms.parent.get('clusterLabels')).toBeInstanceOf(FormArray);
+ expect(config.fields.clusterTypeDescriptor).toEqual('Workload');
+ })
+
it('should call get clouds when controller credentials have been validated', () => {
const apiSpy = spyOn(component['apiClient'], 'getAviClouds').and.callThrough();
component.getClouds();
@@ -60,18 +66,4 @@ describe('SharedLoadBalancerStepComponent', () => {
component.getServiceEngineGroups();
expect(apiSpy).toHaveBeenCalled();
});
-
- it('should add new label', () => {
- component.addLabel("somekey", "someval");
- component.addLabel("somekey2", "someval2");
- expect(component.labels.get("somekey")).toEqual("someval");
- expect(component.labels.get("somekey2")).toEqual("someval2");
- });
-
- it('should delete existing label', () => {
- component.addLabel("akey", "avalue");
- expect(component.labels.get("akey")).toEqual('avalue');
- component.deleteLabel("akey");
- expect(component.labels.get("akey")).toBeFalsy();
- });
});
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.ts
index 5f645112bb..8a98c1d1aa 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.component.ts
@@ -1,20 +1,21 @@
// Angular imports
import { Component, OnInit } from '@angular/core';
-import { Validators } from '@angular/forms';
+import { FormArray, Validators } from '@angular/forms';
+import { ClrLoadingState } from "@clr/angular";
// Third party imports
import { debounceTime, distinctUntilChanged, finalize, takeUntil } from 'rxjs/operators';
-// App imports
-import { APIClient } from "../../../../../../../swagger";
-import AppServices from '../../../../../../../shared/service/appServices';
+import { IpFamilyEnum } from 'src/app/shared/constants/app.constants';
import { AviCloud } from "src/app/swagger/models/avi-cloud.model";
import { AviServiceEngineGroup } from "src/app/swagger/models/avi-service-engine-group.model";
-import { AviVipNetwork } from './../../../../../../../swagger/models/avi-vip-network.model';
-import { ClrLoadingState } from "@clr/angular";
-import { IpFamilyEnum } from 'src/app/shared/constants/app.constants';
-import { LoadBalancerField, LoadBalancerStepMapping } from './load-balancer-step.fieldmapping';
-import { StepFormDirective } from "../../../step-form/step-form";
+import AppServices from '../../../../../../../shared/service/appServices';
+// App imports
+import { APIClient } from "../../../../../../../swagger";
import { StepMapping } from '../../../field-mapping/FieldMapping';
+import { StepFormDirective } from "../../../step-form/step-form";
import { ValidationService } from "../../../validation/validation.service";
+import { TKGLabelsConfig } from '../../widgets/tkg-labels/interfaces/tkg-labels.interface';
+import { AviVipNetwork } from './../../../../../../../swagger/models/avi-vip-network.model';
+import { LoadBalancerField, LoadBalancerStepMapping } from './load-balancer-step.fieldmapping';
const SupervisedFields = [
LoadBalancerField.CONTROLLER_HOST,
@@ -37,11 +38,11 @@ export class SharedLoadBalancerStepComponent extends StepFormDirective implement
selectedCloudName: string;
serviceEngineGroups: Array;
serviceEngineGroupsFiltered: Array;
- labels: Map = new Map();
vipNetworks: Array = [];
selectedNetworkName: string;
selectedManagementClusterNetworkName: string;
loadBalancerLabel = 'Load Balancer Settings';
+ tkgLabelsConfig: TKGLabelsConfig;
private stepMapping: StepMapping;
@@ -50,67 +51,28 @@ export class SharedLoadBalancerStepComponent extends StepFormDirective implement
super();
}
- protected customizeForm() {
- SupervisedFields.forEach(field => {
- this.formGroup.get(field).valueChanges
- .pipe(
- debounceTime(500),
- distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)),
- takeUntil(this.unsubscribe)
- )
- .subscribe(() => {
- if (this.connected) {
- this.connected = false;
- this.disarmField(LoadBalancerField.CLOUD_NAME, true);
- this.clouds = [];
- this.disarmField(LoadBalancerField.SERVICE_ENGINE_GROUP_NAME, true);
- this.serviceEngineGroups = [];
- this.disarmField(LoadBalancerField.NETWORK_CIDR, true);
- this.disarmField(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR, true);
-
- // If connection cleared, toggle validators OFF
- this.toggleValidators(false);
- }
- });
- });
-
- this.formGroup.get(LoadBalancerField.CLOUD_NAME).valueChanges.pipe(
- distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)),
- takeUntil(this.unsubscribe)
- ).subscribe((cloud) => {
- this.selectedCloudName = cloud;
- this.onSelectCloud(this.selectedCloudName);
- });
-
- this.registerOnValueChange("networkName", this.onSelectVipNetwork.bind(this));
- this.registerOnValueChange("networkCIDR", this.onSelectVipCIDR.bind(this));
- this.registerOnValueChange(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_NAME, this.onSelectManagementNetwork.bind(this));
- this.registerOnIpFamilyChange(LoadBalancerField.NETWORK_CIDR, [], []);
- this.registerOnIpFamilyChange(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR, [
- this.validationService.isValidIpNetworkSegment()], [
- this.validationService.isValidIpv6NetworkSegment()
- ]);
+ /**
+ * This is to make sense that the list returned is always up to date.
+ */
+ get vipNetworksPerCloud() {
+ if (this.vipNetworks && this.vipNetworks.length > 0 && this.selectedCloud) {
+ return this.vipNetworks.filter(net => net.cloud === this.selectedCloud.uuid);
+ }
+ return [];
}
- private supplyStepMapping(): StepMapping {
- if (!this.stepMapping) {
- this.stepMapping = this.createStepMapping();
- }
- return this.stepMapping;
+ /**
+ * This is to make sense that the list returned is always up to date.
+ */
+ get subnetsPerNetwork() {
+ return this.getSubnets(this.selectedNetworkName);
}
- private createStepMapping(): StepMapping {
- const result = LoadBalancerStepMapping;
- const managementClusterNetworkNameMapping = AppServices.fieldMapUtilities.getFieldMapping('managementClusterNetworkName', result);
- const managementClusterNetworkCidrMapping = AppServices.fieldMapUtilities.getFieldMapping('managementClusterNetworkCIDR', result);
- if (this.modeClusterStandalone) {
- managementClusterNetworkNameMapping.label = 'STANDALONE CLUSTER VIP NETWORK NAME';
- managementClusterNetworkCidrMapping.label = 'STANDALONE CLUSTER VIP NETWORK CIDR';
- }
- const clusterFieldMapping = AppServices.fieldMapUtilities.getFieldMapping(LoadBalancerField.CLUSTER_LABELS, result);
- clusterFieldMapping.retriever = this.getClusterLabels.bind(this);
- clusterFieldMapping.restorer = this.setClusterLabels.bind(this);
- return result;
+ /**
+ * This is to make sense that the list returned is always up to date.
+ */
+ get subnetsPerManagementNetwork() {
+ return this.getSubnets(this.selectedManagementClusterNetworkName);
}
ngOnInit() {
@@ -122,14 +84,27 @@ export class SharedLoadBalancerStepComponent extends StepFormDirective implement
this.registerDefaultFileImportErrorHandler(this.eventFileImportError);
this.customizeForm();
- }
-
- private setClusterLabels(data: Map) {
- return this.labels = data;
- }
- private getClusterLabels(): Map {
- return this.labels;
+ this.tkgLabelsConfig = {
+ label: {
+ title: this.htmlFieldLabels['clusterLabels'],
+ tooltipText: `By default, all clusters will have NSX Advanced Load Balancer enabled. Here you may
+ optionally specify cluster labels to identify a subset of clusters that should have
+ NSX Advanced Load Balancer enabled. Note: Ensure that these labels are present on
+ individual clusters that should be enabled with NSX Advanced Load Balancer.`,
+ helperText: `By default, all clusters will have NSX Advanced Load Balancer enabled. Here you may optionally
+ specify cluster labels to identify a subset of clusters that should have NSX Advanced Load Balancer
+ enabled.`
+ },
+ forms: {
+ parent: this.formGroup,
+ control: this.formGroup.get('clusterLabels') as FormArray
+ },
+ fields: {
+ clusterTypeDescriptor: 'Workload',
+ fieldMapping: LoadBalancerStepMapping.fieldMappings.find((m) => m.name === LoadBalancerField.CLUSTER_LABELS)
+ }
+ };
}
/**
@@ -249,7 +224,9 @@ export class SharedLoadBalancerStepComponent extends StepFormDirective implement
this.serviceEngineGroupsFiltered = [];
if (cloudName && this.clouds) {
- this.selectedCloud = this.clouds.find((cloud: AviCloud) => { return cloud.name === cloudName; });
+ this.selectedCloud = this.clouds.find((cloud: AviCloud) => {
+ return cloud.name === cloudName;
+ });
if (this.selectedCloud) {
this.serviceEngineGroupsFiltered = this.serviceEngineGroups.filter((group: AviServiceEngineGroup) => {
return group.location.includes(this.selectedCloud.uuid);
@@ -263,13 +240,14 @@ export class SharedLoadBalancerStepComponent extends StepFormDirective implement
*/
onSelectVipNetwork(networkName: string): void {
this.selectedNetworkName = networkName;
- if (!this.formGroup.get(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_NAME).value) { }
+ if (!this.formGroup.get(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_NAME).value) {
+ }
this.formGroup.get(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_NAME).setValue(networkName)
}
onSelectVipCIDR(cidr: string): void {
if (!this.formGroup.get(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR).value) {
- this.formGroup.get(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR).setValue(cidr);
+ this.formGroup.get(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR).setValue(cidr);
}
}
@@ -315,51 +293,10 @@ export class SharedLoadBalancerStepComponent extends StepFormDirective implement
getDisabled(): boolean {
return (
SupervisedFields.some(f => !this.formGroup.get(f).value) ||
- SupervisedFields.some(f => !this.formGroup.get(f).valid)
+ SupervisedFields.some(f => !this.formGroup.get(f).valid)
)
}
- /**
- * Add workload cluster label'
- */
- addLabel(key: string, value: string) {
- if (key === '' || value === '') {
- this.errorNotification = `Key and value for Labels are required.`;
- } else if (!this.labels.has(key)) {
- this.labels.set(key, value);
- this.formGroup.controls[LoadBalancerField.NEW_LABEL_KEY].setValue('');
- this.formGroup.controls[LoadBalancerField.NEW_LABEL_VALUE].setValue('');
- } else {
- this.errorNotification = `A Label with the same key already exists.`;
- }
- }
-
- /**
- * Delete workload cluster label'
- */
- deleteLabel(key: string) {
- this.labels.delete(key);
- }
-
- /**
- * @method getLabelDisabled
- * helper method to get if label add btn should be disabled
- */
- getLabelDisabled(): boolean {
- return !(this.formGroup.get(LoadBalancerField.NEW_LABEL_KEY).valid &&
- this.formGroup.get(LoadBalancerField.NEW_LABEL_VALUE).valid);
- }
-
- /**
- * This is to make sense that the list returned is always up to date.
- */
- get vipNetworksPerCloud() {
- if (this.vipNetworks && this.vipNetworks.length > 0 && this.selectedCloud) {
- return this.vipNetworks.filter(net => net.cloud === this.selectedCloud.uuid);
- }
- return [];
- }
-
getSubnets(networkName: string): any[] {
if (!this.isEmptyArray(this.vipNetworksPerCloud) && networkName) {
const temp = this.vipNetworksPerCloud
@@ -372,22 +309,69 @@ export class SharedLoadBalancerStepComponent extends StepFormDirective implement
}
return [];
}
- /**
- * This is to make sense that the list returned is always up to date.
- */
- get subnetsPerNetwork() {
- return this.getSubnets(this.selectedNetworkName);
- }
- /**
- * This is to make sense that the list returned is always up to date.
- */
- get subnetsPerManagementNetwork() {
- return this.getSubnets(this.selectedManagementClusterNetworkName);
+ protected customizeForm() {
+ SupervisedFields.forEach(field => {
+ this.formGroup.get(field).valueChanges
+ .pipe(
+ debounceTime(500),
+ distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)),
+ takeUntil(this.unsubscribe)
+ )
+ .subscribe(() => {
+ if (this.connected) {
+ this.connected = false;
+ this.disarmField(LoadBalancerField.CLOUD_NAME, true);
+ this.clouds = [];
+ this.disarmField(LoadBalancerField.SERVICE_ENGINE_GROUP_NAME, true);
+ this.serviceEngineGroups = [];
+ this.disarmField(LoadBalancerField.NETWORK_CIDR, true);
+ this.disarmField(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR, true);
+
+ // If connection cleared, toggle validators OFF
+ this.toggleValidators(false);
+ }
+ });
+ });
+
+ this.formGroup.get(LoadBalancerField.CLOUD_NAME).valueChanges.pipe(
+ distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)),
+ takeUntil(this.unsubscribe)
+ ).subscribe((cloud) => {
+ this.selectedCloudName = cloud;
+ this.onSelectCloud(this.selectedCloudName);
+ });
+
+ this.registerOnValueChange("networkName", this.onSelectVipNetwork.bind(this));
+ this.registerOnValueChange("networkCIDR", this.onSelectVipCIDR.bind(this));
+ this.registerOnValueChange(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_NAME, this.onSelectManagementNetwork.bind(this));
+ this.registerOnIpFamilyChange(LoadBalancerField.NETWORK_CIDR, [], []);
+ this.registerOnIpFamilyChange(LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR, [
+ this.validationService.isValidIpNetworkSegment()], [
+ this.validationService.isValidIpv6NetworkSegment()
+ ]);
}
protected storeUserData() {
this.storeUserDataFromMapping(this.supplyStepMapping());
this.storeDefaultDisplayOrder(this.supplyStepMapping());
}
+
+ private supplyStepMapping(): StepMapping {
+ if (!this.stepMapping) {
+ this.stepMapping = this.createStepMapping();
+ }
+ return this.stepMapping;
+ }
+
+ private createStepMapping(): StepMapping {
+ const result = LoadBalancerStepMapping;
+ const managementClusterNetworkNameMapping = AppServices.fieldMapUtilities.getFieldMapping('managementClusterNetworkName', result);
+ const managementClusterNetworkCidrMapping = AppServices.fieldMapUtilities.getFieldMapping('managementClusterNetworkCIDR', result);
+ if (this.modeClusterStandalone) {
+ managementClusterNetworkNameMapping.label = 'STANDALONE CLUSTER VIP NETWORK NAME';
+ managementClusterNetworkCidrMapping.label = 'STANDALONE CLUSTER VIP NETWORK CIDR';
+ }
+ return result;
+ }
}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.fieldmapping.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.fieldmapping.ts
index c2c8d926e1..61f2121900 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.fieldmapping.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/load-balancer/load-balancer-step.fieldmapping.ts
@@ -1,4 +1,4 @@
-import { StepMapping } from '../../../field-mapping/FieldMapping';
+import { ControlType, StepMapping } from '../../../field-mapping/FieldMapping';
import { SimpleValidator } from '../../../constants/validation.constants';
export enum LoadBalancerField {
@@ -19,19 +19,47 @@ export enum LoadBalancerField {
export const LoadBalancerStepMapping: StepMapping = {
fieldMappings: [
- { name: LoadBalancerField.CONTROLLER_HOST, validators: [SimpleValidator.IS_VALID_FQDN_OR_IP], label: 'CONTROLLER HOST' },
- { name: LoadBalancerField.USERNAME, label: 'USERNAME' },
- { name: LoadBalancerField.PASSWORD, mask: true, label: 'PASSWORD' },
- { name: LoadBalancerField.CLOUD_NAME, label: 'CLOUD NAME' },
- { name: LoadBalancerField.SERVICE_ENGINE_GROUP_NAME, label: 'SERVICE ENGINE GROUP NAME' },
- { name: LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_NAME, label: 'MANAGEMENT VIP NETWORK NAME' },
- { name: LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR, validators: [SimpleValidator.IS_VALID_IP_NETWORK_SEGMENT],
- label: 'MANAGEMENT VIP NETWORK CIDR' },
- { name: LoadBalancerField.NETWORK_NAME, label: 'WORKLOAD VIP NETWORK NAME' },
- { name: LoadBalancerField.NETWORK_CIDR, label: 'WORKLOAD VIP NETWORK CIDR' },
- { name: LoadBalancerField.CONTROLLER_CERT, doNotAutoSave: true, label: 'CONTROLLER CERTIFICATE AUTHORITY' },
- { name: LoadBalancerField.CLUSTER_LABELS, hasNoDomControl: true, isMap: true, label: 'CLUSTER LABELS (OPTIONAL)' },
- { name: LoadBalancerField.NEW_LABEL_KEY, validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION], neverStore: true },
- { name: LoadBalancerField.NEW_LABEL_VALUE, validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION], neverStore: true },
+ {
+ name: LoadBalancerField.CONTROLLER_HOST,
+ validators: [SimpleValidator.IS_VALID_FQDN_OR_IP],
+ label: 'CONTROLLER HOST'
+ },
+ {name: LoadBalancerField.USERNAME, label: 'USERNAME'},
+ {name: LoadBalancerField.PASSWORD, mask: true, label: 'PASSWORD'},
+ {name: LoadBalancerField.CLOUD_NAME, label: 'CLOUD NAME'},
+ {name: LoadBalancerField.SERVICE_ENGINE_GROUP_NAME, label: 'SERVICE ENGINE GROUP NAME'},
+ {name: LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_NAME, label: 'MANAGEMENT VIP NETWORK NAME'},
+ {
+ name: LoadBalancerField.MANAGEMENT_CLUSTER_NETWORK_CIDR,
+ validators: [SimpleValidator.IS_VALID_IP_NETWORK_SEGMENT],
+ label: 'MANAGEMENT VIP NETWORK CIDR'
+ },
+ {name: LoadBalancerField.NETWORK_NAME, label: 'WORKLOAD VIP NETWORK NAME'},
+ {name: LoadBalancerField.NETWORK_CIDR, label: 'WORKLOAD VIP NETWORK CIDR'},
+ {name: LoadBalancerField.CONTROLLER_CERT, doNotAutoSave: true, label: 'CONTROLLER CERTIFICATE AUTHORITY'},
+ {
+ name: LoadBalancerField.CLUSTER_LABELS,
+ label: 'CLUSTER LABELS (OPTIONAL)',
+ controlType: ControlType.FormArray,
+ displayFunction: labels => labels.map(label => `${label.key} : ${label.value}`).join(', '),
+ children: [
+ {
+ name: 'key',
+ defaultValue: '',
+ controlType: ControlType.FormControl,
+ validators: [
+ SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION,
+ SimpleValidator.RX_UNIQUE,
+ SimpleValidator.RX_REQUIRED_IF_VALUE
+ ]
+ },
+ {
+ name: 'value',
+ defaultValue: '',
+ controlType: ControlType.FormControl,
+ validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION, SimpleValidator.RX_REQUIRED_IF_KEY]
+ }
+ ]
+ }
]
-}
+};
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.html b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.html
index 34c0c954fa..8f66db7049 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.html
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.html
@@ -33,17 +33,21 @@
-
+
-
- {{ clusterTypeDescriptorTitleCase }} Cluster Location must start and end with an alphanumeric character, and can contain only
+
+ {{ clusterTypeDescriptorTitleCase }} Cluster Location must start and end with an alphanumeric
+ character, and can contain only
letters, numbers, hyphens, underscores, and dots.
-
+
{{ clusterTypeDescriptorTitleCase }} Cluster Location must not include whitespace on ends.
-
+
{{ clusterTypeDescriptorTitleCase }} Cluster Location max length is 63 characters.
@@ -63,12 +67,16 @@
-
+
-
- {{ clusterTypeDescriptorTitleCase }} Cluster Description must start and end with a letter, and can contain only
+ placeholder="optional" aria-label="description"
+ aria-describedby="cluster-description-error">
+
+ {{ clusterTypeDescriptorTitleCase }} Cluster Description must start and end with a letter,
+ and can contain only
lowercase letters, numbers, and hyphens. It must not include whitespace on ends. It has
a max length of 63 characters.
-
-
-
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label keys must start and end with an alphanumeric character, and can contain only
- letters, numbers, hyphens, underscores, and dots.
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label keys must not include whitespace on ends.
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label keys max length is 63 characters.
-
-
- A Label with the same key already exists.
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label value is required if its value is not empty.
-
-
- :
-
-
-
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label values must start and end with an alphanumeric character, and can contain only
- letters, numbers, hyphens, underscores, and dots.
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label values must not include whitespace on ends.
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label values max length is 63 characters.
-
-
- {{ clusterTypeDescriptorTitleCase }} Cluster label value is required if its key is not empty.
-
-
-
-
-
-
-
-
-
+
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.spec.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.spec.ts
index 4a045cdf87..6543c08315 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.spec.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.spec.ts
@@ -1,12 +1,10 @@
// Angular imports
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { FormBuilder } from '@angular/forms';
-import { ReactiveFormsModule } from '@angular/forms';
+import { FormArray, FormBuilder, ReactiveFormsModule } from '@angular/forms';
// App imports
import { APIClient } from '../../../../../../../swagger/api-client.service';
import AppServices from 'src/app/shared/service/appServices';
-import { FieldMapUtilities } from '../../../field-mapping/FieldMapUtilities';
import { Messenger, TanzuEventType } from 'src/app/shared/service/Messenger';
import { MetadataStepComponent } from './metadata-step.component';
import { SharedModule } from '../../../../../../../shared/shared.module';
@@ -42,30 +40,26 @@ describe('MetadataStepComponent', () => {
fixture = TestBed.createComponent(MetadataStepComponent);
component = fixture.componentInstance;
// NOTE: using Azure file import events just for testing
- component.setStepRegistrantData({ wizard: 'BozoWizard', step: WizardForm.METADATA, formGroup: new FormBuilder().group({}),
+ component.setStepRegistrantData({
+ wizard: 'BozoWizard', step: WizardForm.METADATA, formGroup: new FormBuilder().group({}),
eventFileImported: TanzuEventType.AZURE_CONFIG_FILE_IMPORTED,
- eventFileImportError: TanzuEventType.AZURE_CONFIG_FILE_IMPORT_ERROR});
+ eventFileImportError: TanzuEventType.AZURE_CONFIG_FILE_IMPORT_ERROR
+ });
+
component.ngOnInit();
fixture.detectChanges();
});
- it('should add new label', () => {
- component.addLabel("somekey", "someval");
- component.addLabel("somekey2", "someval2");
- const labels = component.getClusterLabels();
- expect(labels.get("somekey")).toEqual("someval");
- expect(labels.get("somekey2")).toEqual("someval2");
- });
+ it('should initialize tkgLabelsConfig', () => {
+ component.setClusterTypeDescriptor('Management');
+ component.ngOnInit();
+ const config = component.tkgLabelsConfig;
- it('should delete existing label', () => {
- component.addLabel("akey", "avalue");
- let labels = component.getClusterLabels();
- expect(labels.get("akey")).toEqual("avalue");
- component.deleteLabel("newLabelKey2");
- labels = component.getClusterLabels();
- expect(labels.get("newLabelKey2")).toBeFalsy();
- });
+ expect(config.label.title).toEqual('LABELS (OPTIONAL)');
+ expect(config.forms.parent.get('clusterLabels')).toBeInstanceOf(FormArray);
+ expect(config.fields.clusterTypeDescriptor).toEqual('Management');
+ })
it('should announce description change', () => {
const msgSpy = spyOn(AppServices.messenger, 'publish').and.callThrough();
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.ts
index b625ed04df..fcc840c676 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.component.ts
@@ -1,17 +1,12 @@
// Angular imports
import { Component, OnInit } from '@angular/core';
-import { FormControl, Validators } from '@angular/forms';
-
+import { FormArray } from '@angular/forms';
// App imports
import AppServices from '../../../../../../../shared/service/appServices';
-import { FormUtils } from '../../../utils/form-utils';
-import { MetadataField, MetadataStepMapping } from './metadata-step.fieldmapping';
-import { StepFormDirective } from '../../../step-form/step-form';
import { StepMapping } from '../../../field-mapping/FieldMapping';
-import { ValidationService } from '../../../validation/validation.service';
-
-const LABEL_KEY_NAME = 'newLabelKey';
-const LABEL_VALUE_NAME = 'newLabelValue';
+import { StepFormDirective } from '../../../step-form/step-form';
+import { TKGLabelsConfig } from '../../widgets/tkg-labels/interfaces/tkg-labels.interface';
+import { MetadataField, MetadataStepMapping } from './metadata-step.fieldmapping';
@Component({
selector: 'app-metadata-step',
@@ -19,172 +14,50 @@ const LABEL_VALUE_NAME = 'newLabelValue';
styleUrls: ['./metadata-step.component.scss']
})
export class MetadataStepComponent extends StepFormDirective implements OnInit {
- labels: Map = new Map();
- keySet: Set = new Set();
- savedKeySet: Set = new Set();
- labelCounter: number = 0;
- private stepMapping: StepMapping;
+ tkgLabelsConfig: TKGLabelsConfig;
- constructor(private validationService: ValidationService) {
+ constructor() {
super();
}
ngOnInit() {
super.ngOnInit();
- AppServices.userDataFormService.buildForm(this.formGroup, this.wizardName, this.formName, this.supplyStepMapping());
- this.htmlFieldLabels = AppServices.fieldMapUtilities.getFieldLabelMap(this.supplyStepMapping());
- this.storeDefaultLabels(this.supplyStepMapping());
+ AppServices.userDataFormService.buildForm(this.formGroup, this.wizardName, this.formName, MetadataStepMapping);
+ this.htmlFieldLabels = AppServices.fieldMapUtilities.getFieldLabelMap(MetadataStepMapping);
+ this.storeDefaultLabels(MetadataStepMapping);
this.registerStepDescriptionTriggers({
fields: [MetadataField.CLUSTER_LOCATION],
- clusterTypeDescriptor: true,
- })
- this.registerDefaultFileImportedHandler(this.eventFileImported, this.supplyStepMapping());
- this.registerDefaultFileImportErrorHandler(this.eventFileImportError);
-
- // initialize label controls
- if (this.labels.size === 0) {
- this.addLabel();
- }
- }
-
- private supplyStepMapping(): StepMapping {
- if (!this.stepMapping) {
- this.stepMapping = this.createStepMapping();
- }
- return this.stepMapping;
- }
-
- private createStepMapping() {
- const result = MetadataStepMapping;
- const clusterFieldMapping = AppServices.fieldMapUtilities.getFieldMapping(MetadataField.CLUSTER_LABELS, result);
- clusterFieldMapping.retriever = this.getClusterLabels.bind(this);
- clusterFieldMapping.restorer = this.setClusterLabels.bind(this);
- return result;
- }
-
- // TODO: the 'labels' field now holds a keyField => valueField mapping, so when receiving the data, we build new controls to hold data
- private setClusterLabels(data: Map) {
- this.clearLabels();
- // ADD new ones
- for (const [key, value] of data) {
- this.addLabel(key, value);
- }
- // ensure at least one field
- if (this.labels.size === 0) {
- this.addLabel();
- }
- }
-
- private clearLabels() {
- // REMOVE existing label fields
- for (const [keyField, valueField] of this.labels) {
- this.formGroup.removeControl(keyField);
- this.formGroup.removeControl(valueField);
- }
- this.labels = new Map();
- this.keySet = new Set();
- this.labelCounter = 0;
- }
-
- // TODO: the 'labels' field holds a keyField => valueField mapping, so when returning the data, we build a new map from field data
- // TODO: public for testing only
- getClusterLabels(): Map {
- const result = new Map();
- for (const [keyField, valueField] of this.labels) {
- const key = this.formGroup.get(keyField).value;
- const val = this.formGroup.get(valueField).value;
- if (key && val) {
- result.set(key, val);
- }
- }
- return result;
- }
-
- addLabel(key?: string, value?: string) {
- this.labelCounter++;
- this.labels.set(LABEL_KEY_NAME + this.labelCounter, LABEL_VALUE_NAME + this.labelCounter);
- this.keySet.add(LABEL_KEY_NAME + this.labelCounter);
- FormUtils.addControl(
- this.formGroup,
- LABEL_KEY_NAME + this.labelCounter,
- new FormControl(key || '', [
- this.validationService.isValidLabelOrAnnotation(),
- this.validationService.isUniqueLabel(
- this.formGroup,
- this.keySet,
- LABEL_KEY_NAME + this.labelCounter)
- ])
- );
-
- FormUtils.addControl(
- this.formGroup,
- LABEL_VALUE_NAME + this.labelCounter,
- new FormControl(value || '', [
- this.validationService.isValidLabelOrAnnotation()
- ])
- );
- // Label value depends on Label key. e.g.: if label key is not empty, then label value is required
- this.onChangeWithDependentField(LABEL_KEY_NAME + this.labelCounter, LABEL_VALUE_NAME + this.labelCounter);
- // Label key depends on Label value. e.g.: if label value is not empty, then label key is required
- this.onChangeWithDependentField(LABEL_VALUE_NAME + this.labelCounter, LABEL_KEY_NAME + this.labelCounter);
- this.validateAllLabels();
- }
-
- /**
- * @method onChangeWithDependentField
- * make the dependent field is required if the indepdent field is not empty.
- * @param fieldName is a independent field which determines if the dependent field is required.
- * @param dependentFieldName is dependent on the independent field.
- */
- onChangeWithDependentField(fieldName: string, dependentFieldName: string) {
- const control = this.formGroup.get(dependentFieldName);
- this.registerOnValueChange(fieldName, (data) => {
- if (data !== '') {
- if (!control.hasValidator(Validators.required)) {
- control.addValidators(Validators.required);
- control.markAsPending(); // validation will not be triggered until the field is touched.
- control.setErrors({required: true});
- }
- } else {
- control.removeValidators(Validators.required);
- }
- this.validateAllLabels(); // all the same label keys can show error message.
+ clusterTypeDescriptor: true
});
- }
+ this.registerDefaultFileImportedHandler(this.eventFileImported, MetadataStepMapping);
+ this.registerDefaultFileImportErrorHandler(this.eventFileImportError);
- validateAllLabels () {
- // The setTimeout wrapper ensures that validation logic will run after a new label field is added.
- setTimeout(_ => {
- for (const [labelKey, labelVal] of this.labels) {
- const key = this.formGroup.get(labelKey);
- const val = this.formGroup.get(labelVal);
- if (key) {
- if (this.savedKeySet.has(labelKey)) {
- key.markAsTouched();
- }
- key.updateValueAndValidity();
- }
- if (val) {
- val.updateValueAndValidity();
- }
+ this.tkgLabelsConfig = {
+ label: {
+ title: this.htmlFieldLabels['clusterLabels'],
+ tooltipText: `Optionally specify labels for the ${this.clusterTypeDescriptor} cluster.`
+ },
+ forms: {
+ parent: this.formGroup,
+ control: this.formGroup.get('clusterLabels') as FormArray
+ },
+ fields: {
+ clusterTypeDescriptor: this.clusterTypeDescriptorTitleCase,
+ fieldMapping: MetadataStepMapping.fieldMappings.find((m) => m.name === MetadataField.CLUSTER_LABELS)
}
- });
- }
-
- deleteLabel(key: string) {
- this.formGroup.removeControl(key);
- this.formGroup.removeControl(this.labels.get(key));
- this.labels.delete(key);
- this.keySet.delete(key);
+ };
}
dynamicDescription(): string {
const clusterLocation = this.getFieldValue(MetadataField.CLUSTER_LOCATION, true);
- return clusterLocation ? 'Location: ' + clusterLocation : 'Specify metadata for the ' + this.clusterTypeDescriptor + ' cluster';
+ return clusterLocation
+ ? `Location: ${clusterLocation}`
+ : `Specify metadata for the ${this.clusterTypeDescriptor} cluster`;
}
protected storeUserData() {
- this.storeUserDataFromMapping(this.supplyStepMapping());
- this.storeDefaultDisplayOrder(this.supplyStepMapping());
+ this.storeUserDataFromMapping(MetadataStepMapping);
+ this.storeDefaultDisplayOrder(MetadataStepMapping);
}
+
}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.fieldmapping.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.fieldmapping.ts
index f20a9968c8..fd75adc5d7 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.fieldmapping.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/steps/metadata-step/metadata-step.fieldmapping.ts
@@ -1,21 +1,47 @@
-import { StepMapping } from '../../../field-mapping/FieldMapping';
+import { ControlType, StepMapping } from '../../../field-mapping/FieldMapping';
import { SimpleValidator } from '../../../constants/validation.constants';
export enum MetadataField {
CLUSTER_LABELS = 'clusterLabels',
CLUSTER_DESCRIPTION = 'clusterDescription',
- CLUSTER_LOCATION = 'clusterLocation'
+ CLUSTER_LOCATION = 'clusterLocation',
}
export const MetadataStepMapping: StepMapping = {
fieldMappings: [
- { name: MetadataField.CLUSTER_LOCATION, validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION], label: 'LOCATION (OPTIONAL)' },
- { name: MetadataField.CLUSTER_DESCRIPTION, validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION], label: 'DESCRIPTION (OPTIONAL)' },
- { name: MetadataField.CLUSTER_LABELS, hasNoDomControl: true, isMap: true, label: 'LABELS (OPTIONAL)' },
+ {
+ name: MetadataField.CLUSTER_LOCATION,
+ validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION],
+ label: 'LOCATION (OPTIONAL)'
+ },
+ {
+ name: MetadataField.CLUSTER_DESCRIPTION,
+ validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION],
+ label: 'DESCRIPTION (OPTIONAL)'
+ },
+ {
+ name: MetadataField.CLUSTER_LABELS,
+ label: 'LABELS (OPTIONAL)',
+ controlType: ControlType.FormArray,
+ displayFunction: labels => labels.map(label => `${label.key} : ${label.value}`).join(', '),
+ children: [
+ {
+ name: 'key',
+ defaultValue: '',
+ controlType: ControlType.FormControl,
+ validators: [
+ SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION,
+ SimpleValidator.RX_UNIQUE,
+ SimpleValidator.RX_REQUIRED_IF_VALUE
+ ]
+ },
+ {
+ name: 'value',
+ defaultValue: '',
+ controlType: ControlType.FormControl,
+ validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION, SimpleValidator.RX_REQUIRED_IF_KEY]
+ }
+ ]
+ }
]
-}
-// About MetadataStep:
-// The clusterLabels field does not actually exist in the DOM; the values are held in the step component.
-// We use hasNoDomControl because the display value needs to be "manually" generated.
-// Note that there are DOM fields that hold various pieces of the cluster labels, but we ignore them in favor of a single "field"
-// that contains the entire map.
+};
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/interfaces/tkg-labels.interface.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/interfaces/tkg-labels.interface.ts
new file mode 100644
index 0000000000..6d8de9f9c4
--- /dev/null
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/interfaces/tkg-labels.interface.ts
@@ -0,0 +1,16 @@
+import { FormArray, FormGroup } from '@angular/forms';
+
+export interface TKGLabelsConfig {
+ label: {
+ title: string;
+ tooltipText: string;
+ helperText?: string;
+ };
+ forms: {
+ parent: FormGroup; // the parent form group
+ control: FormArray; // the control of the labels form array
+ };
+ fields: {
+ [key: string]: any;
+ };
+}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.html b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.html
new file mode 100644
index 0000000000..d2e1d836e6
--- /dev/null
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.html
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label keys must start and end with an
+ alphanumeric
+ character, and can contain only letters, numbers, hyphens, underscores, and dots.
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label keys must not include whitespace on ends.
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label keys max length is 63 characters.
+
+
+ A Label with the same key already exists.
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label value is required if its value is not
+ empty.
+
+
+
+ :
+
+
+
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label values must start and end with an
+ alphanumeric
+ character, and can contain only letters, numbers, hyphens, underscores, and dots.
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label values must not include whitespace on
+ ends.
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label values max length is 63 characters.
+
+
+ {{ config?.fields?.clusterTypeDescriptor }} Cluster label value is required if its key is not empty.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.scss b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.scss
new file mode 100644
index 0000000000..26ee508c8e
--- /dev/null
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.scss
@@ -0,0 +1,20 @@
+.label-details {
+ align-items: baseline;
+ padding-left: 0.6rem;
+}
+
+.btn-delete {
+ border: none;
+}
+
+.btn-add {
+ margin-top: 1rem;
+}
+
+.label-container {
+ margin-left: 0;
+}
+
+.err-label {
+ max-width: 8.7rem;
+}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.spec.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.spec.ts
new file mode 100644
index 0000000000..e0be42a58d
--- /dev/null
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.spec.ts
@@ -0,0 +1,106 @@
+import { TkgLabelsComponent } from './tkg-labels.component';
+import { FormArray, FormBuilder, FormControl, FormGroup } from "@angular/forms";
+import { ControlType } from "../../../field-mapping/FieldMapping";
+import { SimpleValidator } from "../../../constants/validation.constants";
+import { TKGLabelsConfig } from "./interfaces/tkg-labels.interface";
+import { FormUtils } from "../../../utils/form-utils";
+
+describe('TkgLabelsComponent', () => {
+ let component: TkgLabelsComponent;
+ let parentFormGroup: FormGroup;
+
+ const formBuilder = new FormBuilder();
+ const tkgLabelsConfig: TKGLabelsConfig = {
+ label: {
+ title: 'LABELS (OPTIONAL)',
+ tooltipText: `Optionally specify labels for the Management cluster.`
+ },
+ forms: {
+ parent: null,
+ control: null
+ },
+ fields: {
+ clusterType: 'Management',
+ fieldMapping: {
+ name: 'clusterLabels',
+ label: 'LABELS (OPTIONAL)',
+ controlType: ControlType.FormArray,
+ children: [
+ {
+ name: 'key',
+ defaultValue: '',
+ controlType: ControlType.FormControl,
+ validators: [
+ SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION,
+ SimpleValidator.RX_UNIQUE,
+ SimpleValidator.RX_REQUIRED_IF_VALUE
+ ]
+ },
+ {
+ name: 'value',
+ defaultValue: '',
+ controlType: ControlType.FormControl,
+ validators: [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION, SimpleValidator.RX_REQUIRED_IF_KEY]
+ }
+ ]
+ }
+ }
+ };
+
+ beforeEach(() => {
+ parentFormGroup = formBuilder.group({
+ clusterLabels: formBuilder.array([
+ formBuilder.group({
+ key: [''],
+ value: ['']
+ })
+ ])
+ });
+ tkgLabelsConfig.forms.parent = parentFormGroup;
+ tkgLabelsConfig.forms.control = parentFormGroup.get('clusterLabels') as FormArray;
+
+ component = new TkgLabelsComponent();
+ component.config = tkgLabelsConfig;
+ });
+
+ it('should create label', () => {
+ const group = component.createLabel();
+ expect(group.controls.key).toBeInstanceOf(FormControl);
+ expect(group.controls.value).toBeInstanceOf(FormControl);
+ })
+
+ it('should not add new Label on form invalid', () => {
+ spyOn(component.labelsFormArray, 'markAllAsTouched');
+ spyOn(component.labelsFormArray, 'push');
+
+ spyOnProperty(component.labelsFormArray, 'invalid').and.returnValue(true);
+
+ component.addNewLabel();
+
+ expect(component.labelsFormArray.markAllAsTouched).toHaveBeenCalledTimes(1);
+ expect(component.labelsFormArray.push).not.toHaveBeenCalledTimes(1);
+
+ });
+
+ it('should add new Label on form valid', () => {
+ spyOn(component.labelsFormArray, 'markAllAsTouched');
+ spyOn(FormUtils, 'addDynamicControl');
+
+ spyOnProperty(component.labelsFormArray, 'invalid').and.returnValue(false);
+
+ expect(component.labelsFormArray.controls.length).toEqual(1);
+
+ component.addNewLabel();
+
+ expect(component.labelsFormArray.markAllAsTouched).toHaveBeenCalledTimes(1);
+ expect(FormUtils.addDynamicControl).toHaveBeenCalledTimes(2);
+ expect(component.labelsFormArray.length).toEqual(2);
+
+ });
+
+ it('should delete label', () => {
+ component.deleteLabel(0);
+ expect(component.labelsFormArray.length).toEqual(0)
+ })
+
+});
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.ts
new file mode 100644
index 0000000000..c597271c4d
--- /dev/null
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/components/widgets/tkg-labels/tkg-labels.component.ts
@@ -0,0 +1,69 @@
+import { Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
+import { FormArray, FormGroup } from '@angular/forms';
+import { Subject } from 'rxjs';
+import { distinctUntilChanged, takeUntil } from 'rxjs/operators';
+import { ValidatorEnum } from '../../../constants/validation.constants';
+import { FieldMapping } from '../../../field-mapping/FieldMapping';
+import { FormUtils } from '../../../utils/form-utils';
+import { TKGLabelsConfig } from './interfaces/tkg-labels.interface';
+
+@Component({
+ selector: 'app-tkg-labels',
+ templateUrl: './tkg-labels.component.html',
+ styleUrls: ['./tkg-labels.component.scss']
+})
+export class TkgLabelsComponent implements OnChanges, OnDestroy {
+ @Input() config: TKGLabelsConfig;
+
+ validatorEnum = ValidatorEnum;
+
+ stopSubscriptions$ = new Subject();
+
+ get labelsFormArray(): FormArray {
+ return this.config.forms.control as FormArray;
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.config.previousValue !== changes.config.currentValue && changes.config.currentValue) {
+ this.labelsFormArray.valueChanges
+ .pipe(
+ takeUntil(this.stopSubscriptions$),
+ distinctUntilChanged((k, v) => JSON.stringify(k) === JSON.stringify(v))
+ )
+ .subscribe(() => {
+ this.labelsFormArray.controls.forEach((label: FormGroup) => {
+ (this.config.fields.fieldMapping as FieldMapping).children.forEach((field) =>
+ label.get(field.name).updateValueAndValidity()
+ );
+ });
+ });
+ }
+ }
+
+ createLabel(): FormGroup {
+ const labelFormGroup = new FormGroup({});
+ (this.config.fields.fieldMapping as FieldMapping).children.forEach((fieldMapping) =>
+ FormUtils.addDynamicControl(labelFormGroup, '', fieldMapping)
+ );
+
+ return labelFormGroup;
+ }
+
+ deleteLabel(index: number): void {
+ this.labelsFormArray.removeAt(index);
+ }
+
+ addNewLabel(): void {
+ this.labelsFormArray.markAllAsTouched();
+
+ if (this.labelsFormArray.invalid) {
+ return;
+ }
+ this.labelsFormArray.push(this.createLabel());
+ }
+
+ ngOnDestroy(): void {
+ this.stopSubscriptions$.next();
+ this.stopSubscriptions$.complete();
+ }
+}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/constants/validation.constants.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/constants/validation.constants.ts
index 2d9acad3c3..769ffd1273 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/constants/validation.constants.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/constants/validation.constants.ts
@@ -91,7 +91,8 @@ export enum ValidatorEnum {
VALID_CLUSTER_NAME = 'cluster name valid',
// Metadata label
- LABEL_UNIQUE = 'label unique'
+ LABEL_UNIQUE = 'label unique',
+ UNIQUE = 'unique'
}
// SimpleValidator identifies validators available from the Validation service
@@ -119,5 +120,8 @@ export enum SimpleValidator {
IS_VALID_PORT,
IS_VALID_RESOURCE_GROUP_NAME,
NO_WHITE_SPACE,
- NO_TRAILING_SLASH
+ NO_TRAILING_SLASH,
+ RX_UNIQUE,
+ RX_REQUIRED_IF_VALUE,
+ RX_REQUIRED_IF_KEY
}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/field-mapping/FieldMapping.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/field-mapping/FieldMapping.ts
index 49ed37bf3f..64f348b9e2 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/field-mapping/FieldMapping.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/field-mapping/FieldMapping.ts
@@ -32,7 +32,13 @@ export interface FieldMapping {
restorer?: (value: any) => void, // given a saved value (or a retrieved object) this closure will store it. Used esp w/hasNoDomControl
retriever?: (value: any) => any, // given a saved value, this closure will retrieve a backing object.
validators?: SimpleValidator[], // validators used by Clarity framework
+
+ // Enhanced Field Mapping
+ controlType?: ControlType // type of control to use for this field FormControl, FormArray, FormGroup
+ children?: FieldMapping[]
+ displayFunction?: (field) => string
}
+
// NOTES on FieldMapping:
// requiresBackendData:
// This is for fields that give the user the option to pick from a list of backend resources or options. The field should use a stored
@@ -61,3 +67,9 @@ export interface FieldMapping {
// retriever:
// For fields that use a JavaScript OBJECT (not a string or a Map), when the retrieves the saved value, it needs a way
// to retrieve the full object using the stored value (which is a string). This "retriever" closure provides that retrieval mechanism.
+
+export const enum ControlType {
+ FormControl,
+ FormArray,
+ FormGroup
+}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/utils/form-utils.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/utils/form-utils.ts
index de14faf3c4..5592d7ba31 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/utils/form-utils.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/utils/form-utils.ts
@@ -1,4 +1,6 @@
-import { AbstractControl, FormGroup } from "@angular/forms";
+import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
+import AppServices from 'src/app/shared/service/appServices';
+import { ControlType, FieldMapping } from '../field-mapping/FieldMapping';
export class FormUtils {
static addControl(formGroup: FormGroup, name: string, control: AbstractControl) {
@@ -7,6 +9,50 @@ export class FormUtils {
// Clarity 5's status change logic, which triggers form validation to occur
// immediately after form controls are created. Setting emitEvent to false
// avoids the Clarity 'triggerAllFormControlValidation' method from being executed.
- formGroup.addControl(name, control, { emitEvent: false });
+ formGroup.addControl(name, control, {emitEvent: false});
+ }
+
+ static addDynamicControl(formGroup: FormGroup, initialValue: any, fieldMapping: FieldMapping): void {
+ const dynamicControlHandler =
+ (dynamicMapping: Record void>, defaultCase = ControlType.FormControl) =>
+ (controlType: ControlType) =>
+ (dynamicMapping[controlType] || dynamicMapping[defaultCase])();
+
+ const formArrayHandler: () => void = () => {
+ const formData: any[] = (initialValue && initialValue !== '' ? initialValue : null) ??
+ fieldMapping.defaultValue ?? [
+ fieldMapping.children.reduce((obj, item) => ((obj[item.name] = item.defaultValue), obj), {})
+ ];
+
+ const formArray: any[] = formData.map((obj) => {
+ const group: any = {};
+ for (const [key, value] of Object.entries(obj)) {
+ const childFieldMapping: FieldMapping = fieldMapping.children.find((child) => child.name === key);
+ const childValidators = AppServices.fieldMapUtilities.getValidatorArray(childFieldMapping);
+ group[key] = new FormControl(value, childValidators);
+ }
+ return new FormGroup(group);
+ });
+
+ formGroup.addControl(fieldMapping.name, new FormArray(formArray), {emitEvent: false});
+ };
+
+ const formControlHandler: () => void = () => {
+ const validators = AppServices.fieldMapUtilities.getValidatorArray(fieldMapping);
+ formGroup.addControl(fieldMapping.name, new FormControl(initialValue, validators), {emitEvent: false});
+ setTimeout(() => {
+ formGroup.controls[fieldMapping.name].setValue(initialValue);
+ });
+ };
+
+ const dynamicMappings: Record void> = {
+ [ControlType.FormArray]: formArrayHandler,
+ [ControlType.FormControl]: formControlHandler,
+ // TODO: Add FormGroup handler
+ [ControlType.FormGroup]: () => {
+ }
+ };
+
+ dynamicControlHandler(dynamicMappings)(fieldMapping.controlType);
}
}
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/validation/validation.service.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/validation/validation.service.ts
index 7911210303..9a6846c3c2 100755
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/validation/validation.service.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/validation/validation.service.ts
@@ -12,6 +12,7 @@ import isIp from 'is-ip';
*/
import * as validationMethods from './validation.methods';
import { SimpleValidator, ValidatorEnum } from '../constants/validation.constants';
+import { RxwebValidators } from "@rxweb/reactive-form-validators";
/**
* @class ValidationService
@@ -23,29 +24,35 @@ export class ValidationService {
constructor() {
this.simpleValidatorMap = new Map any>([
- [ SimpleValidator.IS_COMMA_SEPARATED_LIST, this.isCommaSeperatedList() ],
- [ SimpleValidator.IS_HTTP_OR_HTTPS, this.isHttpOrHttps() ],
- [ SimpleValidator.IS_NUMBER_POSITIVE, this.isNumberGreaterThanZero() ],
- [ SimpleValidator.IS_NUMERIC_ONLY, this.isNumericOnly() ],
- [ SimpleValidator.IS_STRING_WITHOUT_QUERY_PARAMS, this.isStringWithoutQueryParams() ],
- [ SimpleValidator.IS_STRING_WITHOUT_URL_FRAGMENT, this.isStringWithoutUrlFragment() ],
- [ SimpleValidator.IS_TRUE, this.isTrue() ],
- [ SimpleValidator.IS_VALID_CLUSTER_NAME, this.isValidClusterName() ],
- [ SimpleValidator.IS_VALID_FQDN, this.isValidFqdn() ],
- [ SimpleValidator.IS_VALID_FQDN_OR_IP, this.isValidIpOrFqdn() ],
- [ SimpleValidator.IS_VALID_FQDN_OR_IP_HTTPS, this.isValidIpOrFqdnWithHttpsProtocol() ],
- [ SimpleValidator.IS_VALID_FQDN_OR_IP_LIST, this.isCommaSeparatedIpsOrFqdn() ],
- [ SimpleValidator.IS_VALID_FQDN_OR_IPV6, this.isValidIpv6OrFqdn() ],
- [ SimpleValidator.IS_VALID_FQDN_OR_IPV6_HTTPS, this.isValidIpv6OrFqdnWithHttpsProtocol() ],
- [ SimpleValidator.IS_VALID_IP, this.isValidIp() ],
- [ SimpleValidator.IS_VALID_IP_LIST, this.isValidIps() ],
- [ SimpleValidator.IS_VALID_IP_NETWORK_SEGMENT, this.isValidIpNetworkSegment() ],
- [ SimpleValidator.IS_VALID_IPV6_NETWORK_SEGMENT, this.isValidIpv6NetworkSegment() ],
- [ SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION, this.isValidLabelOrAnnotation() ],
- [ SimpleValidator.IS_VALID_PORT, this.isValidPort() ],
- [ SimpleValidator.IS_VALID_RESOURCE_GROUP_NAME, this.isValidResourceGroupName() ],
- [ SimpleValidator.NO_WHITE_SPACE, this.noWhitespaceOnEnds() ],
- [ SimpleValidator.NO_TRAILING_SLASH, this.noTrailingSlash() ],
+ [SimpleValidator.IS_COMMA_SEPARATED_LIST, this.isCommaSeperatedList()],
+ [SimpleValidator.IS_HTTP_OR_HTTPS, this.isHttpOrHttps()],
+ [SimpleValidator.IS_NUMBER_POSITIVE, this.isNumberGreaterThanZero()],
+ [SimpleValidator.IS_NUMERIC_ONLY, this.isNumericOnly()],
+ [SimpleValidator.IS_STRING_WITHOUT_QUERY_PARAMS, this.isStringWithoutQueryParams()],
+ [SimpleValidator.IS_STRING_WITHOUT_URL_FRAGMENT, this.isStringWithoutUrlFragment()],
+ [SimpleValidator.IS_TRUE, this.isTrue()],
+ [SimpleValidator.IS_VALID_CLUSTER_NAME, this.isValidClusterName()],
+ [SimpleValidator.IS_VALID_FQDN, this.isValidFqdn()],
+ [SimpleValidator.IS_VALID_FQDN_OR_IP, this.isValidIpOrFqdn()],
+ [SimpleValidator.IS_VALID_FQDN_OR_IP_HTTPS, this.isValidIpOrFqdnWithHttpsProtocol()],
+ [SimpleValidator.IS_VALID_FQDN_OR_IP_LIST, this.isCommaSeparatedIpsOrFqdn()],
+ [SimpleValidator.IS_VALID_FQDN_OR_IPV6, this.isValidIpv6OrFqdn()],
+ [SimpleValidator.IS_VALID_FQDN_OR_IPV6_HTTPS, this.isValidIpv6OrFqdnWithHttpsProtocol()],
+ [SimpleValidator.IS_VALID_IP, this.isValidIp()],
+ [SimpleValidator.IS_VALID_IP_LIST, this.isValidIps()],
+ [SimpleValidator.IS_VALID_IP_NETWORK_SEGMENT, this.isValidIpNetworkSegment()],
+ [SimpleValidator.IS_VALID_IPV6_NETWORK_SEGMENT, this.isValidIpv6NetworkSegment()],
+ [SimpleValidator.IS_VALID_LABEL_OR_ANNOTATION, this.isValidLabelOrAnnotation()],
+ [SimpleValidator.IS_VALID_PORT, this.isValidPort()],
+ [SimpleValidator.IS_VALID_RESOURCE_GROUP_NAME, this.isValidResourceGroupName()],
+ [SimpleValidator.NO_WHITE_SPACE, this.noWhitespaceOnEnds()],
+ [SimpleValidator.NO_TRAILING_SLASH, this.noTrailingSlash()],
+ [SimpleValidator.RX_UNIQUE, RxwebValidators.unique()],
+ [
+ SimpleValidator.RX_REQUIRED_IF_VALUE,
+ RxwebValidators.required({conditionalExpression: (x, _) => !!x.value})
+ ],
+ [SimpleValidator.RX_REQUIRED_IF_KEY, RxwebValidators.required({conditionalExpression: (x, _) => !!x.key})]
]);
}
@@ -62,7 +69,7 @@ export class ValidationService {
const ctrlValue: string = control.value;
if (ctrlValue) {
return validationMethods.isValidIp(ctrlValue) ?
- null : { [ValidatorEnum.VALID_IP]: true };
+ null : {[ValidatorEnum.VALID_IP]: true};
}
return null;
}
@@ -113,7 +120,7 @@ export class ValidationService {
const ips: Array = ctrlValue.split(',');
return ips
.map(ipStr => validationMethods.isValidIp(ipStr))
- .reduce((a, b) => a && b, true) ? null : { [ValidatorEnum.VALID_IP]: true };
+ .reduce((a, b) => a && b, true) ? null : {[ValidatorEnum.VALID_IP]: true};
}
return null;
}
@@ -128,7 +135,7 @@ export class ValidationService {
const ctrlValue: string = control.value;
if (ctrlValue) {
return validationMethods.isValidFqdn(ctrlValue) ?
- null : { [ValidatorEnum.VALID_FQDN]: true };
+ null : {[ValidatorEnum.VALID_FQDN]: true};
}
return null;
}
@@ -156,7 +163,7 @@ export class ValidationService {
/**
* @method isValidIpOrFqdn validator to check if input is valid IP or FQDN
*/
- isValidIpv6OrFqdn(): any {
+ isValidIpv6OrFqdn(): any {
return (control: AbstractControl) => {
const ctrlValue: string = control.value;
if (ctrlValue) {
@@ -192,11 +199,11 @@ export class ValidationService {
}
}
- /**
+ /**
* @method isValidIpv6OrFqdnWithHttpsProtocol validator to check if input is valid IP or FQDN
* with protocol prefix
*/
- isValidIpv6OrFqdnWithHttpsProtocol(): any {
+ isValidIpv6OrFqdnWithHttpsProtocol(): any {
return (control: AbstractControl) => {
const ctrlValue: string = control.value;
if (ctrlValue) {
@@ -313,7 +320,7 @@ export class ValidationService {
};
}
return validationMethods.isValidClustername(ctrlValue) ?
- null : { [ValidatorEnum.VALID_CLUSTER_NAME]: true };
+ null : {[ValidatorEnum.VALID_CLUSTER_NAME]: true};
}
return null;
}
@@ -340,7 +347,7 @@ export class ValidationService {
}
}
return validationMethods.isValidLabelOrAnnotation(ctrlValue) ?
- null : { [ValidatorEnum.VALID_CLUSTER_NAME]: true };
+ null : {[ValidatorEnum.VALID_CLUSTER_NAME]: true};
}
return null;
}
@@ -531,7 +538,7 @@ export class ValidationService {
* @method isValidIpv6NetworkSegment
* xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/xxx
*/
- isValidIpv6NetworkSegment(): any {
+ isValidIpv6NetworkSegment(): any {
return (control: AbstractControl) => {
const ctrlValue: string = control.value;
if (ctrlValue) {
@@ -566,7 +573,7 @@ export class ValidationService {
const currentControlIp = control.value;
for (const ipAddr of otherControls) {
if (currentControlIp === ipAddr.value) {
- return { [ValidatorEnum.NETWORKING_IP_UNIQUE]: true };
+ return {[ValidatorEnum.NETWORKING_IP_UNIQUE]: true};
}
}
}
@@ -609,17 +616,17 @@ export class ValidationService {
if (validationMethods.isNumericOnly(ctrlValue)) {
return null;
}
- return { [ValidatorEnum.NUMERIC_ONLY]: true };
+ return {[ValidatorEnum.NUMERIC_ONLY]: true};
}
return null;
}
}
/**
- * @method commaSeparatedIpOrFqdn
- * @param {string} arg input string
- * @return {boolean}
- */
+ * @method commaSeparatedIpOrFqdn
+ * @param {string} arg input string
+ * @return {boolean}
+ */
commaSeparatedIpOrFqdn(arg: string): any {
const ips = arg.split(',');
return ips.map(ip => validationMethods.isValidIp(ip) || validationMethods.isValidFqdn(ip)).reduce((a, b) => a && b, true);
@@ -633,7 +640,7 @@ export class ValidationService {
const ctrlValue: string = control.value;
if (ctrlValue) {
if (!this.commaSeparatedIpOrFqdn(ctrlValue)) {
- return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
+ return {[ValidatorEnum.VALID_IP_OR_FQDN]: true};
}
}
return null;
@@ -650,7 +657,7 @@ export class ValidationService {
return null;
}
if (typeof ctrlValue !== 'number' || ctrlValue < 1) {
- return { [ValidatorEnum.GREATER_THAN_ZERO]: true };
+ return {[ValidatorEnum.GREATER_THAN_ZERO]: true};
}
return null;
}
@@ -662,7 +669,7 @@ export class ValidationService {
const currentAz = control.value;
for (const az of otherControls) {
if (currentAz === az.value) {
- return { [ValidatorEnum.AVAILABILITY_ZONE_UNIQUE]: true };
+ return {[ValidatorEnum.AVAILABILITY_ZONE_UNIQUE]: true};
}
}
}
@@ -673,7 +680,7 @@ export class ValidationService {
return (control: AbstractControl) => {
const ctrlValue: string = control.value;
if (ctrlValue && !XRegExp('^[\\pL-_.()\\w]+$').test(ctrlValue)) {
- return { [ValidatorEnum.VALID_RESOURCE_GROUP_NAME]: true };
+ return {[ValidatorEnum.VALID_RESOURCE_GROUP_NAME]: true};
}
return null;
}
@@ -683,7 +690,7 @@ export class ValidationService {
return (control: AbstractControl) => {
const ctrlValue: string = control.value;
if (ctrlValue && resourceGroups.find(x => x.name === ctrlValue) != null) {
- return { [ValidatorEnum.UNIQUE_RESOURCE_GROUP_NAME]: true };
+ return {[ValidatorEnum.UNIQUE_RESOURCE_GROUP_NAME]: true};
}
return null;
}
@@ -709,7 +716,7 @@ export class ValidationService {
return (control: AbstractControl) => {
const ctrlValue: string = control.value;
if (ctrlValue && !XRegExp('^https?:\/\/').test(ctrlValue)) {
- return { [ValidatorEnum.HTTP_OR_HTTPS]: true };
+ return {[ValidatorEnum.HTTP_OR_HTTPS]: true};
}
return null;
}
@@ -728,21 +735,22 @@ export class ValidationService {
const inputVal = ipCtrl.value;
if (inputVal) {
if (!validationMethods.isValidIp(inputVal) && !(validationMethods.isValidFqdn(inputVal))) {
- return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
+ return {[ValidatorEnum.VALID_IP_OR_FQDN]: true};
}
} else {
- return { [ValidatorEnum.REQUIRED]: true };
+ return {[ValidatorEnum.REQUIRED]: true};
}
if (control.value) {
if (!validationMethods.isNumericOnly(control.value)) {
- return { [ValidatorEnum.VALID_PORT]: true };
+ return {[ValidatorEnum.VALID_PORT]: true};
}
return null;
}
- return { [ValidatorEnum.REQUIRED]: true };
+ return {[ValidatorEnum.REQUIRED]: true};
}
}
+
/**
* @method isValidIpv6Ldap
* - non-empty
@@ -751,30 +759,31 @@ export class ValidationService {
*
* @param {AbstractControl} ipCtrl
*/
- isValidIpv6Ldap(ipCtrl: AbstractControl): any {
+ isValidIpv6Ldap(ipCtrl: AbstractControl): any {
return (control: AbstractControl) => {
const inputVal = ipCtrl.value;
if (inputVal) {
if (!isIp.v6(inputVal) && !(validationMethods.isValidFqdn(inputVal))) {
- return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
+ return {[ValidatorEnum.VALID_IP_OR_FQDN]: true};
}
} else {
- return { [ValidatorEnum.REQUIRED]: true };
+ return {[ValidatorEnum.REQUIRED]: true};
}
if (control.value) {
if (!validationMethods.isNumericOnly(control.value)) {
- return { [ValidatorEnum.VALID_PORT]: true };
+ return {[ValidatorEnum.VALID_PORT]: true};
}
return null;
}
- return { [ValidatorEnum.REQUIRED]: true };
+ return {[ValidatorEnum.REQUIRED]: true};
}
}
+
isValidNameInList(list: Array): any {
return (control: AbstractControl) => {
const ctrlValue: string = control.value;
if (list.indexOf(ctrlValue) === -1) {
- return { [ValidatorEnum.NOT_IN_DATALIST]: true };
+ return {[ValidatorEnum.NOT_IN_DATALIST]: true};
}
return null;
}
@@ -784,7 +793,7 @@ export class ValidationService {
return (control: AbstractControl) => {
const ctrlValue: boolean = control.value;
if (ctrlValue === true) {
- return { [ValidatorEnum.TRUE]: true };
+ return {[ValidatorEnum.TRUE]: true};
}
return null;
}
@@ -795,7 +804,7 @@ export class ValidationService {
const ctrlValue: string = control.value;
for (const key of keys) {
if (ctrlValue !== '' && ctrlValue === formGroup.value[key] && key !== name) {
- return { [ValidatorEnum.LABEL_UNIQUE]: true}
+ return {[ValidatorEnum.LABEL_UNIQUE]: true}
}
}
return null;
diff --git a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/wizard-base/wizard-base.ts b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/wizard-base/wizard-base.ts
index eedcfedddc..ef6e1fa640 100644
--- a/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/wizard-base/wizard-base.ts
+++ b/pkg/v1/tkg/web/src/app/views/landing/wizard/shared/wizard-base/wizard-base.ts
@@ -17,8 +17,11 @@ import { EditionData } from '../../../../../shared/service/branding.service';
import { FieldMapping } from '../field-mapping/FieldMapping';
import { FormDataForHTML, FormUtility } from '../components/steps/form-utility';
import { IdentityField } from '../components/steps/identity-step/identity-step.fieldmapping';
-import { LoadBalancerField } from '../components/steps/load-balancer/load-balancer-step.fieldmapping';
-import { MetadataField } from '../components/steps/metadata-step/metadata-step.fieldmapping';
+import {
+ LoadBalancerField,
+ LoadBalancerStepMapping
+} from '../components/steps/load-balancer/load-balancer-step.fieldmapping';
+import { MetadataField, MetadataStepMapping } from '../components/steps/metadata-step/metadata-step.fieldmapping';
import { MetadataStepComponent } from '../components/steps/metadata-step/metadata-step.component';
import { NetworkField } from '../components/steps/network-step/network-step.fieldmapping';
import { OsImageField } from '../components/steps/os-image-step/os-image-step.fieldmapping';
@@ -51,6 +54,16 @@ export interface StepRegistrantData {
eventFileImportError: TanzuEventType, // the event the wizard broadcasts when an error occurs during file import
}
+export interface Params {
+ key: (obj: T) => string,
+ value: (obj: T) => string
+}
+
+export interface Label {
+ key: string,
+ value: string
+}
+
@Directive()
export abstract class WizardBaseDirective extends BasicSubscriber implements WizardStepRegistrar, OnInit {
APP_ROUTES: Routes = APP_ROUTES;
@@ -88,12 +101,16 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
// supplyFileImportedEvent() allows the child class to give this class the event to broadcast on successful file import
protected abstract supplyFileImportedEvent(): TanzuEventType;
+
// supplyFileImportErrorEvent() allows the child class to give this class the event to broadcast on file import error
protected abstract supplyFileImportErrorEvent(): TanzuEventType;
+
// supplyStepData() allows the child class gives this class the data for the steps.
protected abstract supplyStepData(): FormDataForHTML[];
+
// supplyWizardName() allows the child class gives this class the wizard name; this is used to identify which wizard a step belongs to
protected abstract supplyWizardName(): string;
+
// supplyDisplayOrder() allows the child class to specify the order (and which steps) get displayed (on confirmation page).
// By default, we take the order from the stepData (so stepData should be set before invoking this method)
protected supplyDisplayOrder(): string[] {
@@ -124,19 +141,21 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
// set step description (if it's a step description for this wizard)
AppServices.messenger.subscribe(TanzuEventType.STEP_DESCRIPTION_CHANGE, data => {
- const stepDescriptionPayload = data.payload as StepDescriptionChangePayload;
- if (this.supplyWizardName() === stepDescriptionPayload.wizard) {
- // we use setTimeout to avoid a possible ExpressionChangedAfterItHasBeenCheckedError
- setTimeout(() => { this.stepDescription[stepDescriptionPayload.step] = stepDescriptionPayload.description; }, 0);
- }
- }, this.unsubscribe);
+ const stepDescriptionPayload = data.payload as StepDescriptionChangePayload;
+ if (this.supplyWizardName() === stepDescriptionPayload.wizard) {
+ // we use setTimeout to avoid a possible ExpressionChangedAfterItHasBeenCheckedError
+ setTimeout(() => {
+ this.stepDescription[stepDescriptionPayload.step] = stepDescriptionPayload.description;
+ }, 0);
+ }
+ }, this.unsubscribe);
// set branding and cluster type on branding change for base wizard components
AppServices.messenger.subscribe(TanzuEventType.BRANDING_CHANGED, data => {
- this.edition = data.payload.edition;
- this.clusterTypeDescriptor = data.payload.clusterTypeDescriptor;
- this.title = data.payload.branding.title;
- }, this.unsubscribe);
+ this.edition = data.payload.edition;
+ this.clusterTypeDescriptor = data.payload.clusterTypeDescriptor;
+ this.title = data.payload.branding.title;
+ }, this.unsubscribe);
setTimeout(() => this.broadcastStepStarted(this.firstStep), 0);
}
@@ -150,7 +169,7 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
/**
* Retrieve the config file from the backend and return as a string
*/
- abstract retrieveExportFile(): Observable;
+ abstract retrieveExportFile(): Observable;
/**
* Switch the mode between "Review Configuration" and "Edit Configuration"
@@ -190,7 +209,9 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
* @method method to trigger deployment
*/
abstract createRegionalCluster(params: any): Observable;
+
abstract getPayload(): any;
+
abstract setFromPayload(payload: any);
isOnFirstStep() {
@@ -320,6 +341,31 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
});
}
+ /**
+ * @param arrObj of type [ {key: 'a', value: '1}, {key : 'b, value:'2}]
+ * @param key To Identify the ObjectKey
+ * @param value To Identify the ObjectValue
+ * @return Object {'a': 1 , 'b': '1'}
+ */
+ arrayOfObjectsToObject(arrObj: T[], params: Params): { [key: string]: string; } {
+ return arrObj && arrObj instanceof Array
+ ? arrObj.reduce((obj, item) => ((obj[params.key(item)] = params.value(item)), obj), {})
+ : {};
+ }
+
+ /**
+ * @param obj of type {'a' : '1', 'b': '2'}
+ * @return Array of Objects [ {key: 'a', value:'1'}, {key:'b', value:'2'}]
+ */
+ objectToArrayOfObjects(obj: Record): Label[] {
+ let responseObject: Label[] = [];
+ for (const [key, value] of Object.entries(obj)) {
+ responseObject = [...responseObject, {key, value: value as string}
+ ]
+ }
+ return responseObject;
+ }
+
/**
* Converts ES6 map to stringifyable object
* @param strMap ES6 map that will be converted
@@ -414,11 +460,15 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
}
payload.ceipOptIn = this.getBooleanFieldValue(WizardForm.CEIP, CeipField.OPTIN);
- // TODO: for labels, we are reaching into storage to get the value, whereas all the other data come from fields
- // It would be better for ALL the fields to use a FieldMapping to retrieve the data
- const labelFieldMapping: FieldMapping = {name: MetadataField.CLUSTER_LABELS, isMap: true};
- let labelsMap = AppServices.userDataService.retrieveStoredValue(this.supplyWizardName(), WizardForm.METADATA, labelFieldMapping);
- payload.labels = this.strMapToObj(labelsMap);
+
+ const metaDataLabels = this.getFieldValue(WizardForm.METADATA, MetadataField.CLUSTER_LABELS);
+ payload.labels = this.arrayOfObjectsToObject<{ key: string, value: string }>(
+ metaDataLabels,
+ {
+ key: label => label.key,
+ value: label => label.value
+ }
+ );
payload.os = this.getFieldValue(WizardForm.OSIMAGE, OsImageField.IMAGE);
payload.annotations = {
@@ -469,9 +519,8 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
, payload.identityManagement);
}
- // TODO: for clusterLabels, we are reaching into storage to get the value, whereas all the other data come from fields
- // It would be better for ALL the fields to use a FieldMapping to retrieve the data
- labelsMap = AppServices.userDataService.retrieveStoredValue(this.supplyWizardName(), 'loadBalancerForm', labelFieldMapping);
+ const loadBalancerLabels = this.getFieldValue(WizardForm.LOADBALANCER, LoadBalancerField.CLUSTER_LABELS);
+
payload.aviConfig = {
'controller': this.getFieldValue(WizardForm.LOADBALANCER, LoadBalancerField.CONTROLLER_HOST),
'username': this.getFieldValue(WizardForm.LOADBALANCER, LoadBalancerField.USERNAME),
@@ -483,7 +532,13 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
'name': this.getFieldValue(WizardForm.LOADBALANCER, LoadBalancerField.NETWORK_NAME),
'cidr': this.getFieldValue(WizardForm.LOADBALANCER, LoadBalancerField.NETWORK_CIDR)
},
- 'labels': this.strMapToObj(labelsMap),
+ 'labels': this.arrayOfObjectsToObject<{ key: string, value: string }>(
+ loadBalancerLabels,
+ {
+ key: label => label.key,
+ value: label => label.value
+ }
+ )
}
return payload;
}
@@ -497,25 +552,34 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
step: stepName,
formGroup: this.form.controls[stepName] as FormGroup,
eventFileImported: this.supplyFileImportedEvent(),
- eventFileImportError: this.supplyFileImportErrorEvent(),
+ eventFileImportError: this.supplyFileImportErrorEvent()
}
stepComponent.setStepRegistrantData(stepRegistrantData);
}
+
//
// Methods that fulfill WizardStepRegistrar
// storeFieldString() is a convenience method to avoid lengthy code lines
storeFieldString(step, field, value: string, displayString?: string) {
- const identifier = { wizard: this.supplyWizardName(), step, field };
+ const identifier = {wizard: this.supplyWizardName(), step, field};
const display = displayString ? displayString : value;
- AppServices.userDataService.store(identifier, { value, display });
+ AppServices.userDataService.store(identifier, {value, display});
+ }
+
+ storeFieldArray(step, field, value: T[], display?: string, displayFunction?: (field) => string) {
+ const identifier = {wizard: this.supplyWizardName(), step, field};
+ AppServices.userDataService.store(identifier, {
+ value,
+ display: display ?? displayFunction(value) ?? value.join(', ')
+ });
}
storeFieldBoolean(step, field: string, booleanValue: boolean) {
- const identifier = { wizard: this.supplyWizardName(), step, field };
+ const identifier = {wizard: this.supplyWizardName(), step, field};
const display = booleanValue ? 'yes' : 'no';
const value = String(booleanValue);
- AppServices.userDataService.store(identifier, { value, display });
+ AppServices.userDataService.store(identifier, {value, display});
}
storeProxyFieldsFromPayload(payload: any) {
@@ -557,7 +621,7 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
* @param payload
*/
saveCommonFieldsFromPayload(payload: any) {
- if (payload.networking !== undefined ) {
+ if (payload.networking !== undefined) {
// Networking - general
this.storeFieldString(WizardForm.NETWORK, NetworkField.NETWORK_NAME, payload.networking.networkName);
this.storeFieldString(WizardForm.NETWORK, NetworkField.CLUSTER_SERVICE_CIDR, payload.networking.clusterServiceCIDR);
@@ -571,15 +635,14 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
// Other fields
this.storeFieldString(WizardForm.CEIP, CeipField.OPTIN, payload.ceipOptIn);
if (payload.labels !== undefined) {
- // we construct a label value that mimics how the meta-data step constructs the saved label value
- // when the user creates it label by label
- const labelArray: Array = [];
- Object.keys(payload.labels).forEach(key => {
- const value = payload.labels[key];
- labelArray[labelArray.length] = key + ":" + value;
- });
- const labelValueToSave = labelArray.join(', ');
- this.storeFieldString(WizardForm.METADATA, MetadataField.CLUSTER_LABELS, labelValueToSave);
+ const arrayOfObjects: Label[] = this.objectToArrayOfObjects(payload.labels);
+ const fieldMapping: FieldMapping = MetadataStepMapping.fieldMappings.find(field => field.name === MetadataField.CLUSTER_LABELS);
+ this.storeFieldArray(
+ WizardForm.METADATA,
+ MetadataField.CLUSTER_LABELS,
+ arrayOfObjects,
+ null,
+ fieldMapping.displayFunction);
}
if (payload.annotations !== undefined) {
@@ -643,7 +706,15 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
this.storeFieldString(WizardForm.LOADBALANCER, LoadBalancerField.CONTROLLER_CERT, payload.aviConfig.ca_cert);
this.storeFieldString(WizardForm.LOADBALANCER, LoadBalancerField.NETWORK_NAME, payload.aviConfig.network.name);
this.storeFieldString(WizardForm.LOADBALANCER, LoadBalancerField.NETWORK_CIDR, payload.aviConfig.network.cidr);
- this.storeMap(WizardForm.LOADBALANCER, LoadBalancerField.CLUSTER_LABELS, this.objToStrMap(payload.aviConfig.labels));
+ const arrayOfObjects: Label[] = this.objectToArrayOfObjects(payload.aviConfig.labels);
+ const fieldMapping: FieldMapping = LoadBalancerStepMapping.fieldMappings
+ .find(field => field.name === LoadBalancerField.CLUSTER_LABELS);
+ this.storeFieldArray(
+ WizardForm.LOADBALANCER,
+ LoadBalancerField.CLUSTER_LABELS,
+ arrayOfObjects,
+ null,
+ fieldMapping.displayFunction);
}
}
@@ -655,43 +726,62 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
// HTML convenience methods
//
- get registrar(): WizardStepRegistrar {
+ get registrar() {
return this;
}
get CeipForm(): FormDataForHTML {
- return { name: WizardForm.CEIP, title: 'CEIP Agreement', description: 'Join the CEIP program for TKG',
- i18n: { title: 'ceip agreement step title', description: 'ceip agreement step description' },
- clazz: SharedCeipStepComponent };
+ return {
+ name: WizardForm.CEIP, title: 'CEIP Agreement', description: 'Join the CEIP program for TKG',
+ i18n: {title: 'ceip agreement step title', description: 'ceip agreement step description'},
+ clazz: SharedCeipStepComponent
+ };
}
+
get IdentityForm(): FormDataForHTML {
- return { name: WizardForm.IDENTITY, title: 'Identity Management', description: SharedIdentityStepComponent.description,
- i18n: { title: 'identity step title', description: 'identity step description' },
- clazz: SharedIdentityStepComponent };
+ return {
+ name: WizardForm.IDENTITY,
+ title: 'Identity Management',
+ description: SharedIdentityStepComponent.description,
+ i18n: {title: 'identity step title', description: 'identity step description'},
+ clazz: SharedIdentityStepComponent
+ };
}
+
get MetadataForm(): FormDataForHTML {
- return { name: WizardForm.METADATA, title: 'Metadata',
+ return {
+ name: WizardForm.METADATA, title: 'Metadata',
description: 'Specify metadata for the ' + this.clusterTypeDescriptor + ' cluster',
- i18n: { title: 'metadata step name', description: 'metadata step description' },
- clazz: MetadataStepComponent };
+ i18n: {title: 'metadata step name', description: 'metadata step description'},
+ clazz: MetadataStepComponent
+ };
}
+
get NetworkForm(): FormDataForHTML {
- return { name: WizardForm.NETWORK, title: 'Kubernetes Network',
+ return {
+ name: WizardForm.NETWORK, title: 'Kubernetes Network',
description: SharedNetworkStepComponent.description,
- i18n: { title: 'Kubernetes network step name', description: 'Kubernetes network step description' },
- clazz: SharedNetworkStepComponent };
+ i18n: {title: 'Kubernetes network step name', description: 'Kubernetes network step description'},
+ clazz: SharedNetworkStepComponent
+ };
}
+
getOsImageForm(clazz: Type): FormDataForHTML {
- return { name: WizardForm.OSIMAGE, title: 'OS Image', description: 'Specify the OS Image',
- i18n: { title: 'OS Image step title', description: 'OS Image step description' },
- clazz: clazz };
+ return {
+ name: WizardForm.OSIMAGE, title: 'OS Image', description: 'Specify the OS Image',
+ i18n: {title: 'OS Image step title', description: 'OS Image step description'},
+ clazz: clazz
+ };
}
+
get wizardForm(): FormGroup {
return this.form;
}
+
get clusterTypeDescriptorTitleCase() {
return FormUtility.titleCase(this.clusterTypeDescriptor);
}
+
get wizardName(): string {
return this.supplyWizardName();
}
@@ -699,6 +789,7 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
get isDataOld(): boolean {
return AppServices.userDataService.isWizardDataOld(this.supplyWizardName());
}
+
//
// HTML convenience methods
@@ -718,33 +809,37 @@ export abstract class WizardBaseDirective extends BasicSubscriber implements Wiz
private broadcastStepComplete(stepCompletedName: string) {
const payload: StepCompletedPayload = {
wizard: this.supplyWizardName(),
- step: stepCompletedName,
+ step: stepCompletedName
}
- AppServices.messenger.publish( { type: TanzuEventType.STEP_COMPLETED, payload } );
+ AppServices.messenger.publish({type: TanzuEventType.STEP_COMPLETED, payload});
}
private broadcastStepStarted(stepStartedName: string) {
const payload: StepStartedPayload = {
wizard: this.supplyWizardName(),
- step: stepStartedName,
+ step: stepStartedName
}
- AppServices.messenger.publish( { type: TanzuEventType.STEP_STARTED, payload } );
+ AppServices.messenger.publish({type: TanzuEventType.STEP_STARTED, payload});
}
private defaultDisplayOrder(stepData: FormDataForHTML[]): string[] {
// reduce the array of stepData items into an array of step name strings, which will be in the same order
return stepData.reduce((accumulator, daStep) => {
- accumulator.push(daStep.name); return accumulator;
+ accumulator.push(daStep.name);
+ return accumulator;
}, []);
}
+
private storeWizardDisplayOrder(displayOrder: string[]) {
AppServices.userDataService.storeWizardDisplayOrder(this.supplyWizardName(), displayOrder);
}
+
private storeWizardStepDescriptions() {
AppServices.userDataService.storeWizardDescriptions(this.wizardName, this.stepDescription);
}
+
private storeWizardTitles() {
- const titles = this.stepData.reduce