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';
6 import * as _ from 'lodash';
7 import { BsModalService } from 'ngx-bootstrap/modal';
9 import { PrometheusService } from '../../../../shared/api/prometheus.service';
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';
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';
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[];
43 // Date formatting rules can be found here: https://momentjs.com/docs/#/displaying/format/
44 bsConfig = { dateInputFormat: 'YYYY-MM-DDT HH:mm' };
51 resource = this.i18n('silence');
53 matchers: AlertmanagerSilenceMatcher[] = [];
54 matcherMatch: AlertmanagerSilenceMatcherMatch = undefined;
57 tooltip: this.i18n('Attribute name'),
58 icon: this.icons.paragraph,
62 tooltip: this.i18n('Value'),
63 icon: this.icons.terminal,
67 tooltip: this.i18n('Regular expression'),
68 icon: this.icons.magic,
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
98 private chooseMode() {
99 this.edit = this.router.url.startsWith('/silence/edit');
100 this.recreate = this.router.url.startsWith('/silence/recreate');
102 this.action = this.actionLabels.EDIT;
103 } else if (this.recreate) {
104 this.action = this.actionLabels.RECREATE;
106 this.action = this.actionLabels.CREATE;
110 private authenticate() {
111 this.permission = this.authStorageService.getPermissions().prometheus;
113 this.permission.read && (this.edit ? this.permission.update : this.permission.create);
115 this.router.navigate(['/404']);
119 private createForm() {
120 this.form = this.formBuilder.group(
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]]
129 validators: CdValidators.custom('matcherRequired', () => this.matchers.length === 0)
134 private setupDates() {
135 const now = new Date();
136 now.setSeconds(0, 0); // Normalizes start date
137 this.form.silentSet('startsAt', now);
139 this.subscribeDateChanges();
142 private updateDate(updateStartDate?: boolean) {
143 const next = this.timeDiff.calculateDate(
144 this.form.getValue(updateStartDate ? 'endsAt' : 'startsAt'),
145 this.form.getValue('duration'),
149 this.form.silentSet(updateStartDate ? 'startsAt' : 'endsAt', next);
153 private subscribeDateChanges() {
154 this.form.get('startsAt').valueChanges.subscribe(() => {
157 this.form.get('duration').valueChanges.subscribe(() => {
160 this.form.get('endsAt').valueChanges.subscribe(() => {
161 this.onDateChange(true);
165 private onDateChange(updateStartDate?: boolean) {
166 if (this.form.getValue('startsAt') < this.form.getValue('endsAt')) {
167 this.updateDuration();
169 this.updateDate(updateStartDate);
173 private updateDuration() {
176 this.timeDiff.calculateDuration(this.form.getValue('startsAt'), this.form.getValue('endsAt'))
182 this.getModeSpecificData();
186 this.prometheusService.ifPrometheusConfigured(
188 this.prometheusService.getRules().subscribe(
189 (rules) => (this.rules = rules),
191 this.prometheusService.disablePrometheusConfig();
197 this.notificationService.show(
198 NotificationType.info,
200 'Please add your Prometheus host to the dashboard configuration and refresh the page'
210 private getModeSpecificData() {
211 this.route.params.subscribe((params: { id: string }) => {
215 if (this.edit || this.recreate) {
216 this.prometheusService.getSilences(params).subscribe((silences) => {
217 this.fillFormWithSilence(silences[0]);
220 this.prometheusService.getAlerts(params).subscribe((alerts) => {
221 this.fillFormByAlert(alerts[0]);
227 private fillFormWithSilence(silence: AlertmanagerSilence) {
228 this.id = silence.id;
230 ['startsAt', 'endsAt'].forEach((attr) => this.form.silentSet(attr, new Date(silence[attr])));
231 this.updateDuration();
233 ['createdBy', 'comment'].forEach((attr) => this.form.silentSet(attr, silence[attr]));
234 this.matchers = silence.matchers;
235 this.validateMatchers();
238 private validateMatchers() {
240 window.setTimeout(() => this.validateMatchers(), 100);
243 this.matcherMatch = this.silenceMatcher.multiMatch(this.matchers, this.rules);
244 this.form.markAsDirty();
245 this.form.updateValueAndValidity();
248 private fillFormByAlert(alert: AlertmanagerAlert) {
249 const labels = alert.labels;
250 Object.keys(labels).forEach((key) =>
259 private setMatcher(matcher: AlertmanagerSilenceMatcher, index?: number) {
260 if (_.isNumber(index)) {
261 this.matchers[index] = matcher;
263 this.matchers.push(matcher);
265 this.validateMatchers();
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]);
276 modalRef.content.submitAction.subscribe((matcher: AlertmanagerSilenceMatcher) => {
277 this.setMatcher(matcher, index);
281 deleteMatcher(index: number) {
282 this.matchers.splice(index, 1);
283 this.validateMatchers();
287 if (this.form.invalid) {
290 this.prometheusService.setSilence(this.getSubmitData()).subscribe(
292 this.router.navigate(['/silence']);
293 this.notificationService.show(
294 NotificationType.success,
295 this.getNotificationTile(resp.body['silenceId']),
301 () => this.form.setErrors({ cdSubmitButton: true })
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;
312 payload.id = this.id;
317 private getNotificationTile(id: string) {
320 action = this.succeededLabels.EDITED;
321 } else if (this.recreate) {
322 action = this.succeededLabels.RECREATED;
324 action = this.succeededLabels.CREATED;
326 return `${action} ${this.resource} ${id}`;