]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
4a6ed695cba44301924c5c64c2343cbdc1416d5f
[ceph-ci.git] /
1 import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
2 import { AbstractControl, FormControl, Validators } from '@angular/forms';
3 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
4 import _ from 'lodash';
5 import { Subscription } from 'rxjs';
6 import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
7 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
8 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
9 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
10 import { CdValidators } from '~/app/shared/forms/cd-validators';
11 import { MultiCluster } from '~/app/shared/models/multi-cluster';
12 import { NotificationService } from '~/app/shared/services/notification.service';
13
14 @Component({
15   selector: 'cd-multi-cluster-form',
16   templateUrl: './multi-cluster-form.component.html',
17   styleUrls: ['./multi-cluster-form.component.scss']
18 })
19 export class MultiClusterFormComponent implements OnInit, OnDestroy {
20   @Output()
21   submitAction = new EventEmitter();
22   readonly endpoints = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{2,5}\/?$/;
23   readonly ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
24   readonly ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
25   clusterApiUrlCmd = 'ceph mgr services';
26   prometheusApiUrlCmd = 'ceph config get mgr mgr/dashboard/PROMETHEUS_API_HOST';
27   crossOriginCmd = `ceph dashboard set-cross-origin-url ${window.location.origin}`;
28   remoteClusterForm: CdFormGroup;
29   showToken = false;
30   connectionVerified: boolean;
31   connectionMessage = '';
32   private subs = new Subscription();
33   showCrossOriginError = false;
34   action: string;
35   cluster: MultiCluster;
36   clustersData: MultiCluster[];
37   clusterAliasNames: string[];
38   clusterUrls: string[];
39   clusterUsers: string[];
40   clusterUrlUserMap: Map<string, string>;
41
42   constructor(
43     public activeModal: NgbActiveModal,
44     public actionLabels: ActionLabelsI18n,
45     public notificationService: NotificationService,
46     private multiClusterService: MultiClusterService
47   ) {
48     this.createForm();
49   }
50   ngOnInit(): void {
51     if (this.action === 'edit') {
52       this.remoteClusterForm.get('remoteClusterUrl').setValue(this.cluster.url);
53       this.remoteClusterForm.get('remoteClusterUrl').disable();
54       this.remoteClusterForm.get('clusterAlias').setValue(this.cluster.cluster_alias);
55       this.remoteClusterForm.get('ssl').setValue(this.cluster.ssl_verify);
56       this.remoteClusterForm.get('ssl_cert').setValue(this.cluster.ssl_certificate);
57     }
58     if (this.action === 'reconnect') {
59       this.remoteClusterForm.get('remoteClusterUrl').setValue(this.cluster.url);
60       this.remoteClusterForm.get('remoteClusterUrl').disable();
61       this.remoteClusterForm.get('clusterAlias').setValue(this.cluster.cluster_alias);
62       this.remoteClusterForm.get('clusterAlias').disable();
63       this.remoteClusterForm.get('username').setValue(this.cluster.user);
64       this.remoteClusterForm.get('username').disable();
65       this.remoteClusterForm.get('clusterFsid').setValue(this.cluster.name);
66       this.remoteClusterForm.get('clusterFsid').disable();
67       this.remoteClusterForm.get('ssl').setValue(this.cluster.ssl_verify);
68       this.remoteClusterForm.get('ssl_cert').setValue(this.cluster.ssl_certificate);
69     }
70     [this.clusterAliasNames, this.clusterUrls, this.clusterUsers] = [
71       'cluster_alias',
72       'url',
73       'user'
74     ].map((prop) => this.clustersData?.map((cluster) => cluster[prop]));
75   }
76
77   createForm() {
78     this.remoteClusterForm = new CdFormGroup({
79       showToken: new FormControl(false),
80       username: new FormControl('', [
81         CdValidators.custom('uniqueUrlandUser', (username: string) => {
82           let remoteClusterUrl = '';
83           if (
84             this.remoteClusterForm &&
85             this.remoteClusterForm.getValue('remoteClusterUrl') &&
86             this.remoteClusterForm.getValue('remoteClusterUrl').endsWith('/')
87           ) {
88             remoteClusterUrl = this.remoteClusterForm.getValue('remoteClusterUrl').slice(0, -1);
89           } else if (this.remoteClusterForm) {
90             remoteClusterUrl = this.remoteClusterForm.getValue('remoteClusterUrl');
91           }
92           return (
93             this.remoteClusterForm &&
94             this.clusterUrls?.includes(remoteClusterUrl) &&
95             this.clusterUsers?.includes(username)
96           );
97         })
98       ]),
99       clusterFsid: new FormControl('', [
100         CdValidators.requiredIf({
101           showToken: true
102         })
103       ]),
104       prometheusApiUrl: new FormControl('', [
105         CdValidators.requiredIf({
106           showToken: true
107         })
108       ]),
109       password: new FormControl('', []),
110       remoteClusterUrl: new FormControl(null, {
111         validators: [
112           CdValidators.custom('endpoint', (value: string) => {
113             if (_.isEmpty(value)) {
114               return false;
115             } else {
116               return (
117                 !this.endpoints.test(value) &&
118                 !this.ipv4Rgx.test(value) &&
119                 !this.ipv6Rgx.test(value)
120               );
121             }
122           }),
123           Validators.required
124         ]
125       }),
126       apiToken: new FormControl('', [
127         CdValidators.requiredIf({
128           showToken: true
129         })
130       ]),
131       clusterAlias: new FormControl(null, {
132         validators: [
133           Validators.required,
134           CdValidators.custom('uniqueName', (clusterAlias: string) => {
135             return (
136               (this.action === 'connect' || this.action === 'edit') &&
137               this.clusterAliasNames &&
138               this.clusterAliasNames.indexOf(clusterAlias) !== -1
139             );
140           })
141         ]
142       }),
143       ssl: new FormControl(false),
144       ttl: new FormControl(15),
145       ssl_cert: new FormControl('', {
146         validators: [
147           CdValidators.requiredIf({
148             ssl: true
149           })
150         ]
151       })
152     });
153   }
154
155   ngOnDestroy() {
156     this.subs.unsubscribe();
157   }
158
159   handleError(error: any): void {
160     if (error.error.code === 'connection_refused') {
161       this.connectionVerified = false;
162       this.showCrossOriginError = true;
163       this.connectionMessage = error.error.detail;
164       this.crossOriginCmd = `ceph config set mgr mgr/dashboard/cross_origin_url ${window.location.origin} `;
165     } else {
166       this.connectionVerified = false;
167       this.connectionMessage = error.error.detail;
168     }
169     this.remoteClusterForm.setErrors({ cdSubmitButton: true });
170     this.notificationService.show(
171       NotificationType.error,
172       $localize`Connection to the cluster failed`
173     );
174   }
175
176   handleSuccess(message?: string): void {
177     this.notificationService.show(NotificationType.success, message);
178     this.submitAction.emit();
179     this.activeModal.close();
180   }
181
182   convertToHours(value: number): number {
183     return value * 24; // Convert days to hours
184   }
185
186   onSubmit() {
187     const url = this.remoteClusterForm.getValue('remoteClusterUrl');
188     const updatedUrl = url.endsWith('/') ? url.slice(0, -1) : url;
189     const clusterAlias = this.remoteClusterForm.getValue('clusterAlias');
190     const username = this.remoteClusterForm.getValue('username');
191     const password = this.remoteClusterForm.getValue('password');
192     const token = this.remoteClusterForm.getValue('apiToken');
193     const clusterFsid = this.remoteClusterForm.getValue('clusterFsid');
194     const prometheusApiUrl = this.remoteClusterForm.getValue('prometheusApiUrl');
195     const ssl = this.remoteClusterForm.getValue('ssl');
196     const ttl = this.convertToHours(this.remoteClusterForm.getValue('ttl'));
197     const ssl_certificate = this.remoteClusterForm.getValue('ssl_cert')?.trim();
198
199     const commonSubscribtion = {
200       error: (error: any) => this.handleError(error),
201       next: (response: any) => {
202         if (response === true) {
203           this.handleSuccess($localize`Cluster connected successfully`);
204         }
205       }
206     };
207
208     switch (this.action) {
209       case 'edit':
210         this.subs.add(
211           this.multiClusterService
212             .editCluster(this.cluster.url, clusterAlias, this.cluster.user)
213             .subscribe({
214               ...commonSubscribtion,
215               complete: () => this.handleSuccess($localize`Cluster updated successfully`)
216             })
217         );
218         break;
219       case 'reconnect':
220         this.subs.add(
221           this.multiClusterService
222             .reConnectCluster(updatedUrl, username, password, token, ssl, ssl_certificate, ttl)
223             .subscribe(commonSubscribtion)
224         );
225         break;
226       case 'connect':
227         this.subs.add(
228           this.multiClusterService
229             .addCluster(
230               updatedUrl,
231               clusterAlias,
232               username,
233               password,
234               token,
235               window.location.origin,
236               clusterFsid,
237               prometheusApiUrl,
238               ssl,
239               ssl_certificate,
240               ttl
241             )
242             .subscribe(commonSubscribtion)
243         );
244         break;
245       default:
246         break;
247     }
248   }
249
250   toggleToken() {
251     this.showToken = !this.showToken;
252   }
253
254   fileUpload(files: FileList, controlName: string) {
255     const file: File = files[0];
256     const reader = new FileReader();
257     reader.addEventListener('load', (event: ProgressEvent<FileReader>) => {
258       const control: AbstractControl = this.remoteClusterForm.get(controlName);
259       control.setValue(event.target.result);
260       control.markAsDirty();
261       control.markAsTouched();
262       control.updateValueAndValidity();
263     });
264     reader.readAsText(file, 'utf8');
265   }
266 }