]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / forms / cd-validators.ts
1 import {
2 AbstractControl,
3 AsyncValidatorFn,
4 ValidationErrors,
5 ValidatorFn,
6 Validators
7 } from '@angular/forms';
8
9 import * as _ from 'lodash';
10 import { Observable, of as observableOf, timer as observableTimer } from 'rxjs';
11 import { map, switchMapTo, take } from 'rxjs/operators';
12
13 export function isEmptyInputValue(value: any): boolean {
14 return value == null || value.length === 0;
15 }
16
17 export type existsServiceFn = (value: any) => Observable<boolean>;
18
19 export class CdValidators {
20 /**
21 * Validator that performs email validation. In contrast to the Angular
22 * email validator an empty email will not be handled as invalid.
23 */
24 static email(control: AbstractControl): ValidationErrors | null {
25 // Exit immediately if value is empty.
26 if (isEmptyInputValue(control.value)) {
27 return null;
28 }
29 return Validators.email(control);
30 }
31
32 /**
33 * Validator function in order to validate IP addresses.
34 * @param {number} version determines the protocol version. It needs to be set to 4 for IPv4 and
35 * to 6 for IPv6 validation. For any other number (it's also the default case) it will return a
36 * function to validate the input string against IPv4 OR IPv6.
37 * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
38 * if the validation failed, otherwise `null`.
39 */
40 static ip(version: number = 0): ValidatorFn {
41 // prettier-ignore
42 const ipv4Rgx =
43 /^((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;
44 const ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
45
46 if (version === 4) {
47 return Validators.pattern(ipv4Rgx);
48 } else if (version === 6) {
49 return Validators.pattern(ipv6Rgx);
50 } else {
51 return Validators.pattern(new RegExp(ipv4Rgx.source + '|' + ipv6Rgx.source));
52 }
53 }
54
55 /**
56 * Validator function in order to validate numbers.
57 * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
58 * if the validation failed, otherwise `null`.
59 */
60 static number(allowsNegative: boolean = true): ValidatorFn {
61 if (allowsNegative) {
62 return Validators.pattern(/^-?[0-9]+$/i);
63 } else {
64 return Validators.pattern(/^[0-9]+$/i);
65 }
66 }
67
68 /**
69 * Validator function in order to validate decimal numbers.
70 * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
71 * if the validation failed, otherwise `null`.
72 */
73 static decimalNumber(allowsNegative: boolean = true): ValidatorFn {
74 if (allowsNegative) {
75 return Validators.pattern(/^-?[0-9]+(.[0-9]+)?$/i);
76 } else {
77 return Validators.pattern(/^[0-9]+(.[0-9]+)?$/i);
78 }
79 }
80
81 /**
82 * Validator that requires controls to fulfill the specified condition if
83 * the specified prerequisites matches. If the prerequisites are fulfilled,
84 * then the given function is executed and if it succeeds, the 'required'
85 * validation error will be returned, otherwise null.
86 * @param {Object} prerequisites An object containing the prerequisites.
87 * ### Example
88 * ```typescript
89 * {
90 * 'generate_key': true,
91 * 'username': 'Max Mustermann'
92 * }
93 * ```
94 * Only if all prerequisites are fulfilled, then the validation of the
95 * control will be triggered.
96 * @param {Function | undefined} condition The function to be executed when all
97 * prerequisites are fulfilled. If not set, then the {@link isEmptyInputValue}
98 * function will be used by default. The control's value is used as function
99 * argument. The function must return true to set the validation error.
100 * @return {ValidatorFn} Returns the validator function.
101 */
102 static requiredIf(prerequisites: Object, condition?: Function | undefined): ValidatorFn {
103 return (control: AbstractControl): ValidationErrors | null => {
104 // Check if all prerequisites matches.
105 if (
106 !Object.keys(prerequisites).every((key) => {
107 return control.parent && control.parent.get(key).value === prerequisites[key];
108 })
109 ) {
110 return null;
111 }
112 const success = _.isFunction(condition)
113 ? condition.call(condition, control.value)
114 : isEmptyInputValue(control.value);
115 return success ? { required: true } : null;
116 };
117 }
118
119 /**
120 * Custom validation by passing a name for the error and a function as error condition.
121 *
122 * @param {string} error
123 * @param {Function} condition - a truthy return value will trigger the error
124 * @returns {ValidatorFn}
125 */
126 static custom(error: string, condition: Function): ValidatorFn {
127 return (control: AbstractControl): { [key: string]: any } => {
128 const value = condition.call(this, control.value);
129 if (value) {
130 return { [error]: value };
131 }
132 return null;
133 };
134 }
135
136 /**
137 * Validate form control if condition is true with validators.
138 *
139 * @param {AbstractControl} formControl
140 * @param {Function} condition
141 * @param {ValidatorFn[]} conditionalValidators List of validators that should only be tested
142 * when the condition is met
143 * @param {ValidatorFn[]} permanentValidators List of validators that should always be tested
144 * @param {AbstractControl[]} watchControls List of controls that the condition depend on.
145 * Every time one of this controls value is updated, the validation will be triggered
146 */
147 static validateIf(
148 formControl: AbstractControl,
149 condition: Function,
150 conditionalValidators: ValidatorFn[],
151 permanentValidators: ValidatorFn[] = [],
152 watchControls: AbstractControl[] = []
153 ) {
154 conditionalValidators = conditionalValidators.concat(permanentValidators);
155
156 formControl.setValidators(
157 (
158 control: AbstractControl
159 ): {
160 [key: string]: any;
161 } => {
162 const value = condition.call(this);
163 if (value) {
164 return Validators.compose(conditionalValidators)(control);
165 }
166 if (permanentValidators.length > 0) {
167 return Validators.compose(permanentValidators)(control);
168 }
169 return null;
170 }
171 );
172
173 watchControls.forEach((control: AbstractControl) => {
174 control.valueChanges.subscribe(() => {
175 formControl.updateValueAndValidity({ emitEvent: false });
176 });
177 });
178 }
179
180 /**
181 * Validator that requires that both specified controls have the same value.
182 * Error will be added to the `path2` control.
183 * @param {string} path1 A dot-delimited string that define the path to the control.
184 * @param {string} path2 A dot-delimited string that define the path to the control.
185 * @return {ValidatorFn} Returns a validator function that always returns `null`.
186 * If the validation fails an error map with the `match` property will be set
187 * on the `path2` control.
188 */
189 static match(path1: string, path2: string): ValidatorFn {
190 return (control: AbstractControl): { [key: string]: any } => {
191 const ctrl1 = control.get(path1);
192 const ctrl2 = control.get(path2);
193 if (ctrl1.value !== ctrl2.value) {
194 ctrl2.setErrors({ match: true });
195 } else {
196 const hasError = ctrl2.hasError('match');
197 if (hasError) {
198 // Remove the 'match' error. If no more errors exists, then set
199 // the error value to 'null', otherwise the field is still marked
200 // as invalid.
201 const errors = ctrl2.errors;
202 _.unset(errors, 'match');
203 ctrl2.setErrors(_.isEmpty(_.keys(errors)) ? null : errors);
204 }
205 }
206 return null;
207 };
208 }
209
210 /**
211 * Asynchronous validator that requires the control's value to be unique.
212 * The validation is only executed after the specified delay. Every
213 * keystroke during this delay will restart the timer.
214 * @param serviceFn {existsServiceFn} The service function that is
215 * called to check whether the given value exists. It must return
216 * boolean 'true' if the given value exists, otherwise 'false'.
217 * @param serviceFnThis {any} The object to be used as the 'this' object
218 * when calling the serviceFn function. Defaults to null.
219 * @param {number|Date} dueTime The delay time to wait before the
220 * serviceFn call is executed. This is useful to prevent calls on
221 * every keystroke. Defaults to 500.
222 * @return {AsyncValidatorFn} Returns an asynchronous validator function
223 * that returns an error map with the `notUnique` property if the
224 * validation check succeeds, otherwise `null`.
225 */
226 static unique(
227 serviceFn: existsServiceFn,
228 serviceFnThis: any = null,
229 dueTime = 500
230 ): AsyncValidatorFn {
231 return (control: AbstractControl): Observable<ValidationErrors | null> => {
232 // Exit immediately if user has not interacted with the control yet
233 // or the control value is empty.
234 if (control.pristine || isEmptyInputValue(control.value)) {
235 return observableOf(null);
236 }
237 // Forgot previous requests if a new one arrives within the specified
238 // delay time.
239 return observableTimer(dueTime).pipe(
240 switchMapTo(serviceFn.call(serviceFnThis, control.value)),
241 map((resp: boolean) => {
242 if (!resp) {
243 return null;
244 } else {
245 return { notUnique: true };
246 }
247 }),
248 take(1)
249 );
250 };
251 }
252
253 /**
254 * Validator function for UUIDs.
255 * @param required - Defines if it is mandatory to fill in the UUID
256 * @return Validator function that returns an error object containing `invalidUuid` if the
257 * validation failed, `null` otherwise.
258 */
259 static uuid(required = false): ValidatorFn {
260 const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
261 return (control: AbstractControl): { [key: string]: any } | null => {
262 if (control.pristine && control.untouched) {
263 return null;
264 } else if (!required && !control.value) {
265 return null;
266 } else if (uuidRe.test(control.value)) {
267 return null;
268 }
269 return { invalidUuid: 'This is not a valid UUID' };
270 };
271 }
272 }