blob: cd2a20899a6def7d81af77443bbd99dfd4eed312 [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 { html, render, TemplateResult } from 'lit-html';
import { define } from '../../../elements-sk/modules/define';
import '../../../elements-sk/modules/icons/add-box-icon-sk';
import '../../../elements-sk/modules/icons/delete-icon-sk';
import { $$ } from '../../../infra-sk/modules/dom';
import { diffDate } from '../../../infra-sk/modules/human';
import { errorMessage } from '../../../elements-sk/modules/errorMessage';
import {
abbr,
displaySilence,
expiresIn,
getDurationTillNextDay,
displayNotes,
} from '../am';
import * as paramset from '../paramset';
import { Incident, ParamSet, Note, Silence } from '../json';
const BOT_CENTRIC_PARAMS = ['alertname', 'bot'];
export class State {
key: string = '';
param_set: ParamSet = {};
duration: string = '';
created: number = 0;
user: string = '';
notes: Note[] = [];
active: boolean = false;
}
export class SilenceSk extends HTMLElement {
notes: Note[] = [];
private state: State = {
key: '',
param_set: {},
duration: '',
created: 0,
user: '',
notes: [],
active: false,
};
private incidents: Incident[] = [];
private static template = (ele: SilenceSk) => html`
<h2 class=${ele.classOfH2()} @click=${ele.headerClick}>${displaySilence(
ele.state.param_set
)}</h2>
<div class=body>
<section class=actions>
${ele.actionButtons()}
</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.active,
ele.state.created,
ele.state.duration
)}</td></tr>
</table>
<table class=params>
${ele.table()}
</table>
<section class=notes>
${displayNotes(ele.state.notes, ele.state.key, 'del-silence-note')}
</section>
<section class=addNote>
${ele.displayAddNote()}
</section>
<section class=matches>
<h1>Matches</h1>
${ele.displayMatches()}
</section>
</div>
`;
connectedCallback(): void {
this._render();
}
/** @prop silence_state A Silence. */
get silence_state(): State {
return this.state;
}
set silence_state(val: State) {
this.state = val;
this._render();
}
/** @prop silence_incidents The current active incidents. */
get silence_incidents(): Incident[] {
return this.incidents;
}
set silence_incidents(val: Incident[]) {
this.incidents = val;
this._render();
}
private table(): TemplateResult[] {
const keys = Object.keys(this.state.param_set);
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=${() =>
this.deleteRule(k)}></delete-icon-sk>
</td>
<th>${k}</th>
<td>
<input class=param-val @change=${(e: Event) =>
this.modifyRule(e, k)} .value=${this.displayParamValue(
this.state.param_set[k]!
)}></input>
${this.displayAddBots(botCentricParams, k)}
</td>
</tr>`
);
rules.push(html`
<tr>
<td>
<add-box-icon-sk title='Add rule.' @click=${() =>
this.addRule()}></add-box-icon-sk>
</td>
<td>
<input id='add_param_key'></input>
</td>
<td>
<input class=param-val id='add_param_value'></input>
</td>
</tr>
`);
return rules;
}
private displayAddBots(
botCentricParams: boolean,
key: string
): TemplateResult {
if (botCentricParams && key === 'bot') {
return html`<button class="param-btns" @click=${() => this.botsChooser()}>
Add bot
</button>`;
}
return html``;
}
private displayParamValue(paramValue: string[]): string | string[] {
if (paramValue.length > 1) {
return `${paramValue.join('|')}`;
}
return paramValue;
}
private displayAddNote(): TemplateResult {
if (this.state.key) {
return html`
<textarea
rows="2"
cols="80"
placeholder="Add description for the silence"></textarea>
<button @click=${this.addNote}>Submit</button>
`;
}
return html`<textarea
rows="2"
cols="80"
placeholder="Add description for the silence"></textarea>`;
}
private gotoIncident(incident: Incident): void {
window.location.href = `/?alert_id=${incident.id}&tab=1`;
}
private displayMatches(): TemplateResult[] {
if (!this.incidents) {
return [];
}
return this.incidents
.filter(
(incident) =>
paramset.match(this.state.param_set, incident.params) &&
incident.active
)
.map(
(incident) =>
html`<h2 @click=${() => this.gotoIncident(incident)}>
${incident.params.alertname} ${abbr(incident.params.abbr)}
</h2>`
);
}
private classOfH2(): string {
if (!this.state.active) {
return 'inactive';
}
return '';
}
private actionButtons(): TemplateResult {
if (this.state.active) {
return html`<button @click=${this.save}>Save</button>
<button @click=${this.archive}>Archive</button>`;
}
return html`<button @click=${this.reactivate}>Reactivate</button>
<delete-icon-sk
title="Delete silence."
@click=${this.delete}></delete-icon-sk>`;
}
private headerClick(): void {
if (this.hasAttribute('collapsed')) {
this.removeAttribute('collapsed');
} else {
this.setAttribute('collapsed', '');
}
}
private durationChange(e: Event): void {
this.state.duration = (e.target as HTMLInputElement).value;
}
// Populates duration till next Monday 9am.
private tillNextShift(): void {
this.state.duration = getDurationTillNextDay(1, 9);
this._render();
}
private save(): void {
const detail = {
silence: this.state,
};
if (!this.state.key) {
const textarea = $$('textarea', this)! as HTMLInputElement;
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),
author: '', // The backend fills in the author.
},
];
}
this.dispatchEvent(
new CustomEvent('save-silence', { detail: detail, bubbles: true })
);
}
private archive(): void {
const detail = {
silence: this.state,
};
this.dispatchEvent(
new CustomEvent('archive-silence', { detail: detail, bubbles: true })
);
}
private reactivate(): void {
const detail = {
silence: this.state,
};
this.dispatchEvent(
new CustomEvent('reactivate-silence', { detail: detail, bubbles: true })
);
}
private delete(): void {
const detail = {
silence: this.state,
};
this.dispatchEvent(
new CustomEvent('delete-silence', { detail: detail, bubbles: true })
);
}
private deleteRule(key: string): void {
const silence = JSON.parse(JSON.stringify(this.state)) as Silence;
delete silence.param_set[key];
const detail = {
silence: silence,
};
this.dispatchEvent(
new CustomEvent('delete-silence-param', {
detail: detail,
bubbles: true,
})
);
}
private modifyRule(e: Event, key: string): void {
const silence = JSON.parse(JSON.stringify(this.state)) as Silence;
silence.param_set[key] = [(e.target as HTMLInputElement).value];
const detail = {
silence: silence,
};
this.dispatchEvent(
new CustomEvent('modify-silence-param', {
detail: detail,
bubbles: true,
})
);
}
private addRule(): void {
const keyInput = $$('#add_param_key', this) as HTMLInputElement;
if (!keyInput.value) {
errorMessage('Please enter a name for the new param');
keyInput.focus();
return;
}
const valueInput = $$('#add_param_value', this) as HTMLInputElement;
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)) as Silence;
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 = '';
}
private botsChooser(): void {
this.dispatchEvent(
new CustomEvent('bot-chooser', { detail: {}, bubbles: true })
);
}
private addNote(): void {
const textarea = $$('textarea', this) as HTMLInputElement;
const detail = {
key: this.state.key,
text: textarea.value,
};
this.dispatchEvent(
new CustomEvent('add-silence-note', { detail: detail, bubbles: true })
);
textarea.value = '';
}
private _render(): void {
render(SilenceSk.template(this), this, { eventContext: this });
}
}
define('silence-sk', SilenceSk);