blob: 627e5f633a278906db842e10764ce188dc79152c [file] [log] [blame]
/**
* @module /silence-sk
* @description <h2><code>silence-sk</code></h2>
*
* @evt add-silence-note Sent when the user adds a note to an silence.
* The detail includes the text of the note and the key of the silence.
*
* <pre>
* detail {
* key: "12312123123",
* text: "blah blah blah",
* }
* </pre>
*
* @evt del-silence-note Sent when the user deletes a note on an silence.
* The detail includes the index of the note and the key of the silence.
*
* <pre>
* detail {
* key: "12312123123",
* index: 0,
* }
* </pre>
*
* @evt save-silence Sent when the user saves a silence.
* The detail is the silence.
*
* <pre>
* detail {
* silence: {...},
* }
* </pre>
*
* @evt archive-silence Sent when the user archives a silence.
* The detail is the silence.
*
* <pre>
* detail {
* silence: {...},
* }
* </pre>
*
* @evt reactivate-silence Sent when the user reactivates a silence.
* The detail is the silence.
*
* <pre>
* detail {
* silence: {...},
* }
* </pre>
*
* @evt delete-silence Sent when the user deletes a silence.
* The detail is the silence.
*
* <pre>
* detail {
* silence: {...},
* }
* </pre>
*
* @evt delete-silence-param Sent when the user deletes a param from a silence.
* The detail is a copy of the silence with the parameter deleted.
*
* <pre>
* detail {
* silence: {...},
* }
* </pre>
*
* @evt modify-silence-param Sent when the user modifies a param from a silence.
* The detail is a copy of the silence with the parameter modified.
*
* <pre>
* detail {
* silence: {...},
* }
* </pre>
*
* @evt add-silence-param Sent when the user add a param to a silence.
* The detail is a copy of the silence with the new parameter added.
*
* <pre>
* detail {
* silence: {...},
* }
* </pre>
*
*/
import { define } from 'elements-sk/define';
import 'elements-sk/icon/add-box-icon-sk';
import 'elements-sk/icon/delete-icon-sk';
import { $$ } from 'common-sk/modules/dom';
import { diffDate } from 'common-sk/modules/human';
import { errorMessage } from 'elements-sk/errorMessage';
import { html, render } from 'lit-html';
import { upgradeProperty } from 'elements-sk/upgradeProperty';
import {
abbr, displaySilence, expiresIn, getDurationTillNextDay, notes,
} from '../am';
import * as paramset from '../paramset';
const BOT_CENTRIC_PARAMS = ['alertname', 'bot'];
function table(ele, o) {
const keys = Object.keys(o);
keys.sort();
const botCentricParams = JSON.stringify(keys) === JSON.stringify(BOT_CENTRIC_PARAMS);
const rules = keys.filter((k) => !k.startsWith('__')).map((k) => html`
<tr>
<td>
<delete-icon-sk title='Delete rule.' @click=${(e) => ele._deleteRule(e, k)}></delete-icon-sk>
</td>
<th>${k}</th>
<td>
<input @change=${(e) => ele._modifyRule(e, k)} .value=${displayParamValue(o[k])}></input>
${displayAddBots(botCentricParams, k, ele)}
</td>
</tr>`);
rules.push(html`
<tr>
<td>
<add-box-icon-sk title='Add rule.' @click=${(e) => ele._addRule(e)}></add-box-icon-sk>
</td>
<td>
<input id='add_param_key'></input>
</td>
<td>
<input id='add_param_value'></input>
</td>
</tr>
`);
return rules;
}
function displayAddBots(botCentricParams, key, ele) {
if (botCentricParams && key === 'bot') {
return html `<button class="param-btns" @click=${() => ele._botsChooser()}>Add bot</button>`;
}
return '';
}
function displayParamValue(paramValue) {
if (paramValue.length > 1) {
return `${paramValue.join('|')}`
}
return paramValue;
}
function addNote(ele) {
if (ele._state.key) {
return html`
<textarea rows=2 cols=80 placeholder="Add description for the silence"></textarea>
<button @click=${ele._addNote}>Submit</button>
`;
}
return html`<textarea rows=2 cols=80 placeholder="Add description for the silence"></textarea>`;
}
function gotoIncident(incident) {
window.location.href = `/?alert_id=${incident.id}&tab=1`;
}
function matches(ele) {
if (!ele._incidents) {
return '';
}
return ele._incidents.filter(
(incident) => paramset.match(ele._state.param_set, incident.params) && incident.active,
).map((incident) => html`<h2 @click=${() => gotoIncident(incident)}> ${incident.params.alertname} ${abbr(incident)}</h2>`);
}
function classOfH2(silence) {
if (!silence.active) {
return 'inactive';
}
return '';
}
function actionButtons(ele) {
if (ele._state.active) {
return html`<button @click=${ele._save}>Save</button>
<button @click=${ele._archive}>Archive</button>`;
}
return html`<button @click=${ele._reactivate}>Reactivate</button>
<delete-icon-sk title='Delete silence.' @click=${ele._delete}></delete-icon-sk>`;
}
const template = (ele) => html`
<h2 class=${classOfH2(ele._state)} @click=${ele._headerClick}>${displaySilence(ele._state)}</h2>
<div class=body>
<section class=actions>
${actionButtons(ele)}
</section>
<table class=info>
<tr><th>User:</th><td>${ele._state.user}</td></th>
<tr><th>Duration:</th><td><input class="duration" @change=${ele._durationChange} value=${ele._state.duration}></input><button class="param-btns" @click=${ele._tillNextShift}>Till next shift</button></td></th>
<tr><th>Created</th><td title=${new Date(ele._state.created * 1000).toLocaleString()}>${diffDate(ele._state.created * 1000)}</td></tr>
<tr><th>Expires</th><td>${expiresIn(ele._state)}</td></tr>
</table>
<table class=params>
${table(ele, ele._state.param_set)}
</table>
<section class=notes>
${notes(ele)}
</section>
<section class=addNote>
${addNote(ele)}
</section>
<section class=matches>
<h1>Matches</h1>
${matches(ele)}
</section>
</div>
`;
define('silence-sk', class extends HTMLElement {
constructor() {
super();
this._incidents = [];
}
connectedCallback() {
upgradeProperty(this, 'state');
upgradeProperty(this, 'incidents');
}
/** @prop state {Object} A Silence. */
get state() { return this._state; }
set state(val) {
this._state = val;
this._render();
}
/** @prop incidents {string} The current active incidents. */
get incidents() { return this._incidents; }
set incidents(val) {
this._incidents = val;
this._render();
}
_headerClick() {
if (this.hasAttribute('collapsed')) {
this.removeAttribute('collapsed');
} else {
this.setAttribute('collapsed', '');
}
}
_durationChange(e) {
this._state.duration = e.target.value;
}
// Populates duration till next Monday 9am.
_tillNextShift() {
this._state.duration = getDurationTillNextDay(1, 9);
this._render();
}
_save() {
const detail = {
silence: this._state,
};
if (!this._state.key) {
const textarea = $$('textarea', this);
if (!textarea.value) {
errorMessage('Please enter a description for the silence');
textarea.focus();
return;
}
detail.silence.notes = [{
text: textarea.value,
ts: Math.floor(new Date().getTime() / 1000),
}];
}
this.dispatchEvent(new CustomEvent('save-silence', { detail: detail, bubbles: true }));
}
_archive() {
const detail = {
silence: this._state,
};
this.dispatchEvent(new CustomEvent('archive-silence', { detail: detail, bubbles: true }));
}
_reactivate() {
const detail = {
silence: this._state,
};
this.dispatchEvent(new CustomEvent('reactivate-silence', { detail: detail, bubbles: true }));
}
_delete() {
const detail = {
silence: this._state,
};
this.dispatchEvent(new CustomEvent('delete-silence', { detail: detail, bubbles: true }));
}
_deleteRule(e, key) {
const silence = JSON.parse(JSON.stringify(this._state));
delete silence.param_set[key];
const detail = {
silence: silence,
};
this.dispatchEvent(new CustomEvent('delete-silence-param', { detail: detail, bubbles: true }));
}
_modifyRule(e, key) {
const silence = JSON.parse(JSON.stringify(this._state));
silence.param_set[key] = [e.target.value];
const detail = {
silence: silence,
};
this.dispatchEvent(new CustomEvent('modify-silence-param', { detail: detail, bubbles: true }));
}
_addRule() {
const keyInput = $$('#add_param_key', this);
if (!keyInput.value) {
errorMessage('Please enter a name for the new param');
keyInput.focus();
return;
}
const valueInput = $$('#add_param_value', this);
if (!valueInput.value) {
errorMessage('Please enter a value for the new param');
valueInput.focus();
return;
}
// Dispatch event adding the new silence param.
const silence = JSON.parse(JSON.stringify(this._state));
silence.param_set[keyInput.value] = [valueInput.value];
const detail = {
silence: silence,
};
this.dispatchEvent(new CustomEvent('add-silence-param', { detail: detail, bubbles: true }));
// Reset the manual param key and value.
keyInput.value = '';
valueInput.value = '';
}
_botsChooser() {
this.dispatchEvent(new CustomEvent('bot-chooser', { detail: {}, bubbles: true }));
}
_addNote() {
const textarea = $$('textarea', this);
const detail = {
key: this._state.key,
text: textarea.value,
};
this.dispatchEvent(new CustomEvent('add-silence-note', { detail: detail, bubbles: true }));
textarea.value = '';
}
_deleteNote(e, index) {
const detail = {
key: this._state.key,
index: index,
};
this.dispatchEvent(new CustomEvent('del-silence-note', { detail: detail, bubbles: true }));
}
_render() {
render(template(this), this, { eventContext: this });
}
});