});
it('should edit the second cluster', () => {
- multiCluster.edit(alias, editedAlias);
+ multiCluster.edit(alias, editedAlias, password);
multiCluster.existTableCell(editedAlias);
});
const pages = {
index: { url: '#/multi-cluster/overview', id: 'cd-multi-cluster' },
- 'manage-clusters': { url: '#/multi-cluster/manage-clusters', id: 'cd-multi-cluster-list' }
+ 'manage-clusters': { url: '#/multi-cluster/manage-clusters', id: 'cd-multi-cluster-list' },
+ connect: { url: '#/multi-cluster/manage-clusters/connect', id: 'cd-multi-cluster-form' }
};
const WAIT_TIMER = 1000;
auth(url: string, alias: string, username: string, password: string) {
cy.contains('button', 'Connect').click();
cy.get('cd-multi-cluster-form').should('exist');
- cy.get('cd-modal').within(() => {
- cy.get('input[name=remoteClusterUrl]').type(url);
- cy.get('input[name=clusterAlias]').type(alias);
- cy.get('input[name=username]').type(username);
- cy.get('input[name=password]').type(password);
- cy.get('cd-submit-button').click();
- });
+ this.navigateTo('connect');
+ cy.get('input[name=remoteClusterUrl]').type(url);
+ cy.get('input[name=clusterAlias]').type(alias);
+ cy.get('input[name=username]').type(username);
+ cy.get('#password').type(password);
+ cy.get('[data-testid=submitBtn]').click();
cy.wait(WAIT_TIMER);
}
disconnect(alias: string) {
- this.clickRowActionButton(alias, 'disconnect');
+ this.searchTable(alias);
+ cy.wait(3000);
+ cy.get(`[cdstablerow] [cdstabledata]:nth-child(${this.columnIndex.alias})`)
+ .filter((_i, el) => el.innerText.trim() === alias)
+ .parent('[cdstablerow]')
+ .find('[cdstabledata] [data-testid="table-action-btn"]')
+ .click({ force: true });
+ cy.get(`button.disconnect`).click({ force: true });
cy.get('cds-modal').within(() => {
cy.get('#confirmation_input').click({ force: true });
cy.get('cd-submit-button').click();
}
reconnect(alias: string, password: string) {
- this.clickRowActionButton(alias, 'reconnect');
- cy.get('cd-modal').within(() => {
- cy.get('input[name=password]').type(password);
- cy.get('cd-submit-button').click();
- });
+ this.searchTable(alias);
+ cy.wait(3000);
+ cy.get(`[cdstablerow] [cdstabledata]:nth-child(${this.columnIndex.alias})`)
+ .filter((_i, el) => el.innerText.trim() === alias)
+ .parent('[cdstablerow]')
+ .find('[cdstabledata] [data-testid="table-action-btn"]')
+ .click({ force: true });
+ cy.get(`button.reconnect`).click({ force: true });
+ cy.get('#password').type(password);
+ cy.get('[data-testid=submitBtn]').click();
cy.wait(WAIT_TIMER);
}
- edit(alias: string, newAlias: string) {
- this.clickRowActionButton(alias, 'edit');
- cy.get('cd-modal').within(() => {
- cy.get('input[name=clusterAlias]').clear().type(newAlias);
- cy.get('cd-submit-button').click();
- });
+ edit(alias: string, newAlias: string, password: string) {
+ this.searchTable(alias);
+ cy.wait(3000);
+ cy.get(`[cdstablerow] [cdstabledata]:nth-child(${this.columnIndex.alias})`)
+ .filter((_i, el) => el.innerText.trim() === alias)
+ .parent('[cdstablerow]')
+ .find('[cdstabledata] [data-testid="table-action-btn"]')
+ .click({ force: true });
+ cy.get(`button.edit`).click({ force: true });
+ cy.get('input[name=clusterAlias]').clear().type(newAlias);
+ cy.get('#password').type(password);
+ cy.get('[data-testid=submitBtn]').click();
cy.wait(WAIT_TIMER);
}
checkConnectionStatus(alias: string, expectedStatus = 'CONNECTED', shouldReload = true) {
- let aliasIndex = this.columnIndex.alias;
- let statusIndex = this.columnIndex.connection;
+ const aliasIndex = this.columnIndex.alias;
+ const statusIndex = this.columnIndex.connection;
if (shouldReload) {
cy.reload(true, { log: true, timeout: 5 * 1000 });
}
-
- this.getTableCell(aliasIndex, alias)
+ this.searchTable(alias);
+ cy.wait(3000);
+ cy.get(`[cdstablerow] [cdstabledata]:nth-child(${aliasIndex})`)
+ .filter((_i, el) => el.innerText.trim() === alias)
.parent()
.find(`[cdstabledata]:nth-child(${statusIndex}) cds-tag`)
.should(($ele) => {
- const status = $ele.toArray().map((v) => v.innerText);
+ const status = $ele.toArray().map((v) => v.innerText.trim());
expect(status).to.include(expectedStatus);
});
}
import { UpgradeProgressComponent } from './ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component';
import { MultiClusterComponent } from './ceph/cluster/multi-cluster/multi-cluster.component';
import { MultiClusterListComponent } from './ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component';
-import { MultiClusterDetailsComponent } from './ceph/cluster/multi-cluster/multi-cluster-details/multi-cluster-details.component';
import { SmbClusterFormComponent } from './ceph/smb/smb-cluster-form/smb-cluster-form.component';
import { SmbShareFormComponent } from './ceph/smb/smb-share-form/smb-share-form.component';
import { SmbJoinAuthFormComponent } from './ceph/smb/smb-join-auth-form/smb-join-auth-form.component';
path: `${URLVerbs.RECONNECT}/:name`,
component: MultiClusterFormComponent,
data: { breadcrumbs: ActionLabels.RECONNECT }
- },
- {
- path: 'performance-details',
- component: MultiClusterDetailsComponent
}
]
}
import Analytics from '@carbon/icons/es/analytics/16';
import CloseFilled from '@carbon/icons/es/close--filled/16';
import ProgressBarRoundIcon from '@carbon/icons/es/progress-bar--round/32';
+import Connect from '@carbon/icons/es/connect/32';
import {
NgbActiveModal,
NgbDatepickerModule,
})
export class ClusterModule {
constructor(private iconService: IconService) {
- this.iconService.registerAll([Analytics, CloseFilled, ProgressBarRoundIcon]);
+ this.iconService.registerAll([Analytics, CloseFilled, ProgressBarRoundIcon, Connect]);
}
}
-<div cdsCol [columnNumbers]="{md: 6}">
- <ng-container>
- <form name="remoteClusterForm"
- #frm="ngForm"
- [formGroup]="remoteClusterForm"
- novalidate>
-
- <div i18n="form title"
- class="form-header">{{ action | titlecase }} Cluster</div>
+<form name="remoteClusterForm"
+ #frm="ngForm"
+ [formGroup]="remoteClusterForm"
+ novalidate>
+ <div cdsGrid
+ [useCssGrid]="true"
+ [narrow]="true"
+ [fullWidth]="true">
+ <div cdsCol
+ [columnNumbers]="{sm: 4, md: 8}">
+ <div cdsRow
+ class="form-heading">
+ <h3>{{ action | titlecase }} Cluster</h3>
+ </div>
<cds-inline-notification
*ngIf="connectionVerified !== undefined && !connectionVerified && connectionMessage !== 'Connection refused'"
[subtitle]="connectionMessage">
</cds-inline-notification>
- <div class="form-item">
+ <div cdsRow
+ class="form-item">
<cds-text-label
cdRequiredField="Cluster API URL"
[invalid]="remoteClusterForm.controls.remoteClusterUrl.invalid && remoteClusterForm.controls.remoteClusterUrl.dirty"
for="remoteClusterUrl"
i18n>
Cluster API URL
-
- <input cdsText
- type="text"
- id="remoteClusterUrl"
- name="remoteClusterUrl"
- placeholder="https://localhost:4202"
- formControlName="remoteClusterUrl"
- [invalid]="remoteClusterForm.controls.remoteClusterUrl.invalid && remoteClusterForm.controls.remoteClusterUrl.dirty">
+ <input cdsText
+ type="text"
+ id="remoteClusterUrl"
+ name="remoteClusterUrl"
+ placeholder="https://localhost:4202"
+ formControlName="remoteClusterUrl"
+ [invalid]="remoteClusterForm.controls.remoteClusterUrl.invalid && remoteClusterForm.controls.remoteClusterUrl.dirty">
</cds-text-label>
<ng-template #urlErrorTpl>
- <span
- class="invalid-feedback"
- *ngIf="remoteClusterForm.showError('remoteClusterUrl', frm, 'required')"
- i18n
- >This field is required.</span
- >
- <span
- class="invalid-feedback"
- *ngIf="remoteClusterForm.showError('remoteClusterUrl', frm, 'invalidURL')"
- i18n
- >Please enter a valid URL.</span
- >
- <span
- class="invalid-feedback"
- *ngIf="remoteClusterForm.showError('remoteClusterUrl', frm, 'hubUrlCheck')"
- i18n
- >The hub cluster cannot be connected.</span
- >
+ <span class="invalid-feedback"
+ *ngIf="remoteClusterForm.showError('remoteClusterUrl', frm, 'required')"
+ i18n>This field is required.
+ </span>
+ <span class="invalid-feedback"
+ *ngIf="remoteClusterForm.showError('remoteClusterUrl', frm, 'invalidURL')"
+ i18n>Please enter a valid URL.
+ </span>
+ <span class="invalid-feedback"
+ *ngIf="remoteClusterForm.showError('remoteClusterUrl', frm, 'hubUrlCheck')"
+ i18n>The hub cluster cannot be connected.
+ </span>
</ng-template>
- <cd-help-text>
- Enter the Dashboard API URL. CLI: <b>{{ clusterApiUrlCmd }}</b>
- </cd-help-text>
+ <cd-help-text>
+ Enter the Dashboard API URL. CLI: <b>{{ clusterApiUrlCmd }}</b>
+ </cd-help-text>
</div>
- <div class="form-item">
+ <div cdsRow
+ class="form-item">
<cds-text-label
cdRequiredField="Alias Name"
[invalid]="remoteClusterForm.showError('clusterAlias', frm)"
placeholder="Name/Text to uniquely identify cluster"
formControlName="clusterAlias">
</cds-text-label>
-
<ng-template #aliasErrorTpl>
- <span *ngIf="remoteClusterForm.showError('clusterAlias', frm, 'required')" i18n>
+ <span *ngIf="remoteClusterForm.showError('clusterAlias', frm, 'required')"
+ i18n>
This field is required.
</span>
- <span *ngIf="remoteClusterForm.showError('clusterAlias', frm, 'uniqueName')" i18n>
+ <span *ngIf="remoteClusterForm.showError('clusterAlias', frm, 'uniqueName')"
+ i18n>
The chosen alias name is already in use.
</span>
</ng-template>
</div>
- <div class="form-item" *ngIf="action !== 'edit'">
+ <div cdsRow
+ class="form-item"
+ *ngIf="action !== 'edit'">
<cds-text-label
cdRequiredField="Username"
[invalid]="remoteClusterForm.showError('username', frm)"
name="username"
formControlName="username">
</cds-text-label>
-
<ng-template #usernameErrorTpl>
- <span *ngIf="remoteClusterForm.showError('username', frm, 'required')" i18n>
+ <span *ngIf="remoteClusterForm.showError('username', frm, 'required')"
+ i18n>
This field is required.
</span>
- <span *ngIf="remoteClusterForm.showError('username', frm, 'uniqueUrlandUser')" i18n>
+ <span *ngIf="remoteClusterForm.showError('username', frm, 'uniqueUrlandUser')"
+ i18n>
A cluster with the chosen user is already connected.
</span>
</ng-template>
</div>
- <div class="form-item" *ngIf="action !== 'edit'">
+ <div cdsRow
+ class="form-item"
+ *ngIf="action !== 'edit'">
<cds-password-label labelInputID="password"
label="Password..."
i18n>Password
- <input cdsPassword
- type="password"
- placeholder="Password..."
- id="password"
- autocomplete="new-password"
- formControlName="password"
- >
+ <input cdsPassword
+ type="password"
+ placeholder="Password..."
+ id="password"
+ name="password"
+ autocomplete="new-password"
+ formControlName="password">
</cds-password-label>
</div>
- <div class="form-item" *ngIf="action !== 'edit'">
- <cds-select formControlName="ttl"
- name="ttl"
- for="ttl"
- label="Login Expiration"
- id="ttl"
- i18n>
+ <div cdsRow
+ class="form-item"
+ *ngIf="action !== 'edit'">
+ <cds-select formControlName="ttl"
+ name="ttl"
+ for="ttl"
+ label="Login Expiration"
+ id="ttl"
+ i18n>
<option value="1">1 day</option>
<option value="7">1 week</option>
<option value="15">15 days</option>
</cds-select>
</div>
- <div class="form-item">
+ <div cdsRow
+ class="form-item">
<cds-checkbox
formControlName="ssl"
id="ssl"
</cds-checkbox>
</div>
- <div class="form-item" *ngIf="remoteClusterForm.get('ssl').value">
+ <div cdsRow
+ class="form-item"
+ *ngIf="remoteClusterForm.get('ssl').value">
<cds-text-label
label="Certificate"
[helperText]="sslHelpTpl"
[invalid]="remoteClusterForm.showError('ssl_cert', frm)"
[invalidText]="sslCertErrorTpl"
i18n>
- <div cdsStack="vertical" gap="3">
- <textarea cdsTextArea
- id="ssl_cert"
- rows="5"
- formControlName="ssl_cert"></textarea>
-
- <input type="file" (change)="fileUpload($event.target.files, 'ssl_cert')">
+ <div cdsStack="vertical"
+ gap="3">
+ <textarea cdsTextArea
+ id="ssl_cert"
+ rows="5"
+ formControlName="ssl_cert"></textarea>
+ <input type="file"
+ (change)="fileUpload($event.target.files, 'ssl_cert')">
</div>
</cds-text-label>
-
<ng-template #sslHelpTpl>
<span i18n>The SSL certificate in PEM format.</span>
</ng-template>
-
<ng-template #sslCertErrorTpl>
- <span *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'required')" i18n>
+ <span *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'required')"
+ i18n>
This field is required.
</span>
- <span *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'pattern')" i18n>
+ <span *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'pattern')"
+ i18n>
Invalid SSL certificate.
</span>
</ng-template>
[submitText]="(action | titlecase) + ' ' + 'Cluster'"
wrappingClass="text-right"></cd-form-button-panel>
- </form>
- </ng-container>
-</div>
+ </div>
+ </div>
+</form>
import { ToastrModule } from 'ngx-toastr';
import { NotificationService } from '~/app/shared/services/notification.service';
import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
-import { DatePipe } from '@angular/common';
-import { ReactiveFormsModule } from '@angular/forms';
+import { CommonModule, DatePipe } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from '~/app/shared/shared.module';
+import { CheckboxModule, GridModule, InputModule, SelectModule } from 'carbon-components-angular';
describe('MultiClusterFormComponent', () => {
let component: MultiClusterFormComponent;
await TestBed.configureTestingModule({
imports: [
SharedModule,
+ CommonModule,
+ FormsModule,
+ CheckboxModule,
+ GridModule,
ReactiveFormsModule,
+ InputModule,
+ SelectModule,
RouterTestingModule,
HttpClientTestingModule,
ToastrModule.forRoot()
</ng-container>
</nav>
<div [ngbNavOutlet]="nav"></div>
- <router-outlet></router-outlet>
</div>
</ng-container>
import { CdTableAction } from '~/app/shared/models/cd-table-action';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
-import { ModalService } from '~/app/shared/services/modal.service';
-import { MultiClusterFormComponent } from '../multi-cluster-form/multi-cluster-form.component';
import { TableComponent } from '~/app/shared/datatable/table/table.component';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { Permissions } from '~/app/shared/models/permissions';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
import { MultiCluster } from '~/app/shared/models/multi-cluster';
-import { ActivatedRoute, Router } from '@angular/router';
+import { Router } from '@angular/router';
import { CookiesService } from '~/app/shared/services/cookie.service';
import { Observable, Subscription } from 'rxjs';
import { SettingsService } from '~/app/shared/api/settings.service';
public actionLabels: ActionLabelsI18n,
private notificationService: NotificationService,
private authStorageService: AuthStorageService,
- private modalService: ModalService,
private cookieService: CookiesService,
private settingsService: SettingsService,
private cdsModalService: ModalCdsService,
- private route: ActivatedRoute,
private urlBuilder: URLBuilderService
) {
super();
}
}
- openRemoteClusterInfoModal(action: string) {
- const initialState = {
- clustersData: this.data,
- action: action,
- cluster: this.selection.first()
- };
- this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
- size: 'xl'
- });
- this.bsModalRef.componentInstance.submitAction.subscribe(() => {
- const currentRoute = this.router.url.split('?')[0];
- this.multiClusterService.refreshMultiCluster(currentRoute);
- this.checkClusterConnectionStatus();
- this.multiClusterService.isClusterAdded(true);
- });
- }
-
openDeleteClusterModal() {
const cluster = this.selection.first();
this.modalRef = this.cdsModalService.show(DeleteConfirmationModalComponent, {
setExpandedRow(expandedRow: any) {
super.setExpandedRow(expandedRow);
- this.router.navigate(['performance-details'], { relativeTo: this.route });
}
refresh() {
*ngIf="managedByConfig$ | async as managedByConfig">
<div class="row h-100 justify-content-center align-items-center">
<div class="blank-page">
- <i class="mx-auto d-block"
- [ngClass]="[icons.large, icons.wrench]">
- </i>
- <div class="mt-4 text-center"
- *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length === 0">
- <h3 class="fw-bold">Connect Cluster</h3>
- <h4 class="mt-3">Upgrade your current cluster to a multi-cluster setup effortlessly.
- Click on the "Connect Cluster" button to begin the process.</h4>
- </div>
- <div class="mt-4 text-center"
- *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length > 0">
- <h4 class="mt-3">This cluster is already managed by cluster -
- <a target="_blank"
- [href]="managedByConfig['MANAGED_BY_CLUSTERS'][0]['url']">
- {{ managedByConfig['MANAGED_BY_CLUSTERS'][0]['fsid'] }}
- <i class="fa fa-external-link"></i>
- </a>
- </h4>
- </div>
- <div class="mt-4"
- *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length === 0">
- <div class="text-center">
- <button class="btn btn-primary"
- (click)="openRemoteClusterInfoModal()">
- <i class="mx-auto"
- [ngClass]="icons.add">
- </i> Connect Cluster
- </button>
+ <svg cdsIcon="connect"
+ size="32"
+ class="mx-auto d-block"></svg>
+ <div class="mt-4 text-center"
+ *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length === 0">
+ <h3 class="fw-bold">Connect Cluster</h3>
+ <h4 class="mt-3">Upgrade your current cluster to a multi-cluster setup effortlessly.
+ Click on the "Connect Cluster" button to begin the process.</h4>
+ </div>
+ <div class="mt-4 text-center"
+ *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length > 0">
+ <h4 class="mt-3">This cluster is already managed by cluster -
+ <a target="_blank"
+ [href]="managedByConfig['MANAGED_BY_CLUSTERS'][0]['url']">
+ {{ managedByConfig['MANAGED_BY_CLUSTERS'][0]['fsid'] }}
+ <i class="fa fa-external-link"></i>
+ </a>
+ </h4>
+ </div>
+ <div class="mt-4"
+ *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length === 0">
+ <div class="text-center">
+ <button class="btn btn-primary"
+ (click)="openConnectClusterForm()">
+ <i class="mx-auto"
+ [ngClass]="icons.add">
+ </i> Connect Cluster
+ </button>
+ </div>
</div>
- </div>
</div>
</div>
</ng-container>
import { Observable, Subscription } from 'rxjs';
import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
import { Icons } from '~/app/shared/enum/icons.enum';
-import { ModalService } from '~/app/shared/services/modal.service';
-import { MultiClusterFormComponent } from './multi-cluster-form/multi-cluster-form.component';
import { PrometheusService } from '~/app/shared/api/prometheus.service';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
POOL_CAPACITY_UTILIZATION: 0,
POOL_IOPS_UTILIZATION: 0,
POOL_THROUGHPUT_UTILIZATION: 0,
- TOTAL_CAPACITY: 0,
+ TOTAL_CAPACITY: [],
USED_CAPACITY: 0,
HOSTS: 0,
POOLS: 0,
constructor(
private multiClusterService: MultiClusterService,
private settingsService: SettingsService,
- private modalService: ModalService,
private router: Router,
private prometheusService: PrometheusService,
private notificationService: NotificationService
}
}
- openRemoteClusterInfoModal() {
- const initialState = {
- action: 'connect'
- };
- this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
- size: 'lg'
- });
- this.bsModalRef.componentInstance.submitAction.subscribe(() => {
- this.loading = true;
- setTimeout(() => {
- const currentRoute = this.router.url.split('?')[0];
- this.multiClusterService.refreshMultiCluster(currentRoute);
- this.getPrometheusData(this.prometheusService.lastHourDateObject);
- }, this.PROMETHEUS_DELAY);
- });
+ openConnectClusterForm() {
+ this.router.navigate(['multi-cluster/manage-clusters/connect']);
}
getPrometheusData(selectedTime: any, selectedQueries?: string) {
const clusterUser = clusterDetails[USER];
if (
- clusterName === this.selectedCluster[USER] &&
+ clusterName === this.selectedCluster['name'] &&
clusterUser === this.selectedCluster[USER] &&
clusterDetails['cluster_alias'] !== 'local-cluster'
) {
idea = 'idea',
userAccessLocked = 'user--access-locked', // User access locked
chevronDown = 'chevron--down',
+ connect = 'connect',
/* Icons for special effect */
size16 = '16',
size20 = '20',