mirror of
https://github.com/prymitive/karma
synced 2026-05-19 04:26:41 +00:00
333 lines
9.3 KiB
JavaScript
333 lines
9.3 KiB
JavaScript
import React, { Component } from "react";
|
|
import PropTypes from "prop-types";
|
|
|
|
import { observable, action } from "mobx";
|
|
import { observer } from "mobx-react";
|
|
|
|
import semver from "semver";
|
|
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
|
|
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
|
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
|
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
|
|
|
|
import { APISilence } from "Models/API";
|
|
import { AlertStore, FormatBackendURI, FormatAlertsQ } from "Stores/AlertStore";
|
|
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
|
import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query";
|
|
import { FetchWithCredentials } from "Common/Fetch";
|
|
import { Modal } from "Components/Modal";
|
|
import {
|
|
LabelSetList,
|
|
GroupListToUniqueLabelsList
|
|
} from "Components/LabelSetList";
|
|
|
|
const ProgressMessage = () => (
|
|
<div className="text-center">
|
|
<FontAwesomeIcon
|
|
icon={faCircleNotch}
|
|
className="text-muted display-1 mb-3"
|
|
spin
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
const ErrorMessage = ({ message }) => (
|
|
<div className="text-center">
|
|
<FontAwesomeIcon
|
|
icon={faExclamationCircle}
|
|
className="text-danger display-1 mb-3"
|
|
/>
|
|
<p>{message}</p>
|
|
</div>
|
|
);
|
|
ErrorMessage.propTypes = {
|
|
message: PropTypes.node.isRequired
|
|
};
|
|
|
|
const SuccessMessage = () => (
|
|
<div className="text-center">
|
|
<FontAwesomeIcon
|
|
icon={faCheckCircle}
|
|
className="text-success display-1 mb-3"
|
|
/>
|
|
<p>
|
|
Silence deleted, it might take a few minutes for affected alerts to change
|
|
state
|
|
</p>
|
|
</div>
|
|
);
|
|
|
|
const DeleteSilenceModalContent = observer(
|
|
class DeleteSilenceModalContent extends Component {
|
|
static propTypes = {
|
|
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
|
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
|
cluster: PropTypes.string.isRequired,
|
|
silence: APISilence.isRequired,
|
|
onHide: PropTypes.func
|
|
};
|
|
|
|
previewState = observable(
|
|
{
|
|
fetch: null,
|
|
error: null,
|
|
alertLabels: [],
|
|
setError(err) {
|
|
this.error = err;
|
|
},
|
|
groupsToUniqueLabels(groups) {
|
|
this.alertLabels = GroupListToUniqueLabelsList(groups);
|
|
}
|
|
},
|
|
{
|
|
setError: action.bound,
|
|
groupsToUniqueLabels: action.bound
|
|
}
|
|
);
|
|
|
|
deleteState = observable(
|
|
{
|
|
fetch: null,
|
|
done: false,
|
|
error: null,
|
|
setDone() {
|
|
this.done = true;
|
|
},
|
|
setError(err) {
|
|
this.error = err;
|
|
},
|
|
reset() {
|
|
this.done = false;
|
|
this.error = null;
|
|
}
|
|
},
|
|
{
|
|
setDone: action.bound,
|
|
setError: action.bound,
|
|
reset: action.bound
|
|
}
|
|
);
|
|
|
|
getAlertmanager = () =>
|
|
this.props.alertStore.data.upstreams.instances
|
|
.filter(u => u.cluster === this.props.cluster)
|
|
.slice(0, 1)[0];
|
|
|
|
parseAlertmanagerResponse = response => {
|
|
/*
|
|
{"status": "success"}
|
|
or
|
|
{
|
|
"status": "error",
|
|
"errorType": "bad_data",
|
|
"error": "silence 706959fd-4590-4e21-b983-859ba6ec0e1a already expired"
|
|
}
|
|
*/
|
|
if (response.status === "success") {
|
|
this.deleteState.setError(null);
|
|
} else if (response.status === "error" && response.error) {
|
|
this.deleteState.setError(response.error);
|
|
} else {
|
|
this.deleteState.setError(JSON.stringify(response));
|
|
}
|
|
this.deleteState.setDone();
|
|
};
|
|
|
|
onFetchPreview = () => {
|
|
const { silence } = this.props;
|
|
|
|
const alertsURI =
|
|
FormatBackendURI("alerts.json?") +
|
|
FormatAlertsQ([
|
|
FormatQuery(StaticLabels.SilenceID, QueryOperators.Equal, silence.id)
|
|
]);
|
|
|
|
this.previewState.fetch = FetchWithCredentials(alertsURI, {})
|
|
.then(result => result.json())
|
|
.then(result => {
|
|
this.previewState.groupsToUniqueLabels(Object.values(result.groups));
|
|
this.previewState.setError(null);
|
|
})
|
|
.catch(err => {
|
|
console.trace(err);
|
|
return this.previewState.setError(
|
|
`Request fetching affected alerts failed with: ${err.message}`
|
|
);
|
|
});
|
|
};
|
|
|
|
onDelete = () => {
|
|
const { silence } = this.props;
|
|
|
|
// if it's already deleted then do nothing
|
|
if (this.deleteState.done && this.deleteState.error === null) return;
|
|
|
|
// reset state so we get a spinner
|
|
this.deleteState.reset();
|
|
|
|
const alertmanager = this.getAlertmanager();
|
|
|
|
const isOpenAPI = semver.satisfies(alertmanager.version, ">=0.16.0");
|
|
|
|
const uri = isOpenAPI
|
|
? `${alertmanager.uri}/api/v2/silence/${silence.id}`
|
|
: `${alertmanager.uri}/api/v1/silence/${silence.id}`;
|
|
|
|
this.deleteState.fetch = FetchWithCredentials(uri, {
|
|
method: "DELETE",
|
|
headers: alertmanager.headers
|
|
})
|
|
.then(result => {
|
|
if (isOpenAPI) {
|
|
if (result.ok) {
|
|
this.deleteState.setError(null);
|
|
this.deleteState.setDone();
|
|
} else {
|
|
result.text().then(this.deleteState.setError);
|
|
this.deleteState.setDone();
|
|
}
|
|
} else {
|
|
result.json().then(this.parseAlertmanagerResponse);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.trace(err);
|
|
this.deleteState.setDone();
|
|
this.deleteState.setError(
|
|
`Delete request failed with: ${err.message}`
|
|
);
|
|
});
|
|
};
|
|
|
|
componentDidMount() {
|
|
const { silenceFormStore } = this.props;
|
|
silenceFormStore.toggle.setBlur(true);
|
|
this.onFetchPreview();
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
const { silenceFormStore } = this.props;
|
|
silenceFormStore.toggle.setBlur(false);
|
|
}
|
|
|
|
render() {
|
|
const { alertStore, onHide } = this.props;
|
|
|
|
const isDone = this.deleteState.done && this.deleteState.error === null;
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<div className="modal-header">
|
|
<h5 className="modal-title">Delete silence</h5>
|
|
<button type="button" className="close" onClick={onHide}>
|
|
<span>×</span>
|
|
</button>
|
|
</div>
|
|
<div className="modal-body">
|
|
{this.deleteState.done ? (
|
|
this.deleteState.error !== null ? (
|
|
<ErrorMessage message={this.deleteState.error} />
|
|
) : (
|
|
<SuccessMessage />
|
|
)
|
|
) : this.deleteState.fetch !== null ? (
|
|
<ProgressMessage />
|
|
) : this.previewState.error === null ? (
|
|
<LabelSetList
|
|
alertStore={alertStore}
|
|
labelsList={this.previewState.alertLabels}
|
|
/>
|
|
) : (
|
|
<ErrorMessage message={this.previewState.error} />
|
|
)}
|
|
{isDone ? null : (
|
|
<div className="d-flex flex-row-reverse">
|
|
<button
|
|
type="button"
|
|
className="btn btn-outline-danger mr-2"
|
|
onClick={this.onDelete}
|
|
disabled={
|
|
this.deleteState.fetch !== null &&
|
|
this.deleteState.done === false
|
|
}
|
|
>
|
|
<FontAwesomeIcon icon={faCheckCircle} className="mr-1" />
|
|
{this.deleteState.fetch !== null &&
|
|
this.deleteState.error !== null
|
|
? "Retry"
|
|
: "Confirm"}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
const DeleteSilence = observer(
|
|
class DeleteSilence extends Component {
|
|
static propTypes = {
|
|
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
|
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
|
cluster: PropTypes.string.isRequired,
|
|
silence: APISilence.isRequired,
|
|
onModalExit: PropTypes.func
|
|
};
|
|
|
|
toggle = observable(
|
|
{
|
|
visible: false,
|
|
toggle() {
|
|
this.visible = !this.visible;
|
|
}
|
|
},
|
|
{ toggle: action.bound }
|
|
);
|
|
|
|
render() {
|
|
const {
|
|
alertStore,
|
|
silenceFormStore,
|
|
cluster,
|
|
silence,
|
|
onModalExit
|
|
} = this.props;
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<button
|
|
className="btn btn-outline-danger btn-sm"
|
|
onClick={this.toggle.toggle}
|
|
>
|
|
<FontAwesomeIcon
|
|
className="mr-1 d-none d-sm-inline-block"
|
|
icon={faTrash}
|
|
/>
|
|
Delete
|
|
</button>
|
|
<Modal
|
|
isOpen={this.toggle.visible}
|
|
isUpper={true}
|
|
toggleOpen={this.toggle.toggle}
|
|
onExited={onModalExit}
|
|
>
|
|
<DeleteSilenceModalContent
|
|
alertStore={alertStore}
|
|
silenceFormStore={silenceFormStore}
|
|
cluster={cluster}
|
|
silence={silence}
|
|
onHide={this.toggle.toggle}
|
|
/>
|
|
</Modal>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
export { DeleteSilence, DeleteSilenceModalContent };
|