Advanced Cluster Management Enhancements
- Chnaged cluster page design from switch to single page
- Added bootstrap option in creation and registration
- Added horizontal scaling, vertical scaling and upgrade in
cluster action
- In cluster page, for list combined two apis to show list done
temporary fix from UI
Change-Id: Ie4d30f5db28025ec92398cc60fc50ab7030f8f9b
Signed-off-by: SANDHYA.JS <sandhya.j@tataelxsi.co.in>
diff --git a/src/app/k8s/k8scluster/K8sClusterComponent.html b/src/app/k8s/k8scluster/K8sClusterComponent.html
index 0bb251e..3c01f4f 100644
--- a/src/app/k8s/k8scluster/K8sClusterComponent.html
+++ b/src/app/k8s/k8scluster/K8sClusterComponent.html
@@ -16,29 +16,15 @@
Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
-->
<div class="d-flex flex-row justify-content-between">
- <div class="d-flex align-items-center header-style">
- <div class="switches-container justify-content-start">
- <input type="radio" id="switchRegister" name="switchPlan" (change)="onChangeEvent($event.target.value)"
- value="Registered" checked="checked" />
- <input type="radio" id="switchManage" name="switchPlan" (change)="onChangeEvent($event.target.value)"
- value="Managed" />
- <label for="switchRegister">{{'PAGE.K8S.REGISTERED' | translate}}</label>
- <label for="switchManage">{{'PAGE.K8S.MANAGED' | translate}}</label>
- <div class="switch-wrapper">
- <div class="switch">
- <div>{{'PAGE.K8S.REGISTERED' | translate}}</div>
- <div>{{'PAGE.K8S.MANAGED' | translate}}</div>
- </div>
- </div>
- </div>
+ <div class="d-flex align-items-center header-style"> {{'PAGE.K8S.MENUK8SCLUSTER' | translate}}
</div>
<span class="button justify-content-end">
- <button *ngIf="isCluster === 'Registered'" class="btn btn-primary me-2" type="button" placement="top"
- container="body" ngbTooltip="{{'PAGE.K8S.REGISTERCLUSTER' | translate}}" (click)="addK8sCluster()">
+ <button class="btn btn-primary me-2" type="button" placement="top" container="body"
+ ngbTooltip="{{'PAGE.K8S.REGISTERCLUSTER' | translate}}" (click)="addK8sCluster('Register')">
<i class="fas fa-plus-circle" aria-hidden="true"></i> {{'PAGE.K8S.REGISTERCLUSTER' | translate}}
</button>
- <button *ngIf="isCluster === 'Managed'" class="btn btn-primary me-2" type="button" placement="top" container="body"
- ngbTooltip="{{'Create Cluster' | translate}}" (click)="addK8sCluster()">
+ <button class="btn btn-primary me-2" type="button" placement="top" container="body"
+ ngbTooltip="{{'PAGE.K8S.CREATECLUSTER' | translate}}" (click)="addK8sCluster('Manage')">
<i class="fas fa-plus-circle" aria-hidden="true"></i> {{'PAGE.K8S.CREATECLUSTER' | translate}}
</button>
</span>
@@ -46,11 +32,14 @@
<div class="mt-2 mb-2 list-utilites-actions">
<div class="col-auto me-auto">
<nav class="custom-items-config">
+ <span><i class="fas fa-cloud-upload-alt text-info"></i>{{clusterModeFirstStep}}</span>
+ <span><i class="fas fa-clipboard-check text-info"></i>{{clusterModeSecondStep}}</span>
<span><i class="fas fa-clock text-success"></i>{{operationalStateFirstStep}}</span>
<span><i class="fas fa-spinner text-warning"></i>{{operationalStateSecondStep}}</span>
<span><i class="fas fa-spinner text-danger"></i>{{operationalStateThirdStep}}</span>
<span><i class="fas fa-times-circle text-danger"></i>{{operationalStateFourthStep}}</span>
<span><i class="fas fa-times-circle text-warning"></i>{{operationalStateFifthStep}}</span>
+ <span><i class="fas fa-ban text-danger"></i>{{operationalStateSixthStep}}</span>
</nav>
</div>
<page-per-row class="me-2" (pagePerRow)="onChange($event)"></page-per-row>
diff --git a/src/app/k8s/k8scluster/K8sClusterComponent.scss b/src/app/k8s/k8scluster/K8sClusterComponent.scss
index d194553..8c2b739 100644
--- a/src/app/k8s/k8scluster/K8sClusterComponent.scss
+++ b/src/app/k8s/k8scluster/K8sClusterComponent.scss
@@ -15,88 +15,3 @@
Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
*/
-.switches-container {
- border-bottom: 1px solid #5f5f5f;
- background-color: #054c8c;
- border-radius: 2px;
- width: 230px;
- position: relative;
- display: block;
- padding: 0;
- line-height: 2em;
- border-radius: 3rem;
- margin-left: auto;
- margin-right: auto;
- height: 36px;
-}
-
-.switches-container input {
- visibility: hidden;
- position: absolute;
- top: 0;
-}
-
-.switches-container label {
- width: 50%;
- padding: 0;
- margin: 0;
- text-align: center;
- cursor: pointer;
- color: white;
- font-size: 0.875rem;
- font-weight: 400;
-}
-
-.switch-wrapper {
- position: absolute;
- top: 0;
- bottom: 0;
- width: 50%;
- padding: 0.15rem;
- z-index: 3;
- transition: transform 0.5s cubic-bezier(0.77, 0, 0.175, 1);
-}
-
-.switch {
- border-radius: 3rem;
- background: white;
- height: 100%;
- width: 110px;
- margin-top: 1px;
- margin-right: 1px;
-}
-
-.switch div {
- width: 100%;
- text-align: center;
- opacity: 0;
- display: block;
- color: #054c8c;
- font-size: 0.875rem;
- font-weight: 400;
- transition: opacity 0.2s cubic-bezier(0.77, 0, 0.175, 1) 0.125s;
- will-change: opacity;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-/* slide the switch box from right to left */
-.switches-container input:nth-of-type(1):checked ~ .switch-wrapper {
- transform: translateX(0%);
-}
-
-/* slide the switch box from left to right */
-.switches-container input:nth-of-type(2):checked ~ .switch-wrapper {
- transform: translateX(100%);
-}
-
-/* toggle the switch box labels - first checkbox:checked - show first switch div */
-.switches-container input:nth-of-type(1):checked ~ .switch-wrapper .switch div:nth-of-type(1) {
- opacity: 1;
-}
-
-/* toggle the switch box labels - second checkbox:checked - show second switch div */
-.switches-container input:nth-of-type(2):checked ~ .switch-wrapper .switch div:nth-of-type(2) {
- opacity: 1;
-}
diff --git a/src/app/k8s/k8scluster/K8sClusterComponent.ts b/src/app/k8s/k8scluster/K8sClusterComponent.ts
index ee98ed5..95c8e88 100644
--- a/src/app/k8s/k8scluster/K8sClusterComponent.ts
+++ b/src/app/k8s/k8scluster/K8sClusterComponent.ts
@@ -29,8 +29,9 @@
import { K8SCLUSTERDATA, K8SCLUSTERDATADISPLAY, K8SCREATEDATADISPLAY } from 'K8sModel';
import { LocalDataSource } from 'ng2-smart-table';
import { RestService } from 'RestService';
-import { Subscription } from 'rxjs';
-import { SharedService } from 'SharedService';
+import { forkJoin, Observable, Subscription } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { isNullOrUndefined, SharedService } from 'SharedService';
/**
* Creating Component
* @Component takes K8sClusterComponent.html as template url
@@ -81,6 +82,15 @@
/** operational State failed creation data @public */
public operationalStateFifthStep: string = CONFIGCONSTANT.k8OperationalStateFifthStep;
+ /** operational State failed creation data @public */
+ public operationalStateSixthStep: string = CONFIGCONSTANT.k8OperationalStateSixthStep;
+
+ /** Cluster Mode Managed data @public */
+ public clusterModeFirstStep: string = CONFIGCONSTANT.clusterModeFirstStep;
+
+ /** Cluster Mode Regsitered data @public */
+ public clusterModeSecondStep: string = CONFIGCONSTANT.clusterModeSecondStep;
+
/** cluster Type @public */
public isCluster: string = 'Registered';
@@ -128,6 +138,32 @@
name: { title: this.translateService.instant('NAME'), width: '20%', sortDirection: 'asc' },
identifier: { title: this.translateService.instant('IDENTIFIER'), width: '20%' },
version: { title: this.translateService.instant('K8VERSION'), width: '10%' },
+ clusterMode: {
+ title: this.translateService.instant('Cluster Mode'), width: '15%', type: 'html',
+ filter: {
+ type: 'list',
+ config: {
+ selectText: 'Select',
+ list: [
+ { value: this.clusterModeFirstStep, title: this.clusterModeFirstStep },
+ { value: this.clusterModeSecondStep, title: this.clusterModeSecondStep }
+ ]
+ }
+ },
+ valuePrepareFunction: (cell: K8SCLUSTERDATADISPLAY, row: K8SCLUSTERDATADISPLAY): string => {
+ if (row.clusterMode === this.clusterModeFirstStep) {
+ return `<span class="icon-label" title="${row.clusterMode}">
+ <i class="fas fa-cloud-upload-alt text-info"></i>
+ </span>`;
+ } else if (row.clusterMode === this.clusterModeSecondStep) {
+ return `<span class="icon-label" title="${row.clusterMode}">
+ <i class="fas fa-clipboard-check text-info"></i>
+ </span>`;
+ } else {
+ return `<span>${row.clusterMode}</span>`;
+ }
+ }
+ },
state: {
title: this.translateService.instant('STATE'), width: '15%', type: 'html',
filter: {
@@ -138,8 +174,9 @@
{ value: this.operationalStateFirstStep, title: this.operationalStateFirstStep },
{ value: this.operationalStateSecondStep, title: this.operationalStateSecondStep },
{ value: this.operationalStateThirdStep, title: this.operationalStateThirdStep },
- { value: this.operationalStateThirdStep, title: this.operationalStateFourthStep },
- { value: this.operationalStateThirdStep, title: this.operationalStateFifthStep }
+ { value: this.operationalStateFourthStep, title: this.operationalStateFourthStep },
+ { value: this.operationalStateFifthStep, title: this.operationalStateFifthStep },
+ { value: this.operationalStateSixthStep, title: this.operationalStateFifthStep }
]
}
},
@@ -164,13 +201,16 @@
return `<span class="icon-label" title="${row.state}">
<i class="fas fa-times-circle text-warning"></i>
</span>`;
+ } else if (row.state === this.operationalStateSixthStep) {
+ return `<span class="icon-label" title="${row.state}">
+ <i class="fas fa-ban text-danger"></i>
+ </span>`;
} else {
return `<span>${row.state}</span>`;
}
}
},
created: { title: this.translateService.instant('CREATED'), width: '15%' },
- modified: { title: this.translateService.instant('MODIFIED'), width: '15%' },
Actions: {
name: 'Action', width: '5%', filter: false, sort: false, title: this.translateService.instant('ACTIONS'), type: 'custom',
valuePrepareFunction: (cell: K8SCLUSTERDATADISPLAY, row: K8SCLUSTERDATADISPLAY): K8SCLUSTERDATADISPLAY => row,
@@ -202,11 +242,11 @@
}
/** Compose new K8s Cluster Accounts @public */
- public addK8sCluster(): void {
+ public addK8sCluster(type?: string): void {
// eslint-disable-next-line security/detect-non-literal-fs-filename
const modalRef: NgbModalRef = this.modalService.open(K8sAddClusterComponent, { backdrop: 'static' });
- if (this.isCluster === 'Registered') {
+ if (type === 'Register') {
modalRef.componentInstance.profileType = 'Register';
} else {
modalRef.componentInstance.profileType = 'Manage';
@@ -231,56 +271,83 @@
public generateK8sclusterData(k8sClusterdata: K8SCLUSTERDATA): K8SCLUSTERDATADISPLAY {
return {
name: k8sClusterdata.name,
- state: k8sClusterdata.state,
+ state: !isNullOrUndefined(k8sClusterdata.state) ? k8sClusterdata.state : 'N/A',
identifier: k8sClusterdata._id,
- operationalState: k8sClusterdata._admin.operationalState,
+ operationalState: !isNullOrUndefined(k8sClusterdata._admin.operationalState) ? k8sClusterdata._admin.operationalState : 'N/A',
version: k8sClusterdata.k8s_version,
created: this.sharedService.convertEpochTime(Number(k8sClusterdata._admin.created)),
modified: this.sharedService.convertEpochTime(Number(k8sClusterdata._admin.modified)),
- pageType: 'cluster'
+ pageType: 'cluster',
+ createdbyosm: !isNullOrUndefined(k8sClusterdata.created) ? (k8sClusterdata.created) : 'false',
+ bootstrap: !isNullOrUndefined(k8sClusterdata.bootstrap) ? (k8sClusterdata.bootstrap) : false,
+ key: (k8sClusterdata.key === 'registered') ? true : false,
+ clusterMode: (k8sClusterdata.created === 'true') ? 'MANAGED' : 'REGISTERED'
};
}
- /** Change event @public */
- public onChangeEvent(value: string): void {
- if (value === 'Managed') {
- this.isCluster = 'Managed';
- } else {
- this.isCluster = 'Registered';
- }
- sessionStorage.setItem('clusterType', value);
- this.generateColumns();
- this.generateSettings();
- this.generateData();
- }
-
/** Fetching the data from server to Load in the smarttable @protected */
protected generateData(): void {
this.isLoadingResults = true;
- if (this.isCluster === 'Registered') {
- this.clusterUrl = environment.K8SCREATECLUSTER_URL + '?created=false';
- } else {
- this.clusterUrl = environment.K8SCREATECLUSTER_URL + '?created=true';
- }
- this.restService.getResource(this.clusterUrl).subscribe((k8sClusterDatas: K8SCLUSTERDATA[]) => {
- this.k8sClusterData = [];
- k8sClusterDatas.forEach((k8sClusterdata: K8SCLUSTERDATA) => {
- const k8sClusterDataObj: K8SCLUSTERDATADISPLAY = this.generateK8sclusterData(k8sClusterdata);
- this.k8sClusterData.push(k8sClusterDataObj);
+ const tempURL: Observable<K8SCLUSTERDATA[]>[] = [];
+ const apiUrl1: string = environment.K8SCLUSTER_URL;
+ const apiUrl2: string = environment.K8SCREATECLUSTER_URL;
+ const response1$: Observable<K8SCLUSTERDATA[]> = this.restService.getResource(apiUrl1).pipe(
+ map((data: unknown) =>
+ data as K8SCLUSTERDATA[]
+ )
+ );
+ const response2$: Observable<K8SCLUSTERDATA[]> = this.restService.getResource(apiUrl2).pipe(
+ map((data: unknown) =>
+ data as K8SCLUSTERDATA[]
+ )
+ );
+ tempURL.push(response1$, response2$);
+ forkJoin(tempURL).subscribe(
+ ([response1, response2]) => {
+ if (!isNullOrUndefined(response1) && !isNullOrUndefined(response2)) {
+ const combinedResponse: K8SCLUSTERDATA[] = [...response1, ...response2];
+ const uniqueResponse = this.removeDuplicates(combinedResponse);
+ this.k8sClusterData = [];
+ uniqueResponse.forEach((clusterData: K8SCLUSTERDATA) => {
+ const k8sClusterDataObj: K8SCLUSTERDATADISPLAY = this.generateK8sclusterData(clusterData);
+ this.k8sClusterData.push(k8sClusterDataObj);
+ });
+ if (this.k8sClusterData.length > 0) {
+ this.checkDataClass = 'dataTables_present';
+ } else {
+ this.checkDataClass = 'dataTables_empty';
+ }
+ this.dataSource.load(this.k8sClusterData).then((data: boolean) => {
+ this.isLoadingResults = false;
+ }).catch(() => {
+ this.isLoadingResults = false;
+ });
+ }
+ }, (error: ERRORDATA) => {
+ this.restService.handleError(error, 'get');
+ this.isLoadingResults = false;
});
- if (this.k8sClusterData.length > 0) {
- this.checkDataClass = 'dataTables_present';
+ }
+
+ /** Removes duplicates based on 'name' and merges them if necessary */
+ private removeDuplicates(response: K8SCLUSTERDATA[]): K8SCLUSTERDATA[] {
+ const seenIds = new Set();
+ const uniqueResponse: K8SCLUSTERDATA[] = [];
+ response.forEach((obj) => {
+ if (!seenIds.has(obj.name)) {
+ uniqueResponse.push(obj);
+ seenIds.add(obj.name);
} else {
- this.checkDataClass = 'dataTables_empty';
+ const existingObj = uniqueResponse.find((o) => o.name === obj.name);
+ Object.keys(obj).forEach((key) => {
+ // eslint-disable-next-line security/detect-object-injection
+ if (existingObj[key] !== obj[key]) {
+ // eslint-disable-next-line security/detect-object-injection
+ existingObj[key] = obj[key];
+ }
+ });
}
- this.dataSource.load(this.k8sClusterData).then((data: boolean) => {
- this.isLoadingResults = false;
- }).catch(() => {
- this.isLoadingResults = false;
- });
- }, (error: ERRORDATA) => {
- this.restService.handleError(error, 'get');
- this.isLoadingResults = false;
});
+ return uniqueResponse;
}
}