dashboard/shell/edit/monitoring.coreos.com.prome.../AlertingRule.vue

535 lines
15 KiB
Vue

<script>
import debounce from 'lodash/debounce';
import has from 'lodash/has';
import isEmpty from 'lodash/isEmpty';
import pickBy from 'lodash/pickBy';
import { Checkbox } from '@components/Form/Checkbox';
import CodeMirror from '@shell/components/CodeMirror';
import KeyValue from '@shell/components/form/KeyValue';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import UnitInput from '@shell/components/form/UnitInput';
import { _VIEW } from '@shell/config/query-params';
import { toSeconds } from '@shell/utils/duration';
const IGNORED_ANNOTATIONS = [
'summary',
'message',
'description',
'runbook_url',
];
export default {
components: {
Checkbox,
CodeMirror,
KeyValue,
LabeledInput,
LabeledSelect,
UnitInput,
},
props: {
value: {
type: Object,
default: () => ({}),
},
mode: {
type: String,
default: 'create',
},
},
data() {
return {
selectedSeverityLabel: null,
ignoredAnnotations: IGNORED_ANNOTATIONS,
severityOptions: [
{
label: this.t('prometheusRule.alertingRules.labels.severity.choices.critical'),
value: 'critical'
},
{
label: this.t('prometheusRule.alertingRules.labels.severity.choices.warning'),
value: 'warning'
},
{
label: this.t('prometheusRule.alertingRules.labels.severity.choices.none'),
value: 'none'
},
],
};
},
computed: {
isView() {
return this.mode === _VIEW;
},
severityLabelChecked: {
get() {
if (has(this.value?.labels, 'severity')) {
return true;
}
return false;
},
set(value) {
if (value) {
if (isEmpty(this.value?.labels)) {
this.value['labels'] = { severity: 'none' };
} else {
this.value.labels['severity'] = 'none';
}
} else {
this.value.labels['severity'] = '';
delete this.value.labels.severity;
}
},
},
summaryAnnotationChecked: {
get() {
if (has(this.value?.annotations, 'summary')) {
return true;
}
return false;
},
set(value) {
if (value) {
if (isEmpty(this.value?.annotations)) {
this.value['annotations'] = { summary: '' };
} else {
this.value.annotations = { ...this.value.annotations, summary: '' };
}
} else {
this.value.annotations['summary'] = '';
delete this.value.annotations.summary;
}
},
},
messageAnnotationChecked: {
get() {
if (has(this.value?.annotations, 'message')) {
return true;
}
return false;
},
set(value) {
if (value) {
if (isEmpty(this.value?.annotations)) {
this.value['annotations'] = { message: '' };
} else {
this.value.annotations = { ...this.value.annotations, message: '' };
}
} else {
this.value.annotations['message'] = '';
delete this.value.annotations.message;
}
},
},
descriptionAnnotationChecked: {
get() {
if (has(this.value?.annotations, 'description')) {
return true;
}
return false;
},
set(value) {
if (value) {
if (isEmpty(this.value?.annotations)) {
this.value['annotations'] = { description: '' };
} else {
this.value.annotations = { ...this.value.annotations, description: '' };
}
} else {
this.value.annotations['description'] = '';
delete this.value.annotations.description;
}
},
},
runbookAnnotationChecked: {
get() {
if (has(this.value?.annotations, 'runbook_url')) {
return true;
}
return false;
},
set(value) {
if (value) {
if (isEmpty(this.value?.annotations)) {
this.value['annotations'] = { runbook_url: '' };
} else {
this.value.annotations = { ...this.value.annotations, runbook_url: '' };
}
} else {
this.value.annotations['runbook_url'] = '';
delete this.value.annotations.runbook_url;
}
},
},
filteredAnnotations() {
const { ignoredAnnotations } = this;
const annotations = this.value?.annotations;
return pickBy(annotations, (value, key) => {
return !ignoredAnnotations.includes(key);
});
},
filteredLabels() {
const labels = this.value?.labels;
return pickBy(labels, (value, key) => {
return key !== 'severity';
});
},
showCustomSeverityInput() {
const { selectedSeverityLabel } = this;
if (selectedSeverityLabel === 'custom') {
return true;
}
return false;
},
waitToFireFor: {
get() {
if (![null, undefined].includes(this.value.for)) {
// see:
// https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/#rule
// https://prometheus.io/docs/prometheus/latest/configuration/configuration/#duration
return toSeconds(this.value.for);
}
return undefined;
},
set(v) {
this.value['for'] = [null, undefined].includes(v) ? undefined : `${ v }s`;
}
}
},
watch: {
selectedSeverityLabel(value /* , oldValue */) {
const neu = value === 'custom' ? '' : value;
if (this.value?.labels) {
this.value.labels['severity'] = neu;
} else {
this.value['labels'] = { severity: neu };
}
},
},
created() {
this.queueUpdate = debounce(this.updateExpression, 500);
this.queueLabelUpdate = debounce(this.updateLabels, 500);
this.queueAnnotationUpdate = debounce(this.updateAnnotations, 500);
},
mounted() {
this.$nextTick(() => {
if (this.value?.labels?.severity) {
this.selectedSeverityLabel = this.value.labels.severity;
}
});
},
methods: {
updateLabels(value) {
const neu = { ...value };
if (this.selectedSeverityLabel) {
neu['severity'] = this.selectedSeverityLabel;
}
this.value['labels'] = neu;
},
updateAnnotations(value) {
const {
ignoredAnnotations,
value: { annotations = {} },
} = this;
const neu = { ...value };
ignoredAnnotations.forEach((anno) => {
if (has(annotations, anno)) {
neu[anno] = annotations[anno];
}
});
this.value['annotations'] = neu;
},
updateExpression(value) {
this.value['expr'] = value;
}
},
};
</script>
<template>
<div>
<div class="row mt-25">
<div class="col span-6">
<LabeledInput
v-model:value="value.alert"
:label="t('prometheusRule.alertingRules.name')"
:required="true"
:mode="mode"
data-testid="v2-monitoring-alerting-rules-alert-name"
/>
</div>
<div class="col span-6">
<UnitInput
v-model:value="waitToFireFor"
:suffix="t('suffix.seconds', {count: value.for})"
:placeholder="t('prometheusRule.alertingRules.for.placeholder')"
:label="t('prometheusRule.alertingRules.for.label')"
:mode="mode"
/>
</div>
</div>
<div class="row">
<div class="col span-12">
<LabeledInput
v-model:value="value.expr"
:label="t('prometheusRule.promQL.label')"
:required="true"
:mode="mode"
>
<template #field>
<CodeMirror
class="mt-20 promql-input"
:value="value.expr"
:options="{
mode: null,
foldGutter: false,
readOnly: mode === 'view',
}"
data-testid="v2-monitoring-alerting-rules-promql"
@onInput="queueUpdate"
/>
</template>
</LabeledInput>
</div>
</div>
<div class="suggested-labels">
<div class="row mb-0 mt-30">
<div class="col span-12">
<h3>
<t k="prometheusRule.alertingRules.labels.label" />
</h3>
</div>
</div>
<div :class="[{ hide: isView && !severityLabelChecked }, 'row']">
<div
v-if="isView && severityLabelChecked"
class="col span-6 severity"
>
<label><t k="prometheusRule.alertingRules.labels.severity.label" /></label>
<div>{{ value.labels.severity }}</div>
</div>
<div
v-else
class="severity col span-6"
>
<Checkbox
v-model:value="severityLabelChecked"
:mode="mode"
:label="t('prometheusRule.alertingRules.labels.severity.label')"
/>
<LabeledSelect
v-if="severityLabelChecked"
v-model:value="value.labels.severity"
class="mt-10"
:mode="mode"
:label="t('prometheusRule.alertingRules.labels.severity.choices.label')"
:localized-label="false"
:options="severityOptions"
data-testid="v2-monitoring-alerting-rules-severity"
/>
</div>
</div>
<div :class="[{ hide: isView && Object.keys(filteredLabels).length === 0}, 'row', 'mt-0']">
<div class="col span-12">
<KeyValue
key="labels"
:value="filteredLabels"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-multiline="false"
@update:value="queueLabelUpdate"
/>
</div>
</div>
<div class="suggested-annotations">
<div class="row mb-0 mt-30">
<div class="col span-12">
<h3>
<t k="prometheusRule.alertingRules.annotations.label" />
</h3>
</div>
</div>
<div class="row mt-0 mb-0">
<div class="col span-12">
<div :class="[{ hide: isView && !summaryAnnotationChecked }, 'row']">
<div
v-if="isView && summaryAnnotationChecked"
class="col span-6 annotation-checkbox-container"
>
<label>
<t k="prometheusRule.alertingRules.annotations.summary.label" />
</label>
<div>{{ value.annotations.summary }}</div>
</div>
<div
v-else
class="col span-6 annotation-checkbox-container"
>
<Checkbox
v-model:value="summaryAnnotationChecked"
:mode="mode"
:label="t('prometheusRule.alertingRules.annotations.summary.label')"
/>
<LabeledInput
v-if="summaryAnnotationChecked"
v-model:value="value.annotations.summary"
class="mt-5"
:label="t('prometheusRule.alertingRules.annotations.summary.input')"
type="multiline"
:mode="mode"
/>
</div>
</div>
<div :class="[{ hide: isView && !messageAnnotationChecked }, 'row']">
<div
v-if="isView && messageAnnotationChecked"
class="col span-6 annotation-checkbox-container"
>
<label><t
k="prometheusRule.alertingRules.annotations.message.label"
/></label>
<div>{{ value.annotations.message }}</div>
</div>
<div
v-else
class="col span-6 annotation-checkbox-container"
>
<Checkbox
v-model:value="messageAnnotationChecked"
:mode="mode"
:label="t('prometheusRule.alertingRules.annotations.message.label')"
/>
<LabeledInput
v-if="messageAnnotationChecked"
v-model:value="value.annotations.message"
class="mt-5"
:label="t('prometheusRule.alertingRules.annotations.message.input')"
type="multiline"
:mode="mode"
/>
</div>
</div>
<div :class="[ { hide: isView && !descriptionAnnotationChecked }, 'row']">
<div
v-if="isView && descriptionAnnotationChecked"
class="col span-6 annotation-checkbox-container"
>
<label><t
k="prometheusRule.alertingRules.annotations.description.label"
/></label>
<div>{{ value.annotations.description }}</div>
</div>
<div
v-else
class="col span-6 annotation-checkbox-container"
>
<Checkbox
v-model:value="descriptionAnnotationChecked"
:mode="mode"
:label="t('prometheusRule.alertingRules.annotations.description.label')"
/>
<LabeledInput
v-if="descriptionAnnotationChecked"
v-model:value="value.annotations.description"
class="mt-5"
:label="t('prometheusRule.alertingRules.annotations.description.input')"
type="multiline"
:mode="mode"
/>
</div>
</div>
<div :class="[{ hide: isView && !runbookAnnotationChecked }, 'row']">
<div
v-if="isView && runbookAnnotationChecked"
class="col span-6 annotation-checkbox-container"
>
<label>
<t k="prometheusRule.alertingRules.annotations.runbook.label" />
</label>
<div>{{ value.annotations.runbook_url }}</div>
</div>
<div
v-else
class="col span-6 annotation-checkbox-container"
>
<Checkbox
v-model:value="runbookAnnotationChecked"
:mode="mode"
:label="t('prometheusRule.alertingRules.annotations.runbook.label')"
/>
<LabeledInput
v-if="runbookAnnotationChecked"
v-model:value="value.annotations.runbook_url"
class="mt-5"
:label="t('prometheusRule.alertingRules.annotations.runbook.input')"
type="multiline"
:mode="mode"
/>
</div>
</div>
</div>
</div>
<div :class="[{ hide: isView && Object.keys(filteredAnnotations).length === 0}, 'row', 'mt-0']">
<KeyValue
key="annotations"
:value="filteredAnnotations"
:add-label="t('labels.addAnnotation')"
:mode="mode"
:read-allowed="false"
@update:value="queueAnnotationUpdate"
/>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.row {
margin: 20px 0;
}
.remove {
position: absolute;
top: 0;
right: 5px;
}
.promql-input {
width: 100%;
}
</style>