1 import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
2 import { Location } from '@angular/common';
3 import { UntypedFormControl, Validators } from '@angular/forms';
4 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
5 import { Observable, Subscription, forkJoin } from 'rxjs';
6 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
7 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
8 import { WizardStepModel } from '~/app/shared/models/wizard-steps';
9 import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
10 import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
11 import { RgwDaemon } from '../models/rgw-daemon';
12 import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
13 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
14 import { Icons } from '~/app/shared/enum/icons.enum';
15 import { SelectOption } from '~/app/shared/components/select/select-option.model';
16 import _ from 'lodash';
17 import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
18 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
19 import { NotificationService } from '~/app/shared/services/notification.service';
20 import { ActivatedRoute } from '@angular/router';
21 import { map, switchMap } from 'rxjs/operators';
22 import { BaseModal, Step } from 'carbon-components-angular';
23 import { SummaryService } from '~/app/shared/services/summary.service';
24 import { ExecutingTask } from '~/app/shared/models/executing-task';
26 STEP_TITLES_EXISTING_REALM,
27 STEP_TITLES_MULTI_CLUSTER_CONFIGURED,
28 STEP_TITLES_SINGLE_CLUSTER
29 } from './multisite-wizard-steps.enum';
30 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
31 import { MultiCluster, MultiClusterConfig } from '~/app/shared/models/multi-cluster';
33 interface DaemonStats {
35 [key: string]: string;
39 interface EndpointInfo {
42 frontendConfig: string;
51 NewRealm = 'newRealm',
52 ExistingRealm = 'existingRealm'
56 selector: 'cd-rgw-multisite-wizard',
57 templateUrl: './rgw-multisite-wizard.component.html',
58 styleUrls: ['./rgw-multisite-wizard.component.scss']
60 export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
61 multisiteSetupForm: CdFormGroup;
62 currentStep: WizardStepModel;
63 currentStepSub: Subscription;
64 permissions: Permissions;
65 stepTitles: Step[] = STEP_TITLES_MULTI_CLUSTER_CONFIGURED.map((title) => ({
68 stepsToSkip: { [steps: string]: boolean } = {};
69 daemons: RgwDaemon[] = [];
71 clusterDetailsArray: MultiCluster[] = [];
72 isMultiClusterConfigured = false;
73 exportTokenForm: CdFormGroup;
78 rgwEndpoints: { value: any[]; options: any[]; messages: any };
79 executingTask: ExecutingTask;
80 setupCompleted = false;
81 showConfigType = false;
82 realmList: string[] = [];
83 realmsInfo: { realm: string; token: string }[];
86 private wizardStepsService: WizardStepsService,
87 public activeModal: NgbActiveModal,
88 public actionLabels: ActionLabelsI18n,
89 private rgwDaemonService: RgwDaemonService,
90 private multiClusterService: MultiClusterService,
91 private rgwMultisiteService: RgwMultisiteService,
92 private rgwRealmService: RgwRealmService,
93 public notificationService: NotificationService,
94 private route: ActivatedRoute,
95 private summaryService: SummaryService,
96 private location: Location,
97 private cdr: ChangeDetectorRef
100 this.pageURL = 'rgw/multisite/configuration';
101 this.currentStepSub = this.wizardStepsService
103 .subscribe((step: WizardStepModel) => {
104 this.currentStep = step;
106 this.currentStep.stepIndex = 0;
108 this.rgwEndpoints = {
111 messages: new SelectMessages({
112 empty: $localize`There are no endpoints.`,
113 filter: $localize`Select endpoints`
119 this.open = this.route.outlet === 'modal';
120 this.loadRGWEndpoints();
121 this.multiClusterService.getCluster().subscribe((clusters: MultiClusterConfig) => {
122 const currentUrl = clusters['current_url'];
123 this.clusterDetailsArray = Object.values(clusters['config'])
125 .filter((cluster) => cluster['url'] !== currentUrl);
126 this.isMultiClusterConfigured = this.clusterDetailsArray.length > 0;
127 this.stepTitles = (this.isMultiClusterConfigured
128 ? STEP_TITLES_MULTI_CLUSTER_CONFIGURED
129 : STEP_TITLES_SINGLE_CLUSTER
130 ).map((label, index) => ({
132 onClick: () => (this.currentStep.stepIndex = index)
134 this.wizardStepsService.setTotalSteps(this.stepTitles.length);
135 this.selectedCluster = this.isMultiClusterConfigured
136 ? this.clusterDetailsArray[0]['name']
140 this.summaryService.subscribe((summary) => {
141 this.executingTask = summary.executing_tasks.find((task) =>
142 task.name.includes('progress/Multisite-Setup')
146 this.stepTitles.forEach((step) => {
147 this.stepsToSkip[step.label] = false;
150 this.rgwRealmService.getRealmTokens().subscribe((data: { realm: string; token: string }[]) => {
151 const base64Matcher = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
152 this.realmsInfo = data.filter((realmInfo) => base64Matcher.test(realmInfo.token));
153 this.showConfigType = this.realmsInfo.length > 0;
154 if (this.showConfigType) {
155 this.multisiteSetupForm.get('selectedRealm')?.setValue(this.realmsInfo[0].realm);
156 this.cdr.detectChanges();
161 private loadRGWEndpoints(): void {
162 this.rgwDaemonService
165 switchMap((daemons: RgwDaemon[]) => {
166 this.daemons = daemons;
167 return this.fetchDaemonStats(daemons);
170 .subscribe((daemonStatsArray: EndpointInfo[]) => {
171 this.populateRGWEndpoints(daemonStatsArray);
175 private fetchDaemonStats(daemons: RgwDaemon[]): Observable<EndpointInfo[]> {
176 const observables = daemons.map((daemon) =>
177 this.rgwDaemonService.get(daemon.id).pipe(
178 map((daemonStats: DaemonStats) => ({
179 hostname: daemon.server_hostname,
181 frontendConfig: daemonStats?.rgw_metadata?.['frontend_config#0'] || ''
185 return forkJoin(observables);
188 private populateRGWEndpoints(statsArray: EndpointInfo[]): void {
189 this.rgwEndpoints.value = statsArray.map((stats: EndpointInfo) => {
190 const protocol = stats.frontendConfig.includes('ssl_port') ? Protocol.HTTPS : Protocol.HTTP;
191 return `${protocol}://${stats.hostname}:${stats.port}`;
193 this.rgwEndpoints.options = this.rgwEndpoints.value.map(
194 (endpoint) => new SelectOption(false, endpoint, '')
196 this.cdr.detectChanges();
200 this.multisiteSetupForm = new CdFormGroup({
201 realmName: new UntypedFormControl('default_realm', {
202 validators: [Validators.required]
204 zonegroupName: new UntypedFormControl('default_zonegroup', {
205 validators: [Validators.required]
207 zonegroup_endpoints: new UntypedFormControl(null, [Validators.required]),
208 zoneName: new UntypedFormControl('default_zone', {
209 validators: [Validators.required]
211 zone_endpoints: new UntypedFormControl(null, {
212 validators: [Validators.required]
214 username: new UntypedFormControl('default_system_user', {
215 validators: [Validators.required]
217 cluster: new UntypedFormControl(null, {
218 validators: [Validators.required]
220 replicationZoneName: new UntypedFormControl('new_replicated_zone', {
221 validators: [Validators.required]
223 configType: new UntypedFormControl(ConfigType.NewRealm, {}),
224 selectedRealm: new UntypedFormControl(null, {})
227 if (!this.isMultiClusterConfigured) {
228 this.exportTokenForm = new CdFormGroup({});
232 showSubmitButtonLabel() {
233 if (this.wizardStepsService.isLastStep()) {
234 if (!this.setupCompleted) {
235 if (this.isMultiClusterConfigured) {
236 return $localize`Configure Multi-Site`;
238 return $localize`Export Multi-Site token`;
241 return $localize`Close`;
244 return $localize`Next`;
248 showCancelButtonLabel() {
249 return !this.wizardStepsService.isFirstStep()
250 ? this.actionLabels.BACK
251 : this.actionLabels.CANCEL;
255 if (!this.wizardStepsService.isLastStep()) {
256 this.wizardStepsService.moveToNextStep();
258 if (this.setupCompleted) {
264 this.wizardStepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
265 this.currentStep = step;
266 if (this.currentStep.stepIndex === 2 && this.isMultiClusterConfigured) {
267 this.stepsToSkip['Select Cluster'] = false;
274 const values = this.multisiteSetupForm.getRawValue();
275 const realmName = values['realmName'];
276 const zonegroupName = values['zonegroupName'];
277 const zonegroupEndpoints = this.rgwEndpoints.value.join(',');
278 const zoneName = values['zoneName'];
279 const zoneEndpoints = this.rgwEndpoints.value.join(',');
280 const username = values['username'];
281 if (!this.isMultiClusterConfigured || this.stepsToSkip['Select Cluster']) {
282 this.rgwMultisiteService
283 .setUpMultisiteReplication(
291 .subscribe((data: object[]) => {
292 this.setupCompleted = true;
293 this.rgwMultisiteService.setRestartGatewayMessage(false);
294 this.loading = false;
296 this.showSuccessNotification();
299 const cluster = values['cluster'];
300 const replicationZoneName = values['replicationZoneName'];
301 let selectedRealmName = '';
302 if (this.multisiteSetupForm.get('configType').value === ConfigType.ExistingRealm) {
303 selectedRealmName = this.multisiteSetupForm.get('selectedRealm').value;
305 this.rgwMultisiteService
306 .setUpMultisiteReplication(
315 this.clusterDetailsArray,
320 this.setupCompleted = true;
321 this.rgwMultisiteService.setRestartGatewayMessage(false);
322 this.loading = false;
323 this.showSuccessNotification();
326 this.multisiteSetupForm.setErrors({ cdSubmitButton: true });
332 showSuccessNotification() {
333 this.notificationService.show(
334 NotificationType.success,
335 $localize`Multi-site setup completed successfully.`
340 if (!this.wizardStepsService.isFirstStep()) {
341 this.wizardStepsService.moveToPreviousStep();
343 this.location.back();
348 const stepTitle = this.stepTitles[this.currentStep.stepIndex];
349 this.stepsToSkip[stepTitle.label] = true;
354 this.location.back();
357 onConfigTypeChange() {
358 const configType = this.multisiteSetupForm.get('configType')?.value;
359 if (configType === ConfigType.ExistingRealm) {
360 this.stepTitles = STEP_TITLES_EXISTING_REALM.map((title) => ({
363 this.stepTitles.forEach((steps, index) => {
364 steps.onClick = () => (this.currentStep.stepIndex = index);
366 } else if (this.isMultiClusterConfigured) {
367 this.stepTitles = STEP_TITLES_MULTI_CLUSTER_CONFIGURED.map((title) => ({
371 this.stepTitles = STEP_TITLES_SINGLE_CLUSTER.map((title) => ({
375 this.wizardStepsService.setTotalSteps(this.stepTitles.length);