Files
karma/ui/src/Components/AlertAck/index.js
Łukasz Mierzwa 159bcc4e8a fix(ui): use mode:no-cors for fetch GET requests
This should play better with auth middlewares that uses redirects since CORS will no longer break the redirect chain
2019-11-14 16:25:17 +00:00

254 lines
7.6 KiB
JavaScript

import React, { Component } from "react";
import PropTypes from "prop-types";
import { observable, action, computed, toJS } from "mobx";
import { observer } from "mobx-react";
import moment from "moment";
import semver from "semver";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck } from "@fortawesome/free-solid-svg-icons/faCheck";
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
import { APIGroup } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
import {
SilenceFormStore,
MatchersFromGroup,
GenerateAlertmanagerSilenceData
} from "Stores/SilenceFormStore";
import { FetchPost } from "Common/Fetch";
import { TooltipWrapper } from "Components/TooltipWrapper";
const SubmitState = Object.freeze({
Idle: "Idle",
InProgress: "InProgress",
Done: "Done",
Failed: "Failed"
});
const newPendingSilence = (
group,
members,
durationSeconds,
author,
commentPrefix
) => ({
payload: GenerateAlertmanagerSilenceData(
moment.utc(),
moment.utc().add(durationSeconds, "seconds"),
MatchersFromGroup(group, []),
author,
`${
commentPrefix ? commentPrefix + " " : ""
}This alert was acknowledged using karma on ${moment.utc().toString()}`
),
membersToTry: members,
submitState: SubmitState.Idle,
submitResult: null,
isDone: false,
isFailed: false,
fetch: null
});
const AlertAck = observer(
class AlertAck extends Component {
static propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
group: APIGroup.isRequired
};
constructor(props) {
super(props);
this.submitState = observable(
{
silencesByCluster: {},
reset() {
this.silencesByCluster = {};
},
pushSilence(cluster, silence) {
this.silencesByCluster[cluster] = silence;
},
markDone(cluster) {
this.silencesByCluster[cluster].isDone = true;
},
markFailed(cluster) {
this.silencesByCluster[cluster].isDone = true;
this.silencesByCluster[cluster].isFailed = true;
},
get isIdle() {
return Object.keys(this.silencesByCluster).length === 0;
},
get isInprogress() {
return (
Object.values(this.silencesByCluster).filter(
pendingSilence => pendingSilence.isDone === false
).length > 0
);
},
get isDone() {
return (
Object.values(this.silencesByCluster).filter(
pendingSilence => pendingSilence.isDone === true
).length > 0
);
},
get isFailed() {
return (
Object.values(this.silencesByCluster).filter(
pendingSilence => pendingSilence.isFailed === true
).length > 0
);
}
},
{
reset: action.bound,
pushSilence: action.bound,
markDone: action.bound,
markFailed: action.bound,
isIdle: computed,
isInprogress: computed,
isDone: computed,
isFailed: computed
}
);
}
maybeTryAgainAfterError = cluster => {
if (this.submitState.silencesByCluster[cluster].membersToTry.length) {
this.handleAlertmanagerRequest(cluster);
} else {
this.submitState.markFailed(cluster);
}
};
handleAlertmanagerRequest = cluster => {
const { alertStore } = this.props;
const member = this.submitState.silencesByCluster[
cluster
].membersToTry.pop();
const am = alertStore.data.getAlertmanagerByName(member);
if (am === undefined) {
const err = `Alertmanager instance "${member} not found`;
console.error(err);
this.maybeTryAgainAfterError(cluster);
return;
}
const isOpenAPI = semver.satisfies(am.version, ">=0.16.0");
const uri = isOpenAPI
? `${am.uri}/api/v2/silences`
: `${am.uri}/api/v1/silences`;
this.submitState.silencesByCluster[cluster].fetch = FetchPost(uri, {
body: JSON.stringify(
this.submitState.silencesByCluster[cluster].payload
),
headers: {
"Content-Type": "application/json",
...am.headers
}
})
.then(result => {
if (isOpenAPI) {
if (result.ok) {
return result
.json()
.then(r => this.submitState.markDone(cluster));
} else {
this.maybeTryAgainAfterError(cluster);
}
} else {
return result
.json()
.then(r =>
r.status === "success"
? this.submitState.markDone(cluster)
: this.maybeTryAgainAfterError(cluster)
);
}
})
.catch(() => {
this.maybeTryAgainAfterError(cluster);
});
};
onACK = () => {
const { group, alertStore, silenceFormStore } = this.props;
if (this.submitState.isInprogress || this.submitState.isDone) {
return;
}
const alertmanagers = Object.entries(group.alertmanagerCount)
.filter(([amName, alertCount]) => alertCount > 0)
.map(([amName, _]) => amName);
const clusters = Object.entries(
alertStore.data.upstreams.clusters
).filter(([clusterName, clusterMembers]) =>
alertmanagers.some(m => clusterMembers.includes(m))
);
this.submitState.reset();
for (const [clusterName, clusterMembers] of clusters) {
const pendingSilence = newPendingSilence(
toJS(group),
toJS(clusterMembers),
toJS(alertStore.settings.values.alertAcknowledgement.durationSeconds),
alertStore.settings.values.silenceForm.author !== ""
? alertStore.settings.values.silenceForm.author
: silenceFormStore.data.author !== ""
? toJS(silenceFormStore.data.author)
: toJS(alertStore.settings.values.alertAcknowledgement.author),
toJS(alertStore.settings.values.alertAcknowledgement.commentPrefix)
);
this.submitState.pushSilence(clusterName, pendingSilence);
this.handleAlertmanagerRequest(clusterName);
}
};
render() {
const { alertStore } = this.props;
if (alertStore.settings.values.alertAcknowledgement.enabled === false) {
return null;
}
return (
<TooltipWrapper title="Acknowlage this alert with a short lived silence">
<span
className={`badge badge-pill components-label components-label-with-hover px-2 ${
this.submitState.isFailed
? "badge-warning"
: this.submitState.isDone
? "badge-success"
: "badge-secondary"
}`}
onClick={this.onACK}
>
{this.submitState.isIdle ? (
<FontAwesomeIcon icon={faCheck} fixedWidth />
) : this.submitState.isInprogress ? (
<FontAwesomeIcon icon={faSpinner} fixedWidth spin />
) : this.submitState.isFailed ? (
<FontAwesomeIcon icon={faExclamationCircle} fixedWidth />
) : (
<FontAwesomeIcon icon={faCheckCircle} fixedWidth />
)}
</span>
</TooltipWrapper>
);
}
}
);
export { AlertAck };