]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
a9eb0aed0d89fbf84b2b5c343697f25de94f6f07
[ceph.git] /
1 import { Component } from '@angular/core';
2 import { Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4 import { I18n } from '@ngx-translate/i18n-polyfill';
5
6 import * as _ from 'lodash';
7 import { BsModalService } from 'ngx-bootstrap/modal';
8
9 import { PrometheusService } from '../../../../shared/api/prometheus.service';
10 import {
11   ActionLabelsI18n,
12   SucceededActionLabelsI18n
13 } from '../../../../shared/constants/app.constants';
14 import { Icons } from '../../../../shared/enum/icons.enum';
15 import { NotificationType } from '../../../../shared/enum/notification-type.enum';
16 import { CdFormBuilder } from '../../../../shared/forms/cd-form-builder';
17 import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
18 import { CdValidators } from '../../../../shared/forms/cd-validators';
19 import {
20   AlertmanagerSilence,
21   AlertmanagerSilenceMatcher,
22   AlertmanagerSilenceMatcherMatch
23 } from '../../../../shared/models/alertmanager-silence';
24 import { Permission } from '../../../../shared/models/permissions';
25 import { AlertmanagerAlert, PrometheusRule } from '../../../../shared/models/prometheus-alerts';
26 import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
27 import { NotificationService } from '../../../../shared/services/notification.service';
28 import { PrometheusSilenceMatcherService } from '../../../../shared/services/prometheus-silence-matcher.service';
29 import { TimeDiffService } from '../../../../shared/services/time-diff.service';
30 import { SilenceMatcherModalComponent } from '../silence-matcher-modal/silence-matcher-modal.component';
31
32 @Component({
33   selector: 'cd-prometheus-form',
34   templateUrl: './silence-form.component.html',
35   styleUrls: ['./silence-form.component.scss']
36 })
37 export class SilenceFormComponent {
38   icons = Icons;
39   permission: Permission;
40   form: CdFormGroup;
41   rules: PrometheusRule[];
42
43   // Date formatting rules can be found here: https://momentjs.com/docs/#/displaying/format/
44   bsConfig = { dateInputFormat: 'YYYY-MM-DDT HH:mm' };
45
46   recreate = false;
47   edit = false;
48   id: string;
49
50   action: string;
51   resource = this.i18n('silence');
52
53   matchers: AlertmanagerSilenceMatcher[] = [];
54   matcherMatch: AlertmanagerSilenceMatcherMatch = undefined;
55   matcherConfig = [
56     {
57       tooltip: this.i18n('Attribute name'),
58       icon: this.icons.paragraph,
59       attribute: 'name'
60     },
61     {
62       tooltip: this.i18n('Value'),
63       icon: this.icons.terminal,
64       attribute: 'value'
65     },
66     {
67       tooltip: this.i18n('Regular expression'),
68       icon: this.icons.magic,
69       attribute: 'isRegex'
70     }
71   ];
72
73   constructor(
74     private i18n: I18n,
75     private router: Router,
76     private authStorageService: AuthStorageService,
77     private formBuilder: CdFormBuilder,
78     private prometheusService: PrometheusService,
79     private notificationService: NotificationService,
80     private route: ActivatedRoute,
81     private timeDiff: TimeDiffService,
82     private bsModalService: BsModalService,
83     private silenceMatcher: PrometheusSilenceMatcherService,
84     private actionLabels: ActionLabelsI18n,
85     private succeededLabels: SucceededActionLabelsI18n
86   ) {
87     this.init();
88   }
89
90   private init() {
91     this.chooseMode();
92     this.authenticate();
93     this.createForm();
94     this.setupDates();
95     this.getData();
96   }
97
98   private chooseMode() {
99     this.edit = this.router.url.startsWith('/silence/edit');
100     this.recreate = this.router.url.startsWith('/silence/recreate');
101     if (this.edit) {
102       this.action = this.actionLabels.EDIT;
103     } else if (this.recreate) {
104       this.action = this.actionLabels.RECREATE;
105     } else {
106       this.action = this.actionLabels.CREATE;
107     }
108   }
109
110   private authenticate() {
111     this.permission = this.authStorageService.getPermissions().prometheus;
112     const allowed =
113       this.permission.read && (this.edit ? this.permission.update : this.permission.create);
114     if (!allowed) {
115       this.router.navigate(['/404']);
116     }
117   }
118
119   private createForm() {
120     this.form = this.formBuilder.group(
121       {
122         startsAt: [null, [Validators.required]],
123         duration: ['2h', [Validators.min(1)]],
124         endsAt: [null, [Validators.required]],
125         createdBy: [this.authStorageService.getUsername(), [Validators.required]],
126         comment: [null, [Validators.required]]
127       },
128       {
129         validators: CdValidators.custom('matcherRequired', () => this.matchers.length === 0)
130       }
131     );
132   }
133
134   private setupDates() {
135     const now = new Date();
136     now.setSeconds(0, 0); // Normalizes start date
137     this.form.silentSet('startsAt', now);
138     this.updateDate();
139     this.subscribeDateChanges();
140   }
141
142   private updateDate(updateStartDate?: boolean) {
143     const next = this.timeDiff.calculateDate(
144       this.form.getValue(updateStartDate ? 'endsAt' : 'startsAt'),
145       this.form.getValue('duration'),
146       updateStartDate
147     );
148     if (next) {
149       this.form.silentSet(updateStartDate ? 'startsAt' : 'endsAt', next);
150     }
151   }
152
153   private subscribeDateChanges() {
154     this.form.get('startsAt').valueChanges.subscribe(() => {
155       this.onDateChange();
156     });
157     this.form.get('duration').valueChanges.subscribe(() => {
158       this.updateDate();
159     });
160     this.form.get('endsAt').valueChanges.subscribe(() => {
161       this.onDateChange(true);
162     });
163   }
164
165   private onDateChange(updateStartDate?: boolean) {
166     if (this.form.getValue('startsAt') < this.form.getValue('endsAt')) {
167       this.updateDuration();
168     } else {
169       this.updateDate(updateStartDate);
170     }
171   }
172
173   private updateDuration() {
174     this.form.silentSet(
175       'duration',
176       this.timeDiff.calculateDuration(this.form.getValue('startsAt'), this.form.getValue('endsAt'))
177     );
178   }
179
180   private getData() {
181     this.getRules();
182     this.getModeSpecificData();
183   }
184
185   private getRules() {
186     this.prometheusService.ifPrometheusConfigured(
187       () =>
188         this.prometheusService.getRules().subscribe(
189           (rules) => (this.rules = rules),
190           () => {
191             this.prometheusService.disablePrometheusConfig();
192             this.rules = [];
193           }
194         ),
195       () => {
196         this.rules = [];
197         this.notificationService.show(
198           NotificationType.info,
199           this.i18n(
200             'Please add your Prometheus host to the dashboard configuration and refresh the page'
201           ),
202           undefined,
203           undefined,
204           'Prometheus'
205         );
206       }
207     );
208   }
209
210   private getModeSpecificData() {
211     this.route.params.subscribe((params: { id: string }) => {
212       if (!params.id) {
213         return;
214       }
215       if (this.edit || this.recreate) {
216         this.prometheusService.getSilences(params).subscribe((silences) => {
217           this.fillFormWithSilence(silences[0]);
218         });
219       } else {
220         this.prometheusService.getAlerts(params).subscribe((alerts) => {
221           this.fillFormByAlert(alerts[0]);
222         });
223       }
224     });
225   }
226
227   private fillFormWithSilence(silence: AlertmanagerSilence) {
228     this.id = silence.id;
229     if (this.edit) {
230       ['startsAt', 'endsAt'].forEach((attr) => this.form.silentSet(attr, new Date(silence[attr])));
231       this.updateDuration();
232     }
233     ['createdBy', 'comment'].forEach((attr) => this.form.silentSet(attr, silence[attr]));
234     this.matchers = silence.matchers;
235     this.validateMatchers();
236   }
237
238   private validateMatchers() {
239     if (!this.rules) {
240       window.setTimeout(() => this.validateMatchers(), 100);
241       return;
242     }
243     this.matcherMatch = this.silenceMatcher.multiMatch(this.matchers, this.rules);
244     this.form.markAsDirty();
245     this.form.updateValueAndValidity();
246   }
247
248   private fillFormByAlert(alert: AlertmanagerAlert) {
249     const labels = alert.labels;
250     Object.keys(labels).forEach((key) =>
251       this.setMatcher({
252         name: key,
253         value: labels[key],
254         isRegex: false
255       })
256     );
257   }
258
259   private setMatcher(matcher: AlertmanagerSilenceMatcher, index?: number) {
260     if (_.isNumber(index)) {
261       this.matchers[index] = matcher;
262     } else {
263       this.matchers.push(matcher);
264     }
265     this.validateMatchers();
266   }
267
268   showMatcherModal(index?: number) {
269     const modalRef = this.bsModalService.show(SilenceMatcherModalComponent);
270     const modal = modalRef.content as SilenceMatcherModalComponent;
271     modal.rules = this.rules;
272     if (_.isNumber(index)) {
273       modal.editMode = true;
274       modal.preFillControls(this.matchers[index]);
275     }
276     modalRef.content.submitAction.subscribe((matcher: AlertmanagerSilenceMatcher) => {
277       this.setMatcher(matcher, index);
278     });
279   }
280
281   deleteMatcher(index: number) {
282     this.matchers.splice(index, 1);
283     this.validateMatchers();
284   }
285
286   submit() {
287     if (this.form.invalid) {
288       return;
289     }
290     this.prometheusService.setSilence(this.getSubmitData()).subscribe(
291       (resp) => {
292         this.router.navigate(['/silence']);
293         this.notificationService.show(
294           NotificationType.success,
295           this.getNotificationTile(resp.body['silenceId']),
296           undefined,
297           undefined,
298           'Prometheus'
299         );
300       },
301       () => this.form.setErrors({ cdSubmitButton: true })
302     );
303   }
304
305   private getSubmitData(): AlertmanagerSilence {
306     const payload = this.form.value;
307     delete payload.duration;
308     payload.startsAt = payload.startsAt.toISOString();
309     payload.endsAt = payload.endsAt.toISOString();
310     payload.matchers = this.matchers;
311     if (this.edit) {
312       payload.id = this.id;
313     }
314     return payload;
315   }
316
317   private getNotificationTile(id: string) {
318     let action;
319     if (this.edit) {
320       action = this.succeededLabels.EDITED;
321     } else if (this.recreate) {
322       action = this.succeededLabels.RECREATED;
323     } else {
324       action = this.succeededLabels.CREATED;
325     }
326     return `${action} ${this.resource} ${id}`;
327   }
328 }