mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
chore(ui): migrate more components to typescript
This commit is contained in:
committed by
Łukasz Mierzwa
parent
c5d399e3eb
commit
4a16661558
@@ -1,5 +1,13 @@
|
||||
const NewLabelName = (v: string) => `New label: ${v}`;
|
||||
export const NewLabelName = (v: string) => `New label: ${v}`;
|
||||
|
||||
const NewLabelValue = (v: string) => `New value: ${v}`;
|
||||
export const NewLabelValue = (v: string) => `New value: ${v}`;
|
||||
|
||||
export { NewLabelName, NewLabelValue };
|
||||
export interface OptionT {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const StringToOption = (value: string): OptionT => ({
|
||||
label: value,
|
||||
value: value,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
|
||||
import { toJS } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
@@ -12,19 +11,29 @@ 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 { APIAlertGroupT, AlertmanagerSilencePayloadT } from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import {
|
||||
SilenceFormStore,
|
||||
MatchersFromGroup,
|
||||
GenerateAlertmanagerSilenceData,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { useFetchAny } from "Hooks/useFetchAny";
|
||||
import { useFetchAny, UpstreamT } from "Hooks/useFetchAny";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
|
||||
const AlertAck = ({ alertStore, silenceFormStore, group }) => {
|
||||
const [clusters, setClusters] = useState([]);
|
||||
const [upstreams, setUpstreams] = useState([]);
|
||||
interface ClusterT {
|
||||
payload: AlertmanagerSilencePayloadT;
|
||||
clusterName: string;
|
||||
members: string[];
|
||||
}
|
||||
|
||||
const AlertAck: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
group: APIAlertGroupT;
|
||||
}> = ({ alertStore, silenceFormStore, group }) => {
|
||||
const [clusters, setClusters] = useState([] as ClusterT[]);
|
||||
const [upstreams, setUpstreams] = useState([] as UpstreamT[]);
|
||||
const [currentCluster, setCurrentCluster] = useState(0);
|
||||
const [isAcking, setIsAcking] = useState(false);
|
||||
|
||||
@@ -46,15 +55,15 @@ const AlertAck = ({ alertStore, silenceFormStore, group }) => {
|
||||
}
|
||||
|
||||
const alertmanagers = Object.entries(group.alertmanagerCount)
|
||||
.filter(([amName, alertCount]) => alertCount > 0)
|
||||
.filter(([_, alertCount]) => alertCount > 0)
|
||||
.map(([amName, _]) => amName);
|
||||
const clusters = Object.entries(
|
||||
alertStore.data.clustersWithoutReadOnly
|
||||
).filter(([clusterName, clusterMembers]) =>
|
||||
).filter(([_, clusterMembers]) =>
|
||||
alertmanagers.some((m) => clusterMembers.includes(m))
|
||||
);
|
||||
|
||||
let c = [];
|
||||
let c: ClusterT[] = [];
|
||||
for (const [clusterName, clusterMembers] of clusters) {
|
||||
const durationSeconds = toJS(
|
||||
alertStore.settings.values.alertAcknowledgement.durationSeconds
|
||||
@@ -94,7 +103,7 @@ const AlertAck = ({ alertStore, silenceFormStore, group }) => {
|
||||
if (clusters.length) {
|
||||
reset();
|
||||
const cluster = clusters[currentCluster];
|
||||
let u = [];
|
||||
let u: UpstreamT[] = [];
|
||||
cluster.members.forEach((amName) => {
|
||||
const am = alertStore.data.getAlertmanagerByName(amName);
|
||||
if (am !== undefined) {
|
||||
@@ -119,9 +128,9 @@ const AlertAck = ({ alertStore, silenceFormStore, group }) => {
|
||||
}, [alertStore.data, clusters, currentCluster, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
let timer: number;
|
||||
if (!isAcking && error) {
|
||||
timer = setTimeout(() => {
|
||||
timer = window.setTimeout(() => {
|
||||
setUpstreams([]);
|
||||
setIsAcking(false);
|
||||
reset();
|
||||
@@ -168,10 +177,5 @@ const AlertAck = ({ alertStore, silenceFormStore, group }) => {
|
||||
)
|
||||
);
|
||||
};
|
||||
AlertAck.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
||||
group: APIGroup.isRequired,
|
||||
};
|
||||
|
||||
export { AlertAck };
|
||||
@@ -1,11 +1,14 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query";
|
||||
import { PaginatedAlertList } from "Components/PaginatedAlertList";
|
||||
|
||||
const InhibitedByModalContent = ({ alertStore, fingerprints, onHide }) => {
|
||||
const InhibitedByModalContent: FC<{
|
||||
alertStore: AlertStore;
|
||||
fingerprints: string[];
|
||||
onHide: () => void;
|
||||
}> = ({ alertStore, fingerprints, onHide }) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="modal-header">
|
||||
@@ -29,10 +32,5 @@ const InhibitedByModalContent = ({ alertStore, fingerprints, onHide }) => {
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
InhibitedByModalContent.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
fingerprints: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onHide: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { InhibitedByModalContent };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useState, useCallback } from "react";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
@@ -16,7 +15,10 @@ const InhibitedByModalContent = React.lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
const InhibitedByModal = ({ alertStore, fingerprints }) => {
|
||||
const InhibitedByModal: FC<{
|
||||
alertStore: AlertStore;
|
||||
fingerprints: string[];
|
||||
}> = ({ alertStore, fingerprints }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
|
||||
@@ -42,7 +44,6 @@ const InhibitedByModal = ({ alertStore, fingerprints }) => {
|
||||
<InhibitedByModalContent
|
||||
alertStore={alertStore}
|
||||
onHide={() => setIsVisible(false)}
|
||||
isVisible={isVisible}
|
||||
fingerprints={fingerprints}
|
||||
/>
|
||||
</React.Suspense>
|
||||
@@ -50,9 +51,5 @@ const InhibitedByModal = ({ alertStore, fingerprints }) => {
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
InhibitedByModal.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
fingerprints: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
};
|
||||
|
||||
export { InhibitedByModal };
|
||||
@@ -1,9 +1,22 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, {
|
||||
FC,
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
KeyboardEvent,
|
||||
ChangeEvent,
|
||||
} from "react";
|
||||
|
||||
import { useOnClickOutside } from "Hooks/useOnClickOutside";
|
||||
|
||||
const InlineEdit = ({
|
||||
const InlineEdit: FC<{
|
||||
className: string;
|
||||
classNameEditing: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onEnterEditing: () => void;
|
||||
onExitEditing: () => void;
|
||||
}> = ({
|
||||
className,
|
||||
classNameEditing,
|
||||
value,
|
||||
@@ -11,8 +24,8 @@ const InlineEdit = ({
|
||||
onEnterEditing,
|
||||
onExitEditing,
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
const [editedValue, setEditedValue] = useState(null);
|
||||
const ref = useRef(null as null | HTMLInputElement);
|
||||
const [editedValue, setEditedValue] = useState(null as null | string);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const startEditing = () => {
|
||||
@@ -30,11 +43,11 @@ const InlineEdit = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onInput = (event) => {
|
||||
const onInput = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setEditedValue(event.target.value.trim());
|
||||
};
|
||||
|
||||
const onKeyDown = (event) => {
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.keyCode === 13) {
|
||||
if (editedValue) {
|
||||
onChange(editedValue);
|
||||
@@ -75,13 +88,5 @@ const InlineEdit = ({
|
||||
</span>
|
||||
);
|
||||
};
|
||||
InlineEdit.propTypes = {
|
||||
className: PropTypes.string,
|
||||
classNameEditing: PropTypes.string,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onEnterEditing: PropTypes.func,
|
||||
onExitEditing: PropTypes.func,
|
||||
};
|
||||
|
||||
export { InlineEdit };
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useState } from "react";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { APIAlertGroupT } from "Models/APITypes";
|
||||
import { IsMobile } from "Common/Device";
|
||||
import { hashObject } from "Common/Hash";
|
||||
import { StaticLabel } from "Components/Labels/StaticLabel";
|
||||
@@ -9,8 +9,8 @@ import { PageSelect } from "Components/Pagination";
|
||||
|
||||
// take a list of groups and outputs a list of label sets, this ignores
|
||||
// the receiver, so we'll end up with only unique alerts
|
||||
const GroupListToUniqueLabelsList = (groups) => {
|
||||
const alerts = {};
|
||||
const GroupListToUniqueLabelsList = (groups: APIAlertGroupT[]) => {
|
||||
const alerts: { [alertHash: string]: { [labelName: string]: string } } = {};
|
||||
for (const group of groups) {
|
||||
for (const alert of group.alerts) {
|
||||
const alertLabels = Object.assign(
|
||||
@@ -26,7 +26,11 @@ const GroupListToUniqueLabelsList = (groups) => {
|
||||
return Object.values(alerts);
|
||||
};
|
||||
|
||||
const LabelSetList = ({ alertStore, labelsList, title }) => {
|
||||
const LabelSetList: FC<{
|
||||
alertStore: AlertStore;
|
||||
labelsList: { [labelName: string]: string }[];
|
||||
title?: string;
|
||||
}> = ({ alertStore, labelsList, title }) => {
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
|
||||
const maxPerPage = IsMobile() ? 5 : 10;
|
||||
@@ -70,10 +74,5 @@ const LabelSetList = ({ alertStore, labelsList, title }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
LabelSetList.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
labelsList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export { LabelSetList, GroupListToUniqueLabelsList };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
@@ -8,14 +7,17 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclama
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { AlertStore, FilterT } from "Stores/AlertStore";
|
||||
import { QueryOperators } from "Common/Query";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
import { InlineEdit } from "Components/InlineEdit";
|
||||
|
||||
const FilterInputLabel = ({ alertStore, filter }) => {
|
||||
const onChange = (val) => {
|
||||
const FilterInputLabel: FC<{
|
||||
alertStore: AlertStore;
|
||||
filter: FilterT;
|
||||
}> = ({ alertStore, filter }) => {
|
||||
const onChange = (val: string) => {
|
||||
alertStore.status.resume();
|
||||
// if filter is empty string then remove it
|
||||
if (val === "") {
|
||||
@@ -80,17 +82,5 @@ const FilterInputLabel = ({ alertStore, filter }) => {
|
||||
</button>
|
||||
));
|
||||
};
|
||||
FilterInputLabel.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
filter: PropTypes.shape({
|
||||
raw: PropTypes.string,
|
||||
applied: PropTypes.bool,
|
||||
isValid: PropTypes.bool,
|
||||
hits: PropTypes.number,
|
||||
name: PropTypes.string,
|
||||
matcher: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
}),
|
||||
};
|
||||
|
||||
export { FilterInputLabel };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useCallback, MouseEvent } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
@@ -14,7 +13,16 @@ import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
// Same as FilteringLabel but for labels that are counters (usually @state)
|
||||
// and only renders a pill badge with the counter, it doesn't render anything
|
||||
// if the counter is 0
|
||||
const FilteringCounterBadge = observer(
|
||||
const FilteringCounterBadge: FC<{
|
||||
alertStore: AlertStore;
|
||||
name: string;
|
||||
value: string;
|
||||
counter: number;
|
||||
themed: boolean;
|
||||
alwaysVisible: boolean;
|
||||
defaultColor: "light" | "primary";
|
||||
isAppend: boolean;
|
||||
}> = observer(
|
||||
({
|
||||
alertStore,
|
||||
name,
|
||||
@@ -22,13 +30,13 @@ const FilteringCounterBadge = observer(
|
||||
counter,
|
||||
themed,
|
||||
alwaysVisible,
|
||||
defaultColor,
|
||||
isAppend,
|
||||
defaultColor = "light",
|
||||
isAppend = true,
|
||||
}) => {
|
||||
const { ref, props } = useFlashTransition(counter);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(event) => {
|
||||
(event: MouseEvent) => {
|
||||
// left click => apply foo=bar filter
|
||||
// left click + alt => apply foo!=bar filter
|
||||
const operator =
|
||||
@@ -82,19 +90,5 @@ const FilteringCounterBadge = observer(
|
||||
);
|
||||
}
|
||||
);
|
||||
FilteringCounterBadge.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
counter: PropTypes.number.isRequired,
|
||||
themed: PropTypes.bool.isRequired,
|
||||
alwaysVisible: PropTypes.bool,
|
||||
defaultColor: PropTypes.oneOf(["light", "primary"]),
|
||||
isAppend: PropTypes.bool,
|
||||
};
|
||||
FilteringCounterBadge.defaultProps = {
|
||||
defaultColor: "light",
|
||||
isAppend: true,
|
||||
};
|
||||
|
||||
export { FilteringCounterBadge };
|
||||
@@ -1,14 +1,19 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { FC, useCallback, MouseEvent } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { QueryOperators, FormatQuery } from "Common/Query";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
|
||||
const FilteringLabel = ({ alertStore, name, value }) => {
|
||||
const FilteringLabel: FC<{
|
||||
alertStore: AlertStore;
|
||||
name: string;
|
||||
value: string;
|
||||
}> = ({ alertStore, name, value }) => {
|
||||
const handleClick = useCallback(
|
||||
(event) => {
|
||||
(event: MouseEvent) => {
|
||||
// left click => apply foo=bar filter
|
||||
// left click + alt => apply foo!=bar filter
|
||||
const operator =
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
@@ -7,7 +6,12 @@ import { QueryOperators } from "Common/Query";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
|
||||
const HistoryLabel = ({ alertStore, name, matcher, value }) => {
|
||||
const HistoryLabel: FC<{
|
||||
alertStore: AlertStore;
|
||||
name: string;
|
||||
matcher: string;
|
||||
value: string;
|
||||
}> = ({ alertStore, name, matcher, value }) => {
|
||||
const cs = GetClassAndStyle(
|
||||
alertStore,
|
||||
matcher === QueryOperators.Equal ? name : "",
|
||||
@@ -22,11 +26,5 @@ const HistoryLabel = ({ alertStore, name, matcher, value }) => {
|
||||
</span>
|
||||
));
|
||||
};
|
||||
HistoryLabel.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
matcher: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export { HistoryLabel };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useCallback, MouseEvent } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
@@ -10,10 +9,18 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { QueryOperators, FormatQuery } from "Common/Query";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
|
||||
const LabelWithPercent = observer(
|
||||
const LabelWithPercent: FC<{
|
||||
alertStore: AlertStore;
|
||||
name: string;
|
||||
value: string;
|
||||
hits: number;
|
||||
percent: number;
|
||||
offset: number;
|
||||
isActive: boolean;
|
||||
}> = observer(
|
||||
({ alertStore, name, value, hits, percent, offset, isActive }) => {
|
||||
const handleClick = useCallback(
|
||||
(event) => {
|
||||
(event: MouseEvent) => {
|
||||
// left click => apply foo=bar filter
|
||||
// left click + alt => apply foo!=bar filter
|
||||
const operator =
|
||||
@@ -70,8 +77,8 @@ const LabelWithPercent = observer(
|
||||
role="progressbar"
|
||||
style={{ width: offset + "%" }}
|
||||
aria-valuenow={offset}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
@@ -79,22 +86,13 @@ const LabelWithPercent = observer(
|
||||
role="progressbar"
|
||||
style={{ width: percent + "%" }}
|
||||
aria-valuenow={percent}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
LabelWithPercent.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
hits: PropTypes.number.isRequired,
|
||||
percent: PropTypes.number.isRequired,
|
||||
offset: PropTypes.number.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export { LabelWithPercent };
|
||||
@@ -1,11 +1,16 @@
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
|
||||
// Renders a static label element, no click actions, no hover
|
||||
const StaticLabel = ({ alertStore, name, value }) => {
|
||||
const StaticLabel: FC<{
|
||||
alertStore: AlertStore;
|
||||
name: string;
|
||||
value: string;
|
||||
}> = ({ alertStore, name, value }) => {
|
||||
const cs = GetClassAndStyle(alertStore, name, value);
|
||||
|
||||
return useObserver(() => (
|
||||
@@ -1,32 +1,34 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import Select from "react-select";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { OptionT } from "Common/Select";
|
||||
import { Settings, CollapseStateT } from "Stores/Settings";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const AlertGroupCollapseConfiguration = ({ settingsStore }) => {
|
||||
const AlertGroupCollapseConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
if (
|
||||
!Object.values(settingsStore.alertGroupConfig.options)
|
||||
.map((o) => o.value)
|
||||
.includes(settingsStore.alertGroupConfig.config.defaultCollapseState)
|
||||
) {
|
||||
settingsStore.alertGroupConfig.config.defaultCollapseState =
|
||||
settingsStore.alertGroupConfig.options.collapsedOnMobile.value;
|
||||
"collapsedOnMobile";
|
||||
}
|
||||
|
||||
const valueToOption = (val) => {
|
||||
const valueToOption = (val: CollapseStateT): OptionT => {
|
||||
return {
|
||||
label: settingsStore.alertGroupConfig.options[val].label,
|
||||
value: val,
|
||||
};
|
||||
};
|
||||
|
||||
const onCollapseChange = (newValue, actionMeta) => {
|
||||
settingsStore.alertGroupConfig.config.defaultCollapseState = newValue.value;
|
||||
const onCollapseChange = (newValue: CollapseStateT) => {
|
||||
settingsStore.alertGroupConfig.config.defaultCollapseState = newValue;
|
||||
};
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
@@ -41,14 +43,13 @@ const AlertGroupCollapseConfiguration = ({ settingsStore }) => {
|
||||
settingsStore.alertGroupConfig.config.defaultCollapseState
|
||||
)}
|
||||
options={Object.values(settingsStore.alertGroupConfig.options)}
|
||||
onChange={onCollapseChange}
|
||||
onChange={(option) =>
|
||||
onCollapseChange((option as OptionT).value as CollapseStateT)
|
||||
}
|
||||
hideSelectedOptions
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
AlertGroupCollapseConfiguration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { AlertGroupCollapseConfiguration };
|
||||
@@ -1,35 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import InputRange from "react-input-range";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const AlertGroupConfiguration = ({ settingsStore }) => {
|
||||
const [defaultRenderCount, setDefaultRenderCount] = useState(
|
||||
settingsStore.alertGroupConfig.config.defaultRenderCount
|
||||
);
|
||||
|
||||
const onChangeComplete = (value) => {
|
||||
settingsStore.alertGroupConfig.setDefaultRenderCount(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-group mb-0 text-center">
|
||||
<InputRange
|
||||
minValue={1}
|
||||
maxValue={10}
|
||||
step={1}
|
||||
value={defaultRenderCount}
|
||||
id="formControlRange"
|
||||
onChange={setDefaultRenderCount}
|
||||
onChangeComplete={onChangeComplete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
AlertGroupConfiguration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { AlertGroupConfiguration };
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { FC, useState } from "react";
|
||||
|
||||
import InputRange from "react-input-range";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const AlertGroupConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
const [defaultRenderCount, setDefaultRenderCount] = useState(
|
||||
settingsStore.alertGroupConfig.config.defaultRenderCount as number
|
||||
);
|
||||
|
||||
const onChangeComplete = (value: number) => {
|
||||
settingsStore.alertGroupConfig.setDefaultRenderCount(value as number);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-group mb-0 text-center">
|
||||
<InputRange
|
||||
minValue={1}
|
||||
maxValue={10}
|
||||
step={1}
|
||||
value={defaultRenderCount}
|
||||
onChange={(value) => setDefaultRenderCount(value as number)}
|
||||
onChangeComplete={(value) => onChangeComplete(value as number)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { AlertGroupConfiguration };
|
||||
@@ -1,33 +1,34 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import Select from "react-select";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { Settings, SortOrderT } from "Stores/Settings";
|
||||
import { OptionT } from "Common/Select";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { SortLabelName } from "./SortLabelName";
|
||||
|
||||
const AlertGroupSortConfiguration = ({ settingsStore }) => {
|
||||
const AlertGroupSortConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
if (
|
||||
!Object.values(settingsStore.gridConfig.options)
|
||||
.map((o) => o.value)
|
||||
.includes(settingsStore.gridConfig.config.sortOrder)
|
||||
) {
|
||||
settingsStore.gridConfig.config.sortOrder =
|
||||
settingsStore.gridConfig.options.default.value;
|
||||
settingsStore.gridConfig.config.sortOrder = "default";
|
||||
}
|
||||
|
||||
const onSortOrderChange = (newValue, actionMeta) => {
|
||||
settingsStore.gridConfig.config.sortOrder = newValue.value;
|
||||
const onSortOrderChange = (value: SortOrderT) => {
|
||||
settingsStore.gridConfig.config.sortOrder = value;
|
||||
};
|
||||
|
||||
const onSortReverseChange = (event) => {
|
||||
settingsStore.gridConfig.config.reverseSort = event.target.checked;
|
||||
const onSortReverseChange = (value: boolean) => {
|
||||
settingsStore.gridConfig.config.reverseSort = value;
|
||||
};
|
||||
|
||||
const valueToOption = (val) => {
|
||||
const valueToOption = (val: SortOrderT) => {
|
||||
return { label: settingsStore.gridConfig.options[val].label, value: val };
|
||||
};
|
||||
|
||||
@@ -51,7 +52,9 @@ const AlertGroupSortConfiguration = ({ settingsStore }) => {
|
||||
settingsStore.gridConfig.config.sortOrder
|
||||
)}
|
||||
options={Object.values(settingsStore.gridConfig.options)}
|
||||
onChange={onSortOrderChange}
|
||||
onChange={(option) =>
|
||||
onSortOrderChange((option as OptionT).value as SortOrderT)
|
||||
}
|
||||
hideSelectedOptions
|
||||
/>
|
||||
</div>
|
||||
@@ -70,7 +73,7 @@ const AlertGroupSortConfiguration = ({ settingsStore }) => {
|
||||
type="checkbox"
|
||||
value=""
|
||||
checked={settingsStore.gridConfig.config.reverseSort || false}
|
||||
onChange={onSortReverseChange}
|
||||
onChange={(event) => onSortReverseChange(event.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="custom-control-label cursor-pointer mr-3"
|
||||
@@ -85,8 +88,5 @@ const AlertGroupSortConfiguration = ({ settingsStore }) => {
|
||||
</div>
|
||||
));
|
||||
};
|
||||
AlertGroupSortConfiguration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { AlertGroupSortConfiguration };
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const AlertGroupTitleBarColor = ({ settingsStore }) => {
|
||||
const onChange = (event) => {
|
||||
settingsStore.alertGroupConfig.config.colorTitleBar = event.target.checked;
|
||||
const AlertGroupTitleBarColor: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
const onChange = (value: boolean) => {
|
||||
settingsStore.alertGroupConfig.config.colorTitleBar = value;
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
@@ -22,7 +23,7 @@ const AlertGroupTitleBarColor = ({ settingsStore }) => {
|
||||
checked={
|
||||
settingsStore.alertGroupConfig.config.colorTitleBar || false
|
||||
}
|
||||
onChange={onChange}
|
||||
onChange={(event) => onChange(event.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="custom-control-label cursor-pointer mr-3"
|
||||
@@ -35,8 +36,5 @@ const AlertGroupTitleBarColor = ({ settingsStore }) => {
|
||||
</div>
|
||||
));
|
||||
};
|
||||
AlertGroupTitleBarColor.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { AlertGroupTitleBarColor };
|
||||
@@ -1,16 +1,17 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useState } from "react";
|
||||
|
||||
import InputRange from "react-input-range";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const FetchConfiguration = ({ settingsStore }) => {
|
||||
const FetchConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
const [fetchInterval, setFetchInterval] = useState(
|
||||
settingsStore.fetchConfig.config.interval
|
||||
);
|
||||
|
||||
const onChangeComplete = (value) => {
|
||||
const onChangeComplete = (value: number) => {
|
||||
settingsStore.fetchConfig.setInterval(value);
|
||||
};
|
||||
|
||||
@@ -21,16 +22,12 @@ const FetchConfiguration = ({ settingsStore }) => {
|
||||
maxValue={120}
|
||||
step={10}
|
||||
value={fetchInterval}
|
||||
id="formControlRange"
|
||||
formatLabel={(value) => `${value}s`}
|
||||
onChange={setFetchInterval}
|
||||
onChangeComplete={onChangeComplete}
|
||||
onChange={(value) => setFetchInterval(value as number)}
|
||||
onChangeComplete={(value) => onChangeComplete(value as number)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
FetchConfiguration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { FetchConfiguration };
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const FilterBarConfiguration = ({ settingsStore }) => {
|
||||
const onAutohideChange = (event) => {
|
||||
settingsStore.filterBarConfig.config.autohide = event.target.checked;
|
||||
const FilterBarConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
const onAutohideChange = (value: boolean) => {
|
||||
settingsStore.filterBarConfig.config.autohide = value;
|
||||
};
|
||||
return useObserver(() => (
|
||||
<div className="form-group mb-0">
|
||||
@@ -19,7 +20,7 @@ const FilterBarConfiguration = ({ settingsStore }) => {
|
||||
type="checkbox"
|
||||
value=""
|
||||
checked={settingsStore.filterBarConfig.config.autohide || false}
|
||||
onChange={onAutohideChange}
|
||||
onChange={(event) => onAutohideChange(event.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="custom-control-label cursor-pointer mr-3"
|
||||
@@ -32,8 +33,5 @@ const FilterBarConfiguration = ({ settingsStore }) => {
|
||||
</div>
|
||||
));
|
||||
};
|
||||
FilterBarConfiguration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { FilterBarConfiguration };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import Creatable from "react-select/creatable";
|
||||
|
||||
@@ -7,11 +6,14 @@ import { useFetchGet } from "Hooks/useFetchGet";
|
||||
import { FormatBackendURI } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { NewLabelName } from "Common/Select";
|
||||
import { NewLabelName, StringToOption } from "Common/Select";
|
||||
|
||||
const disabledLabel = "Disable multi-grid";
|
||||
|
||||
const valueToOption = (v) => ({ label: v ? v : disabledLabel, value: v });
|
||||
const valueToOption = (v: string) => ({
|
||||
label: v ? v : disabledLabel,
|
||||
value: v,
|
||||
});
|
||||
|
||||
const staticValues = [
|
||||
{ label: disabledLabel, value: "" },
|
||||
@@ -20,7 +22,9 @@ const staticValues = [
|
||||
{ label: "@receiver", value: "@receiver" },
|
||||
];
|
||||
|
||||
const GridLabelName = ({ settingsStore }) => {
|
||||
const GridLabelName: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
const { response } = useFetchGet(FormatBackendURI(`labelNames.json`));
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
@@ -38,10 +42,7 @@ const GridLabelName = ({ settingsStore }) => {
|
||||
response
|
||||
? [
|
||||
...staticValues,
|
||||
...response.map((value) => ({
|
||||
label: value,
|
||||
value: value,
|
||||
})),
|
||||
...response.map((value: string) => StringToOption(value)),
|
||||
]
|
||||
: staticValues
|
||||
}
|
||||
@@ -51,8 +52,5 @@ const GridLabelName = ({ settingsStore }) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
GridLabelName.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { GridLabelName };
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { GridLabelName } from "./GridLabelName";
|
||||
|
||||
const MultiGridConfiguration = ({ settingsStore }) => {
|
||||
const onSortReverseChange = (event) => {
|
||||
settingsStore.multiGridConfig.config.gridSortReverse = event.target.checked;
|
||||
const MultiGridConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
const onSortReverseChange = (value: boolean) => {
|
||||
settingsStore.multiGridConfig.config.gridSortReverse = value;
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
@@ -27,7 +28,7 @@ const MultiGridConfiguration = ({ settingsStore }) => {
|
||||
checked={
|
||||
settingsStore.multiGridConfig.config.gridSortReverse || false
|
||||
}
|
||||
onChange={onSortReverseChange}
|
||||
onChange={(event) => onSortReverseChange(event.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="custom-control-label cursor-pointer mr-3"
|
||||
@@ -41,8 +42,5 @@ const MultiGridConfiguration = ({ settingsStore }) => {
|
||||
</div>
|
||||
));
|
||||
};
|
||||
MultiGridConfiguration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { MultiGridConfiguration };
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import Creatable from "react-select/creatable";
|
||||
|
||||
import { StaticLabels } from "Common/Query";
|
||||
import { OptionT } from "Common/Select";
|
||||
import { FormatBackendURI } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { NewLabelName } from "Common/Select";
|
||||
import { NewLabelName, StringToOption } from "Common/Select";
|
||||
|
||||
const valueToOption = (v) => ({ label: v, value: v });
|
||||
|
||||
const SortLabelName = ({ settingsStore }) => {
|
||||
const SortLabelName: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
const { response } = useFetchGet(FormatBackendURI(`labelNames.json`));
|
||||
|
||||
if (!settingsStore.gridConfig.config.sortLabel) {
|
||||
@@ -27,23 +27,16 @@ const SortLabelName = ({ settingsStore }) => {
|
||||
classNamePrefix="react-select"
|
||||
instanceId="configuration-sort-label"
|
||||
formatCreateLabel={NewLabelName}
|
||||
defaultValue={valueToOption(settingsStore.gridConfig.config.sortLabel)}
|
||||
defaultValue={StringToOption(settingsStore.gridConfig.config.sortLabel)}
|
||||
options={
|
||||
response
|
||||
? response.map((value) => ({
|
||||
label: value,
|
||||
value: value,
|
||||
}))
|
||||
: []
|
||||
response ? response.map((value: string) => StringToOption(value)) : []
|
||||
}
|
||||
onChange={({ value }) => {
|
||||
settingsStore.gridConfig.config.sortLabel = value;
|
||||
onChange={(option) => {
|
||||
settingsStore.gridConfig.config.sortLabel = (option as OptionT)
|
||||
.value as string;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
SortLabelName.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { SortLabelName };
|
||||
@@ -1,32 +1,33 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import Select from "react-select";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { OptionT } from "Common/Select";
|
||||
import { Settings, ThemeT } from "Stores/Settings";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const ThemeConfiguration = ({ settingsStore }) => {
|
||||
const ThemeConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
if (
|
||||
!Object.values(settingsStore.themeConfig.options)
|
||||
.map((o) => o.value)
|
||||
.includes(settingsStore.themeConfig.config.theme)
|
||||
) {
|
||||
settingsStore.themeConfig.config.theme =
|
||||
settingsStore.themeConfig.options.auto.value;
|
||||
settingsStore.themeConfig.config.theme = "auto";
|
||||
}
|
||||
|
||||
const valueToOption = (val) => {
|
||||
const valueToOption = (val: ThemeT) => {
|
||||
return {
|
||||
label: settingsStore.themeConfig.options[val].label,
|
||||
value: val,
|
||||
};
|
||||
};
|
||||
|
||||
const onCollapseChange = (newValue, actionMeta) => {
|
||||
settingsStore.themeConfig.config.theme = newValue.value;
|
||||
const onCollapseChange = (newValue: ThemeT) => {
|
||||
settingsStore.themeConfig.config.theme = newValue;
|
||||
};
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
@@ -39,14 +40,13 @@ const ThemeConfiguration = ({ settingsStore }) => {
|
||||
instanceId="configuration-theme"
|
||||
defaultValue={valueToOption(settingsStore.themeConfig.config.theme)}
|
||||
options={Object.values(settingsStore.themeConfig.options)}
|
||||
onChange={onCollapseChange}
|
||||
onChange={(option) =>
|
||||
onCollapseChange((option as OptionT).value as ThemeT)
|
||||
}
|
||||
hideSelectedOptions
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
ThemeConfiguration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { ThemeConfiguration };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { Accordion } from "Components/Accordion";
|
||||
@@ -13,7 +12,10 @@ import { AlertGroupTitleBarColor } from "./AlertGroupTitleBarColor";
|
||||
import { ThemeConfiguration } from "./ThemeConfiguration";
|
||||
import { MultiGridConfiguration } from "./MultiGridConfiguration";
|
||||
|
||||
const Configuration = ({ settingsStore, defaultIsOpen }) => (
|
||||
const Configuration: FC<{
|
||||
settingsStore: Settings;
|
||||
defaultIsOpen: boolean;
|
||||
}> = ({ settingsStore, defaultIsOpen }) => (
|
||||
<form className="px-3 accordion">
|
||||
<Accordion
|
||||
text="Refresh interval"
|
||||
@@ -64,9 +66,5 @@ const Configuration = ({ settingsStore, defaultIsOpen }) => (
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
Configuration.propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
defaultIsOpen: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export { Configuration };
|
||||
@@ -1,12 +1,14 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
|
||||
|
||||
import { Accordion } from "Components/Accordion";
|
||||
|
||||
const FilterOperatorHelp = ({ operator, description, children }) => (
|
||||
const FilterOperatorHelp: FC<{
|
||||
operator: string;
|
||||
description: string;
|
||||
}> = ({ operator, description, children }) => (
|
||||
<React.Fragment>
|
||||
<dt>
|
||||
<kbd>{operator}</kbd> {description}
|
||||
@@ -24,12 +26,12 @@ const FilterOperatorHelp = ({ operator, description, children }) => (
|
||||
</dd>
|
||||
</React.Fragment>
|
||||
);
|
||||
FilterOperatorHelp.propTypes = {
|
||||
operator: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const QueryHelp = ({ title, operators, warning, children }) => (
|
||||
const QueryHelp: FC<{
|
||||
title: string;
|
||||
operators: string[];
|
||||
warning?: ReactNode;
|
||||
}> = ({ title, operators, warning, children }) => (
|
||||
<React.Fragment>
|
||||
<dt>{title}</dt>
|
||||
<dd className="mb-5">
|
||||
@@ -52,13 +54,10 @@ const QueryHelp = ({ title, operators, warning, children }) => (
|
||||
</dd>
|
||||
</React.Fragment>
|
||||
);
|
||||
QueryHelp.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
operators: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
warning: PropTypes.node,
|
||||
};
|
||||
|
||||
const FilterExample = ({ example, children }) => (
|
||||
const FilterExample: FC<{
|
||||
example: string;
|
||||
}> = ({ example, children }) => (
|
||||
<li>
|
||||
<div>
|
||||
<span className="badge badge-info">{example}</span>
|
||||
@@ -66,12 +65,8 @@ const FilterExample = ({ example, children }) => (
|
||||
<div>{children}</div>
|
||||
</li>
|
||||
);
|
||||
FilterExample.propTypes = {
|
||||
example: PropTypes.string.isRequired,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
const Help = ({ defaultIsOpen }) => (
|
||||
const Help: FC<{ defaultIsOpen: boolean }> = ({ defaultIsOpen }) => (
|
||||
<div className="accordion">
|
||||
<Accordion
|
||||
text="Fiter operators"
|
||||
@@ -319,8 +314,5 @@ const Help = ({ defaultIsOpen }) => (
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
Help.propTypes = {
|
||||
defaultIsOpen: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export { Help };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useState } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
@@ -9,16 +8,19 @@ import { Tab } from "Components/Modal/Tab";
|
||||
import { Configuration } from "./Configuration";
|
||||
import { Help } from "./Help";
|
||||
|
||||
const TabNames = Object.freeze({
|
||||
Configuration: "configuration",
|
||||
Help: "help",
|
||||
});
|
||||
export type OpenTabT = "configuration" | "help";
|
||||
|
||||
const MainModalContent = ({
|
||||
const MainModalContent: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
onHide: () => void;
|
||||
openTab?: OpenTabT;
|
||||
expandAllOptions: boolean;
|
||||
}> = ({
|
||||
alertStore,
|
||||
settingsStore,
|
||||
onHide,
|
||||
openTab,
|
||||
openTab = "configuration",
|
||||
expandAllOptions,
|
||||
}) => {
|
||||
const [tab, setTab] = useState(openTab);
|
||||
@@ -29,13 +31,13 @@ const MainModalContent = ({
|
||||
<nav className="nav nav-pills nav-justified w-100">
|
||||
<Tab
|
||||
title="Configuration"
|
||||
active={tab === TabNames.Configuration}
|
||||
onClick={() => setTab(TabNames.Configuration)}
|
||||
active={tab === "configuration"}
|
||||
onClick={() => setTab("configuration")}
|
||||
/>
|
||||
<Tab
|
||||
title="Help"
|
||||
active={tab === TabNames.Help}
|
||||
onClick={() => setTab(TabNames.Help)}
|
||||
active={tab === "help"}
|
||||
onClick={() => setTab("help")}
|
||||
/>
|
||||
<button type="button" className="close" onClick={onHide}>
|
||||
<span>×</span>
|
||||
@@ -43,10 +45,8 @@ const MainModalContent = ({
|
||||
</nav>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{tab === TabNames.Help ? (
|
||||
<Help defaultIsOpen={expandAllOptions} />
|
||||
) : null}
|
||||
{tab === TabNames.Configuration ? (
|
||||
{tab === "help" ? <Help defaultIsOpen={expandAllOptions} /> : null}
|
||||
{tab === "configuration" ? (
|
||||
<Configuration
|
||||
settingsStore={settingsStore}
|
||||
defaultIsOpen={expandAllOptions}
|
||||
@@ -64,15 +64,5 @@ const MainModalContent = ({
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
MainModalContent.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
onHide: PropTypes.func.isRequired,
|
||||
openTab: PropTypes.oneOf(Object.values(TabNames)),
|
||||
expandAllOptions: PropTypes.bool.isRequired,
|
||||
};
|
||||
MainModalContent.defaultProps = {
|
||||
openTab: TabNames.Configuration,
|
||||
};
|
||||
|
||||
export { MainModalContent, TabNames };
|
||||
export { MainModalContent };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useState, useCallback } from "react";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCog } from "@fortawesome/free-solid-svg-icons/faCog";
|
||||
@@ -17,7 +16,10 @@ const MainModalContent = React.lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
const MainModal = ({ alertStore, settingsStore }) => {
|
||||
const MainModal: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, settingsStore }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
|
||||
@@ -51,7 +53,6 @@ const MainModal = ({ alertStore, settingsStore }) => {
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
onHide={() => setIsVisible(false)}
|
||||
isVisible={isVisible}
|
||||
expandAllOptions={false}
|
||||
/>
|
||||
</React.Suspense>
|
||||
@@ -59,9 +60,5 @@ const MainModal = ({ alertStore, settingsStore }) => {
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
MainModal.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { MainModal };
|
||||
@@ -1,131 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { LabelWithPercent } from "Components/Labels/LabelWithPercent";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
|
||||
const TableRows = observer(({ alertStore, nameStats }) =>
|
||||
nameStats.map((nameStats) => (
|
||||
<tr key={nameStats.name}>
|
||||
<td width="25%" className="text-nowrap mw-100 p-1">
|
||||
<span className="badge badge-light components-label mx-0 mt-0 mb-auto pl-0 text-left">
|
||||
<span className="bg-primary text-white mr-1 px-1 components-labelWithPercent-percent">
|
||||
{nameStats.hits}
|
||||
</span>
|
||||
{nameStats.name}
|
||||
</span>
|
||||
</td>
|
||||
<td width="75%" className="mw-100 p-1">
|
||||
{nameStats.values.slice(0, 9).map((valueStats, i) => (
|
||||
<LabelWithPercent
|
||||
alertStore={alertStore}
|
||||
key={valueStats.value}
|
||||
name={nameStats.name}
|
||||
value={valueStats.value}
|
||||
hits={valueStats.hits}
|
||||
percent={valueStats.percent}
|
||||
offset={valueStats.offset}
|
||||
isActive={
|
||||
alertStore.filters.values.filter((f) => f.raw === valueStats.raw)
|
||||
.length > 0
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{nameStats.values.length > 9 ? (
|
||||
<div className="components-label badge my-2 text-muted mw-100">
|
||||
+{nameStats.values.length - 9} more
|
||||
</div>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
);
|
||||
|
||||
const LabelsTable = observer(
|
||||
({ alertStore, showAllLabels, toggleAllLabels }) => (
|
||||
<React.Fragment>
|
||||
<table
|
||||
className="table table-borderless top-labels"
|
||||
style={{ tableLayout: "fixed" }}
|
||||
>
|
||||
<tbody className="mw-100">
|
||||
<TableRows
|
||||
alertStore={alertStore}
|
||||
nameStats={alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits >= alertStore.info.totalAlerts
|
||||
)}
|
||||
></TableRows>
|
||||
{alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits < alertStore.info.totalAlerts
|
||||
).length > 0 ? (
|
||||
<tr>
|
||||
<td colSpan="2" className="px-1 py-0">
|
||||
<TooltipWrapper
|
||||
title="Toggle all / only common labels"
|
||||
delay={[3000, 100]}
|
||||
>
|
||||
<ToggleIcon
|
||||
isOpen={showAllLabels}
|
||||
className="cursor-pointer text-muted"
|
||||
onClick={toggleAllLabels}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
{showAllLabels ? (
|
||||
<TableRows
|
||||
alertStore={alertStore}
|
||||
nameStats={alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits < alertStore.info.totalAlerts
|
||||
)}
|
||||
></TableRows>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
|
||||
const NothingToShow = () => (
|
||||
<div className="jumbotron bg-transparent">
|
||||
<h1 className="display-5 text-placeholder text-center">
|
||||
No labels to display
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
const OverviewModalContent = observer(({ alertStore, onHide }) => {
|
||||
const [showAllLabels, setShowAllLabels] = useState(false);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Overview</h5>
|
||||
<button type="button" className="close" onClick={onHide}>
|
||||
<span className="align-middle">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{alertStore.data.counters.length === 0 ? (
|
||||
<NothingToShow />
|
||||
) : (
|
||||
<LabelsTable
|
||||
alertStore={alertStore}
|
||||
showAllLabels={showAllLabels}
|
||||
toggleAllLabels={() => setShowAllLabels(!showAllLabels)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
OverviewModalContent.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
onHide: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { OverviewModalContent };
|
||||
135
ui/src/Components/OverviewModal/OverviewModalContent.tsx
Normal file
135
ui/src/Components/OverviewModal/OverviewModalContent.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import React, { FC, useState } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { APILabelCounterT } from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { LabelWithPercent } from "Components/Labels/LabelWithPercent";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
|
||||
const TableRows: FC<{
|
||||
alertStore: AlertStore;
|
||||
nameStats: APILabelCounterT[];
|
||||
}> = observer(({ alertStore, nameStats }) => (
|
||||
<React.Fragment>
|
||||
{nameStats.map((nameStats) => (
|
||||
<tr key={nameStats.name}>
|
||||
<td width="25%" className="text-nowrap mw-100 p-1">
|
||||
<span className="badge badge-light components-label mx-0 mt-0 mb-auto pl-0 text-left">
|
||||
<span className="bg-primary text-white mr-1 px-1 components-labelWithPercent-percent">
|
||||
{nameStats.hits}
|
||||
</span>
|
||||
{nameStats.name}
|
||||
</span>
|
||||
</td>
|
||||
<td width="75%" className="mw-100 p-1">
|
||||
{nameStats.values.slice(0, 9).map((valueStats) => (
|
||||
<LabelWithPercent
|
||||
alertStore={alertStore}
|
||||
key={valueStats.value}
|
||||
name={nameStats.name}
|
||||
value={valueStats.value}
|
||||
hits={valueStats.hits}
|
||||
percent={valueStats.percent}
|
||||
offset={valueStats.offset}
|
||||
isActive={
|
||||
alertStore.filters.values.filter(
|
||||
(f) => f.raw === valueStats.raw
|
||||
).length > 0
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{nameStats.values.length > 9 ? (
|
||||
<div className="components-label badge my-2 text-muted mw-100">
|
||||
+{nameStats.values.length - 9} more
|
||||
</div>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</React.Fragment>
|
||||
));
|
||||
|
||||
const LabelsTable: FC<{
|
||||
alertStore: AlertStore;
|
||||
showAllLabels: boolean;
|
||||
toggleAllLabels: () => void;
|
||||
}> = observer(({ alertStore, showAllLabels, toggleAllLabels }) => (
|
||||
<React.Fragment>
|
||||
<table
|
||||
className="table table-borderless top-labels"
|
||||
style={{ tableLayout: "fixed" }}
|
||||
>
|
||||
<tbody className="mw-100">
|
||||
<TableRows
|
||||
alertStore={alertStore}
|
||||
nameStats={alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits >= alertStore.info.totalAlerts
|
||||
)}
|
||||
></TableRows>
|
||||
{alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits < alertStore.info.totalAlerts
|
||||
).length > 0 ? (
|
||||
<tr>
|
||||
<td colSpan={2} className="px-1 py-0">
|
||||
<TooltipWrapper title="Toggle all / only common labels">
|
||||
<ToggleIcon
|
||||
isOpen={showAllLabels}
|
||||
className="cursor-pointer text-muted"
|
||||
onClick={toggleAllLabels}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
{showAllLabels ? (
|
||||
<TableRows
|
||||
alertStore={alertStore}
|
||||
nameStats={alertStore.data.counters.filter(
|
||||
(nameStats) => nameStats.hits < alertStore.info.totalAlerts
|
||||
)}
|
||||
></TableRows>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</React.Fragment>
|
||||
));
|
||||
|
||||
const NothingToShow: FC<{}> = () => (
|
||||
<div className="jumbotron bg-transparent">
|
||||
<h1 className="display-5 text-placeholder text-center">
|
||||
No labels to display
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
const OverviewModalContent: FC<{
|
||||
alertStore: AlertStore;
|
||||
onHide: () => void;
|
||||
}> = observer(({ alertStore, onHide }) => {
|
||||
const [showAllLabels, setShowAllLabels] = useState(false);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Overview</h5>
|
||||
<button type="button" className="close" onClick={onHide}>
|
||||
<span className="align-middle">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{alertStore.data.counters.length === 0 ? (
|
||||
<NothingToShow />
|
||||
) : (
|
||||
<LabelsTable
|
||||
alertStore={alertStore}
|
||||
showAllLabels={showAllLabels}
|
||||
toggleAllLabels={() => setShowAllLabels(!showAllLabels)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
export { OverviewModalContent };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, useState, useCallback } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
@@ -20,7 +19,9 @@ const OverviewModalContent = React.lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
const OverviewModal = ({ alertStore }) => {
|
||||
const OverviewModal: FC<{
|
||||
alertStore: AlertStore;
|
||||
}> = ({ alertStore }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
|
||||
@@ -53,15 +54,11 @@ const OverviewModal = ({ alertStore }) => {
|
||||
<OverviewModalContent
|
||||
alertStore={alertStore}
|
||||
onHide={() => setIsVisible(false)}
|
||||
isVisible={isVisible}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
OverviewModal.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
};
|
||||
|
||||
export { OverviewModal };
|
||||
@@ -31,7 +31,7 @@ const Placeholder = () => (
|
||||
const PaginatedAlertList: FC<{
|
||||
alertStore: AlertStore;
|
||||
filters: string[];
|
||||
title: string;
|
||||
title?: string;
|
||||
}> = ({ alertStore, filters, title }) => {
|
||||
const { response, error, isLoading } = useFetchGet(
|
||||
FormatBackendURI("alerts.json?") + FormatAlertsQ(filters)
|
||||
|
||||
@@ -18,7 +18,7 @@ const PageSelect: FC<{
|
||||
totalItemsCount: number;
|
||||
totalPages: number;
|
||||
maxPerPage: number;
|
||||
initialPage: number;
|
||||
initialPage?: number;
|
||||
setPageCallback: PageCallback;
|
||||
}> = ({
|
||||
totalItemsCount,
|
||||
|
||||
@@ -21,10 +21,10 @@ import {
|
||||
SilenceFormStore,
|
||||
SilenceFormStage,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
NewClusterRequest,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import { QueryOperators } from "Common/Query";
|
||||
import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
@@ -118,10 +118,10 @@ const SilenceForm = ({
|
||||
const matcher = NewEmptyMatcher();
|
||||
matcher.name = f.name;
|
||||
if (f.matcher === QueryOperators.Regex) {
|
||||
matcher.values = [MatcherValueToObject(`.*${f.value}.*`)];
|
||||
matcher.values = [StringToOption(`.*${f.value}.*`)];
|
||||
matcher.isRegex = f.matcher === QueryOperators.Regex;
|
||||
} else {
|
||||
matcher.values = [MatcherValueToObject(f.value)];
|
||||
matcher.values = [StringToOption(f.value)];
|
||||
}
|
||||
silenceFormStore.data.matchers.push(matcher);
|
||||
});
|
||||
|
||||
@@ -5,11 +5,8 @@ import { mount } from "enzyme";
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { MockThemeContext } from "__mocks__/Theme";
|
||||
import {
|
||||
SilenceFormStore,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { SilenceFormStore, NewEmptyMatcher } from "Stores/SilenceFormStore";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
import { LabelValueInput } from "./LabelValueInput";
|
||||
|
||||
@@ -87,8 +84,8 @@ describe("<LabelValueInput />", () => {
|
||||
options.at(0).simulate("click");
|
||||
options.at(1).simulate("click");
|
||||
expect(matcher.values).toHaveLength(2);
|
||||
expect(matcher.values).toContainEqual(MatcherValueToObject("dev"));
|
||||
expect(matcher.values).toContainEqual(MatcherValueToObject("staging"));
|
||||
expect(matcher.values).toContainEqual(StringToOption("dev"));
|
||||
expect(matcher.values).toContainEqual(StringToOption("staging"));
|
||||
});
|
||||
|
||||
it("selecting one option doesn't force matcher.isRegex=true", () => {
|
||||
@@ -121,10 +118,7 @@ describe("<LabelValueInput />", () => {
|
||||
});
|
||||
|
||||
it("removing last value sets matcher.values to []", () => {
|
||||
matcher.values = [
|
||||
MatcherValueToObject("dev"),
|
||||
MatcherValueToObject("staging"),
|
||||
];
|
||||
matcher.values = [StringToOption("dev"), StringToOption("staging")];
|
||||
const tree = MountedLabelValueInput(true);
|
||||
|
||||
tree.find(".react-select__multi-value__remove").at(0).simulate("click");
|
||||
|
||||
@@ -4,12 +4,9 @@ import { mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import {
|
||||
SilenceFormStore,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { SilenceFormStore, NewEmptyMatcher } from "Stores/SilenceFormStore";
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import { MatchCounter } from "./MatchCounter";
|
||||
|
||||
let matcher;
|
||||
@@ -19,7 +16,7 @@ beforeEach(() => {
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
matcher = NewEmptyMatcher();
|
||||
matcher.name = "foo";
|
||||
matcher.values = [MatcherValueToObject("bar")];
|
||||
matcher.values = [StringToOption("bar")];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -107,7 +104,7 @@ describe("<MatchCounter />", () => {
|
||||
});
|
||||
|
||||
it("sends correct query string for a 'foo=~(bar|baz)' matcher", () => {
|
||||
matcher.values = [MatcherValueToObject("bar"), MatcherValueToObject("baz")];
|
||||
matcher.values = [StringToOption("bar"), StringToOption("baz")];
|
||||
matcher.isRegex = true;
|
||||
silenceFormStore.data.alertmanagers = [];
|
||||
MountedMatchCounter();
|
||||
@@ -117,7 +114,7 @@ describe("<MatchCounter />", () => {
|
||||
});
|
||||
|
||||
it("selecting one Alertmanager instance appends it to the filters", () => {
|
||||
silenceFormStore.data.alertmanagers = [MatcherValueToObject("am1")];
|
||||
silenceFormStore.data.alertmanagers = [StringToOption("am1")];
|
||||
MountedMatchCounter();
|
||||
expect(useFetchGet.mock.calls[0][0]).toBe(
|
||||
"./alerts.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28am1%29%24"
|
||||
@@ -126,8 +123,8 @@ describe("<MatchCounter />", () => {
|
||||
|
||||
it("selecting two Alertmanager instances appends it correctly to the filters", () => {
|
||||
silenceFormStore.data.alertmanagers = [
|
||||
MatcherValueToObject("am1"),
|
||||
MatcherValueToObject("am1"),
|
||||
StringToOption("am1"),
|
||||
StringToOption("am1"),
|
||||
];
|
||||
MountedMatchCounter();
|
||||
expect(useFetchGet.mock.calls[0][0]).toBe(
|
||||
|
||||
@@ -2,11 +2,8 @@ import React from "react";
|
||||
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
import {
|
||||
SilenceFormStore,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { SilenceFormStore, NewEmptyMatcher } from "Stores/SilenceFormStore";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import { SilenceMatch } from ".";
|
||||
|
||||
let silenceFormStore;
|
||||
@@ -33,7 +30,7 @@ const ShallowLabelValueInput = () => {
|
||||
|
||||
describe("<SilenceMatch />", () => {
|
||||
it("allows changing matcher.isRegex value when matcher.values contains 1 element", () => {
|
||||
matcher.values = [MatcherValueToObject("foo")];
|
||||
matcher.values = [StringToOption("foo")];
|
||||
const tree = ShallowLabelValueInput();
|
||||
expect(matcher.isRegex).toBe(false);
|
||||
const regex = tree.find("input[type='checkbox']");
|
||||
@@ -43,7 +40,7 @@ describe("<SilenceMatch />", () => {
|
||||
|
||||
it("disallows changing matcher.isRegex value when matcher.values contains 2 elements", () => {
|
||||
matcher.isRegex = true;
|
||||
matcher.values = [MatcherValueToObject("foo"), MatcherValueToObject("bar")];
|
||||
matcher.values = [StringToOption("foo"), StringToOption("bar")];
|
||||
const tree = ShallowLabelValueInput();
|
||||
expect(matcher.isRegex).toBe(true);
|
||||
const regex = tree.find("input[type='checkbox']");
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
SilenceFormStore,
|
||||
SilenceFormStage,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
import { SilencePreview } from ".";
|
||||
|
||||
@@ -24,7 +24,7 @@ beforeEach(() => {
|
||||
|
||||
const matcher = NewEmptyMatcher();
|
||||
matcher.name = "foo";
|
||||
matcher.values = [MatcherValueToObject("bar")];
|
||||
matcher.values = [StringToOption("bar")];
|
||||
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
silenceFormStore.data.matchers = [matcher];
|
||||
|
||||
@@ -13,9 +13,9 @@ import { Settings } from "Stores/Settings";
|
||||
import {
|
||||
SilenceFormStore,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
SilenceTabNames,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import { DateTimeSelect, TabNames } from "./DateTimeSelect";
|
||||
import { SilenceModalContent } from "./SilenceModalContent";
|
||||
|
||||
@@ -24,7 +24,7 @@ import "Styles/Percy.scss";
|
||||
const MockMatcher = (name, values, isRegex) => {
|
||||
const matcher = NewEmptyMatcher();
|
||||
matcher.name = name;
|
||||
matcher.values = values.map((v) => MatcherValueToObject(v));
|
||||
matcher.values = values.map((v) => StringToOption(v));
|
||||
matcher.isRegex = isRegex;
|
||||
return matcher;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import merge from "lodash.merge";
|
||||
|
||||
import { CommonOptions } from "Common/Fetch";
|
||||
|
||||
interface Upstream {
|
||||
export interface UpstreamT {
|
||||
uri: string;
|
||||
options: RequestInit;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ interface ResponseState {
|
||||
inProgress: boolean;
|
||||
}
|
||||
|
||||
const useFetchAny = (upstreams: Upstream[], { fetcher = null } = {}) => {
|
||||
const useFetchAny = (upstreams: UpstreamT[], { fetcher = null } = {}) => {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [response, setResponse] = useState({
|
||||
response: null,
|
||||
|
||||
@@ -143,13 +143,14 @@ export interface APILabelCounterValueT {
|
||||
value: string;
|
||||
raw: string;
|
||||
hits: number;
|
||||
precent: number;
|
||||
percent: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface APILabelCounterT {
|
||||
name: string;
|
||||
values: APILabelCounterValueT[];
|
||||
hits: number;
|
||||
}
|
||||
|
||||
export interface APISettingsT {
|
||||
|
||||
@@ -50,10 +50,10 @@ class FetchConfig {
|
||||
}
|
||||
}
|
||||
|
||||
type collapseStateT = "expanded" | "collapsedOnMobile" | "collapsed";
|
||||
export type CollapseStateT = "expanded" | "collapsedOnMobile" | "collapsed";
|
||||
interface AlertGroupConfigStorage {
|
||||
defaultRenderCount: number;
|
||||
collapseState: collapseStateT;
|
||||
defaultCollapseState: CollapseStateT;
|
||||
colorTitleBar: boolean;
|
||||
}
|
||||
class AlertGroupConfig {
|
||||
@@ -71,7 +71,7 @@ class AlertGroupConfig {
|
||||
|
||||
constructor(
|
||||
renderCount: number,
|
||||
collapseState: collapseStateT,
|
||||
collapseState: CollapseStateT,
|
||||
colorTitleBar: boolean
|
||||
) {
|
||||
this.config = localStored(
|
||||
@@ -105,9 +105,9 @@ class SilenceFormConfig {
|
||||
});
|
||||
}
|
||||
|
||||
type sortOrderT = "default" | "disabled" | "startsAt" | "label";
|
||||
export type SortOrderT = "default" | "disabled" | "startsAt" | "label";
|
||||
interface GridConfigStorage {
|
||||
sortOrder: sortOrderT;
|
||||
sortOrder: SortOrderT;
|
||||
sortLabel: string | null;
|
||||
reverseSort: boolean | null;
|
||||
groupWidth: number;
|
||||
@@ -153,9 +153,9 @@ class FilterBarConfig {
|
||||
}
|
||||
}
|
||||
|
||||
type themeT = "auto" | "light" | "dark";
|
||||
export type ThemeT = "auto" | "light" | "dark";
|
||||
interface ThemeConfigStorage {
|
||||
theme: themeT;
|
||||
theme: ThemeT;
|
||||
}
|
||||
class ThemeConfig {
|
||||
options = Object.freeze({
|
||||
@@ -167,7 +167,7 @@ class ThemeConfig {
|
||||
dark: { label: "Dark theme", value: "dark" },
|
||||
});
|
||||
config: ThemeConfigStorage;
|
||||
constructor(defaultTheme: themeT) {
|
||||
constructor(defaultTheme: ThemeT) {
|
||||
this.config = localStored(
|
||||
"themeConfig",
|
||||
{
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
MockSilence,
|
||||
MockAlertmanager,
|
||||
} from "__mocks__/Alerts.js";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import {
|
||||
SilenceFormStore,
|
||||
SilenceFormStage,
|
||||
NewEmptyMatcher,
|
||||
SilenceTabNames,
|
||||
MatcherValueToObject,
|
||||
AlertmanagerClustersToOption,
|
||||
} from "./SilenceFormStore";
|
||||
|
||||
@@ -418,13 +418,10 @@ describe("SilenceFormStore.data", () => {
|
||||
|
||||
it("dumps to base64 and back", () => {
|
||||
store.data.matchers = [
|
||||
MockMatcher("foo", [MatcherValueToObject("bar")]),
|
||||
MockMatcher("instance", [MatcherValueToObject("server0|server1")]),
|
||||
MockMatcher("cluster", [
|
||||
MatcherValueToObject("prod"),
|
||||
MatcherValueToObject("dev"),
|
||||
]),
|
||||
MockMatcher("job", [MatcherValueToObject("abc.+")]),
|
||||
MockMatcher("foo", [StringToOption("bar")]),
|
||||
MockMatcher("instance", [StringToOption("server0|server1")]),
|
||||
MockMatcher("cluster", [StringToOption("prod"), StringToOption("dev")]),
|
||||
MockMatcher("job", [StringToOption("abc.+")]),
|
||||
];
|
||||
store.data.startsAt = new Date();
|
||||
store.data.endsAt = addMinutes(addHours(store.data.startsAt, 7), 45);
|
||||
@@ -439,22 +436,22 @@ describe("SilenceFormStore.data", () => {
|
||||
{
|
||||
isRegex: false,
|
||||
name: "foo",
|
||||
values: [MatcherValueToObject("bar")],
|
||||
values: [StringToOption("bar")],
|
||||
},
|
||||
{
|
||||
isRegex: false,
|
||||
name: "instance",
|
||||
values: [MatcherValueToObject("server0|server1")],
|
||||
values: [StringToOption("server0|server1")],
|
||||
},
|
||||
{
|
||||
isRegex: false,
|
||||
name: "cluster",
|
||||
values: [MatcherValueToObject("prod"), MatcherValueToObject("dev")],
|
||||
values: [StringToOption("prod"), StringToOption("dev")],
|
||||
},
|
||||
{
|
||||
isRegex: false,
|
||||
name: "job",
|
||||
values: [MatcherValueToObject("abc.+")],
|
||||
values: [StringToOption("abc.+")],
|
||||
},
|
||||
]);
|
||||
expect(store.data.comment).toBe("base64");
|
||||
|
||||
@@ -16,24 +16,20 @@ import {
|
||||
APIAlertmanagerUpstreamT,
|
||||
AlertmanagerSilencePayloadT,
|
||||
} from "Models/APITypes";
|
||||
import { StringToOption, OptionT } from "Common/Select";
|
||||
|
||||
interface OptionT {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface MultiValueOptionT {
|
||||
export interface MultiValueOptionT {
|
||||
label: string;
|
||||
value: string[];
|
||||
}
|
||||
|
||||
interface MatcherT {
|
||||
export interface MatcherT {
|
||||
name: string;
|
||||
values: OptionT[];
|
||||
isRegex: boolean;
|
||||
}
|
||||
|
||||
interface MatcherWithIDT extends MatcherT {
|
||||
export interface MatcherWithIDT extends MatcherT {
|
||||
id: string;
|
||||
}
|
||||
|
||||
@@ -59,11 +55,6 @@ const NewEmptyMatcher = (): MatcherWithIDT => {
|
||||
};
|
||||
};
|
||||
|
||||
const MatcherValueToObject = (value: string): OptionT => ({
|
||||
label: value,
|
||||
value: value,
|
||||
});
|
||||
|
||||
const AlertmanagerClustersToOption = (clusterDict: {
|
||||
[key: string]: string[];
|
||||
}): MultiValueOptionT[] =>
|
||||
@@ -101,7 +92,7 @@ const MatchersFromGroup = (
|
||||
if (!stripLabels.includes(key)) {
|
||||
const matcher = NewEmptyMatcher();
|
||||
matcher.name = key;
|
||||
matcher.values = [MatcherValueToObject(value)];
|
||||
matcher.values = [StringToOption(value)];
|
||||
matchers.push(matcher);
|
||||
}
|
||||
}
|
||||
@@ -150,7 +141,7 @@ const MatchersFromGroup = (
|
||||
name: key,
|
||||
values: Array.from(values)
|
||||
.sort()
|
||||
.map((value) => MatcherValueToObject(value)),
|
||||
.map((value) => StringToOption(value)),
|
||||
isRegex: values.size > 1,
|
||||
});
|
||||
}
|
||||
@@ -173,7 +164,7 @@ const GenerateAlertmanagerSilenceData = (
|
||||
matchers: MatcherT[],
|
||||
author: string,
|
||||
comment: string,
|
||||
silenceID: string | null
|
||||
silenceID: string | null = null
|
||||
): AlertmanagerSilencePayloadT => {
|
||||
const payload: AlertmanagerSilencePayloadT = {
|
||||
matchers: matchers.map((m) => ({
|
||||
@@ -202,11 +193,11 @@ const UnpackRegexMatcherValues = (isRegex: boolean, value: string) => {
|
||||
return value
|
||||
.slice(1, -1)
|
||||
.split("|")
|
||||
.map((v) => MatcherValueToObject(v));
|
||||
.map((v) => StringToOption(v));
|
||||
} else if (isRegex && value.match(/^(\w+\|)+\w+$/)) {
|
||||
return value.split("|").map((v) => MatcherValueToObject(v));
|
||||
return value.split("|").map((v) => StringToOption(v));
|
||||
} else {
|
||||
return [MatcherValueToObject(value)];
|
||||
return [StringToOption(value)];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -299,7 +290,7 @@ class SilenceFormStore {
|
||||
const matcher = NewEmptyMatcher();
|
||||
matcher.name = m.n;
|
||||
matcher.isRegex = m.r;
|
||||
matcher.values = m.v.map((v) => MatcherValueToObject(v));
|
||||
matcher.values = m.v.map((v) => StringToOption(v));
|
||||
matchers.push(matcher);
|
||||
});
|
||||
|
||||
@@ -503,7 +494,6 @@ export {
|
||||
SilenceFormStore,
|
||||
SilenceFormStage,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
AlertmanagerClustersToOption,
|
||||
SilenceTabNames,
|
||||
MatchersFromGroup,
|
||||
|
||||
Reference in New Issue
Block a user