1 import { Component } from '@angular/core';
2 import { Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
5 import * as _ from 'lodash';
6 import * as moment from 'moment';
8 import { PrometheusService } from '../../../../shared/api/prometheus.service';
11 SucceededActionLabelsI18n
12 } from '../../../../shared/constants/app.constants';
13 import { Icons } from '../../../../shared/enum/icons.enum';
14 import { NotificationType } from '../../../../shared/enum/notification-type.enum';
15 import { CdFormBuilder } from '../../../../shared/forms/cd-form-builder';
16 import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
17 import { CdValidators } from '../../../../shared/forms/cd-validators';
20 AlertmanagerSilenceMatcher,
21 AlertmanagerSilenceMatcherMatch
22 } from '../../../../shared/models/alertmanager-silence';
23 import { Permission } from '../../../../shared/models/permissions';
24 import { AlertmanagerAlert, PrometheusRule } from '../../../../shared/models/prometheus-alerts';
25 import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
26 import { ModalService } from '../../../../shared/services/modal.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';
33 selector: 'cd-prometheus-form',
34 templateUrl: './silence-form.component.html',
35 styleUrls: ['./silence-form.component.scss']
37 export class SilenceFormComponent {
39 permission: Permission;
41 rules: PrometheusRule[];
48 resource = $localize`silence`;
50 matchers: AlertmanagerSilenceMatcher[] = [];
51 matcherMatch: AlertmanagerSilenceMatcherMatch = undefined;
54 tooltip: $localize`Attribute name`,
55 icon: this.icons.paragraph,
59 tooltip: $localize`Value`,
60 icon: this.icons.terminal,
64 tooltip: $localize`Regular expression`,
65 icon: this.icons.magic,
70 datetimeFormat = 'YYYY-MM-DD HH:mm';
73 private router: Router,
74 private authStorageService: AuthStorageService,
75 private formBuilder: CdFormBuilder,
76 private prometheusService: PrometheusService,
77 private notificationService: NotificationService,
78 private route: ActivatedRoute,
79 private timeDiff: TimeDiffService,
80 private modalService: ModalService,
81 private silenceMatcher: PrometheusSilenceMatcherService,
82 private actionLabels: ActionLabelsI18n,
83 private succeededLabels: SucceededActionLabelsI18n
96 private chooseMode() {
97 this.edit = this.router.url.startsWith('/monitoring/silences/edit');
98 this.recreate = this.router.url.startsWith('/monitoring/silences/recreate');
100 this.action = this.actionLabels.EDIT;
101 } else if (this.recreate) {
102 this.action = this.actionLabels.RECREATE;
104 this.action = this.actionLabels.CREATE;
108 private authenticate() {
109 this.permission = this.authStorageService.getPermissions().prometheus;
111 this.permission.read && (this.edit ? this.permission.update : this.permission.create);
113 this.router.navigate(['/404']);
117 private createForm() {
118 const formatValidator = CdValidators.custom('format', (expiresAt: string) => {
119 const result = expiresAt === '' || moment(expiresAt, this.datetimeFormat).isValid();
122 this.form = this.formBuilder.group(
124 startsAt: ['', [Validators.required, formatValidator]],
125 duration: ['2h', [Validators.min(1)]],
126 endsAt: ['', [Validators.required, formatValidator]],
127 createdBy: [this.authStorageService.getUsername(), [Validators.required]],
128 comment: [null, [Validators.required]]
131 validators: CdValidators.custom('matcherRequired', () => this.matchers.length === 0)
136 private setupDates() {
137 const now = moment().format(this.datetimeFormat);
138 this.form.silentSet('startsAt', now);
140 this.subscribeDateChanges();
143 private updateDate(updateStartDate?: boolean) {
145 this.form.getValue(updateStartDate ? 'endsAt' : 'startsAt'),
148 const next = this.timeDiff.calculateDate(date, this.form.getValue('duration'), updateStartDate);
150 const nextDate = moment(next).format(this.datetimeFormat);
151 this.form.silentSet(updateStartDate ? 'startsAt' : 'endsAt', nextDate);
155 private subscribeDateChanges() {
156 this.form.get('startsAt').valueChanges.subscribe(() => {
159 this.form.get('duration').valueChanges.subscribe(() => {
162 this.form.get('endsAt').valueChanges.subscribe(() => {
163 this.onDateChange(true);
167 private onDateChange(updateStartDate?: boolean) {
168 const startsAt = moment(this.form.getValue('startsAt'), this.datetimeFormat);
169 const endsAt = moment(this.form.getValue('endsAt'), this.datetimeFormat);
170 if (startsAt.isBefore(endsAt)) {
171 this.updateDuration();
173 this.updateDate(updateStartDate);
177 private updateDuration() {
178 const startsAt = moment(this.form.getValue('startsAt'), this.datetimeFormat).toDate();
179 const endsAt = moment(this.form.getValue('endsAt'), this.datetimeFormat).toDate();
180 this.form.silentSet('duration', this.timeDiff.calculateDuration(startsAt, endsAt));
185 this.getModeSpecificData();
189 this.prometheusService.ifPrometheusConfigured(
191 this.prometheusService.getRules().subscribe(
193 this.rules = groups['groups'].reduce(
194 (acc, group) => _.concat<PrometheusRule>(acc, group.rules),
199 this.prometheusService.disablePrometheusConfig();
205 this.notificationService.show(
206 NotificationType.info,
207 $localize`Please add your Prometheus host to the dashboard configuration and refresh the page`,
216 private getModeSpecificData() {
217 this.route.params.subscribe((params: { id: string }) => {
221 if (this.edit || this.recreate) {
222 this.prometheusService.getSilences(params).subscribe((silences) => {
223 this.fillFormWithSilence(silences[0]);
226 this.prometheusService.getAlerts(params).subscribe((alerts) => {
227 this.fillFormByAlert(alerts[0]);
233 private fillFormWithSilence(silence: AlertmanagerSilence) {
234 this.id = silence.id;
236 ['startsAt', 'endsAt'].forEach((attr) =>
237 this.form.silentSet(attr, moment(silence[attr]).format(this.datetimeFormat))
239 this.updateDuration();
241 ['createdBy', 'comment'].forEach((attr) => this.form.silentSet(attr, silence[attr]));
242 this.matchers = silence.matchers;
243 this.validateMatchers();
246 private validateMatchers() {
248 window.setTimeout(() => this.validateMatchers(), 100);
251 this.matcherMatch = this.silenceMatcher.multiMatch(this.matchers, this.rules);
252 this.form.markAsDirty();
253 this.form.updateValueAndValidity();
256 private fillFormByAlert(alert: AlertmanagerAlert) {
257 const labels = alert.labels;
258 Object.keys(labels).forEach((key) =>
267 private setMatcher(matcher: AlertmanagerSilenceMatcher, index?: number) {
268 if (_.isNumber(index)) {
269 this.matchers[index] = matcher;
271 this.matchers.push(matcher);
273 this.validateMatchers();
276 showMatcherModal(index?: number) {
277 const modalRef = this.modalService.show(SilenceMatcherModalComponent);
278 const modalComponent = modalRef.componentInstance as SilenceMatcherModalComponent;
279 modalComponent.rules = this.rules;
280 if (_.isNumber(index)) {
281 modalComponent.editMode = true;
282 modalComponent.preFillControls(this.matchers[index]);
284 modalComponent.submitAction.subscribe((matcher: AlertmanagerSilenceMatcher) => {
285 this.setMatcher(matcher, index);
289 deleteMatcher(index: number) {
290 this.matchers.splice(index, 1);
291 this.validateMatchers();
295 if (this.form.invalid) {
298 this.prometheusService.setSilence(this.getSubmitData()).subscribe(
300 this.router.navigate(['/monitoring/silences']);
301 this.notificationService.show(
302 NotificationType.success,
303 this.getNotificationTile(resp.body['silenceId']),
309 () => this.form.setErrors({ cdSubmitButton: true })
313 private getSubmitData(): AlertmanagerSilence {
314 const payload = this.form.value;
315 delete payload.duration;
316 payload.startsAt = moment(payload.startsAt, this.datetimeFormat).toISOString();
317 payload.endsAt = moment(payload.endsAt, this.datetimeFormat).toISOString();
318 payload.matchers = this.matchers;
320 payload.id = this.id;
325 private getNotificationTile(id: string) {
328 action = this.succeededLabels.EDITED;
329 } else if (this.recreate) {
330 action = this.succeededLabels.RECREATED;
332 action = this.succeededLabels.CREATED;
334 return `${action} ${this.resource} ${id}`;