mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
fix(ui): migrate to mobx-react-lite v3
This commit is contained in:
committed by
Łukasz Mierzwa
parent
84fc3e2fe4
commit
fd155db464
+100
-111
@@ -5,7 +5,7 @@ import React, {
|
||||
useCallback,
|
||||
} from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// no types, see react-app-env.d.ts
|
||||
import { useMediaPredicate } from "react-media-hook";
|
||||
@@ -25,130 +25,119 @@ import "Styles/ResetCSS.scss";
|
||||
import "Styles/FontBundle.scss";
|
||||
import "Styles/App.scss";
|
||||
|
||||
// https://github.com/facebook/react/issues/14603
|
||||
const Grid = React.lazy(() =>
|
||||
import("Components/Grid").then((module) => ({
|
||||
default: module.Grid,
|
||||
}))
|
||||
);
|
||||
const NavBar = React.lazy(() =>
|
||||
import("Components/NavBar").then((module) => ({
|
||||
default: module.NavBar,
|
||||
}))
|
||||
);
|
||||
const FaviconBadge = React.lazy(() =>
|
||||
import("Components/FaviconBadge").then((module) => ({
|
||||
default: module.FaviconBadge,
|
||||
}))
|
||||
);
|
||||
const AppToasts = React.lazy(() =>
|
||||
import("Components/Toast/AppToasts").then((module) => ({
|
||||
default: module.AppToasts,
|
||||
}))
|
||||
);
|
||||
const Grid = React.lazy(() => import("Components/Grid"));
|
||||
const NavBar = React.lazy(() => import("Components/NavBar"));
|
||||
const FaviconBadge = React.lazy(() => import("Components/FaviconBadge"));
|
||||
const AppToasts = React.lazy(() => import("Components/Toast/AppToasts"));
|
||||
|
||||
interface AppProps {
|
||||
defaultFilters: Array<string>;
|
||||
uiDefaults: UIDefaults | null;
|
||||
}
|
||||
|
||||
const App: FunctionComponent<AppProps> = ({ defaultFilters, uiDefaults }) => {
|
||||
const [alertStore] = useState<AlertStore>(new AlertStore(null));
|
||||
const [silenceFormStore] = useState<SilenceFormStore>(new SilenceFormStore());
|
||||
const [settingsStore] = useState<Settings>(new Settings(uiDefaults));
|
||||
const App: FunctionComponent<AppProps> = observer(
|
||||
({ defaultFilters, uiDefaults }) => {
|
||||
const [alertStore] = useState<AlertStore>(new AlertStore(null));
|
||||
const [silenceFormStore] = useState<SilenceFormStore>(
|
||||
new SilenceFormStore()
|
||||
);
|
||||
const [settingsStore] = useState<Settings>(new Settings(uiDefaults));
|
||||
|
||||
useEffect(() => {
|
||||
let filters;
|
||||
// parse and decode request query args
|
||||
const p: {
|
||||
params: {
|
||||
q: string[];
|
||||
m?: string;
|
||||
};
|
||||
defaultsUsed: boolean;
|
||||
} = DecodeLocationSearch(window.location.search);
|
||||
// p.defaultsUsed means that karma URI didn't have ?q=foo query args
|
||||
if (p.defaultsUsed) {
|
||||
// no ?q=foo set, use defaults saved by the user or from backend config
|
||||
if (settingsStore.savedFilters.config.present) {
|
||||
filters = settingsStore.savedFilters.config.filters;
|
||||
useEffect(() => {
|
||||
let filters;
|
||||
// parse and decode request query args
|
||||
const p: {
|
||||
params: {
|
||||
q: string[];
|
||||
m?: string;
|
||||
};
|
||||
defaultsUsed: boolean;
|
||||
} = DecodeLocationSearch(window.location.search);
|
||||
// p.defaultsUsed means that karma URI didn't have ?q=foo query args
|
||||
if (p.defaultsUsed) {
|
||||
// no ?q=foo set, use defaults saved by the user or from backend config
|
||||
if (settingsStore.savedFilters.config.present) {
|
||||
filters = settingsStore.savedFilters.config.filters;
|
||||
} else {
|
||||
filters = defaultFilters;
|
||||
}
|
||||
} else {
|
||||
filters = defaultFilters;
|
||||
// user passed ?q=foo, use it as initial filters
|
||||
filters = p.params.q;
|
||||
}
|
||||
} else {
|
||||
// user passed ?q=foo, use it as initial filters
|
||||
filters = p.params.q;
|
||||
}
|
||||
alertStore.filters.setFilters(filters);
|
||||
alertStore.filters.setFilters(filters);
|
||||
|
||||
if (p.params.m && silenceFormStore.data.fromBase64(p.params.m)) {
|
||||
silenceFormStore.toggle.show();
|
||||
}
|
||||
}, [alertStore, defaultFilters, settingsStore]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
if (p.params.m && silenceFormStore.data.fromBase64(p.params.m)) {
|
||||
silenceFormStore.toggle.show();
|
||||
}
|
||||
}, [alertStore, defaultFilters, settingsStore]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const onPopState = useCallback(
|
||||
(event: PopStateEvent) => {
|
||||
event.preventDefault();
|
||||
const p = DecodeLocationSearch(window.location.search);
|
||||
alertStore.filters.setWithoutLocation(p.params.q);
|
||||
},
|
||||
[alertStore]
|
||||
);
|
||||
const onPopState = useCallback(
|
||||
(event: PopStateEvent) => {
|
||||
event.preventDefault();
|
||||
const p = DecodeLocationSearch(window.location.search);
|
||||
alertStore.filters.setWithoutLocation(p.params.q);
|
||||
},
|
||||
[alertStore]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.onpopstate = onPopState;
|
||||
return () => {
|
||||
window.onpopstate = () => {};
|
||||
};
|
||||
}, [onPopState]);
|
||||
useEffect(() => {
|
||||
window.onpopstate = onPopState;
|
||||
return () => {
|
||||
window.onpopstate = () => {};
|
||||
};
|
||||
}, [onPopState]);
|
||||
|
||||
const prefersColorScheme = useMediaPredicate("(prefers-color-scheme)");
|
||||
const prefersDark = useMediaPredicate("(prefers-color-scheme: dark)");
|
||||
const prefersColorScheme = useMediaPredicate("(prefers-color-scheme)");
|
||||
const prefersDark = useMediaPredicate("(prefers-color-scheme: dark)");
|
||||
|
||||
return useObserver(() => (
|
||||
<ErrorBoundary>
|
||||
<span data-theme={`${settingsStore.themeConfig.config.theme}`} />
|
||||
<ThemeContext.Provider
|
||||
value={{
|
||||
isDark:
|
||||
settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.auto.value && prefersColorScheme
|
||||
? prefersDark
|
||||
: settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.dark.value,
|
||||
reactSelectStyles:
|
||||
settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.auto.value && prefersColorScheme
|
||||
? prefersDark
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<span data-theme={`${settingsStore.themeConfig.config.theme}`} />
|
||||
<ThemeContext.Provider
|
||||
value={{
|
||||
isDark:
|
||||
settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.auto.value &&
|
||||
prefersColorScheme
|
||||
? prefersDark
|
||||
: settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.dark.value,
|
||||
reactSelectStyles:
|
||||
settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.auto.value &&
|
||||
prefersColorScheme
|
||||
? prefersDark
|
||||
? ReactSelectStyles(ReactSelectColors.Dark)
|
||||
: ReactSelectStyles(ReactSelectColors.Light)
|
||||
: settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.dark.value
|
||||
? ReactSelectStyles(ReactSelectColors.Dark)
|
||||
: ReactSelectStyles(ReactSelectColors.Light)
|
||||
: settingsStore.themeConfig.config.theme ===
|
||||
settingsStore.themeConfig.options.dark.value
|
||||
? ReactSelectStyles(ReactSelectColors.Dark)
|
||||
: ReactSelectStyles(ReactSelectColors.Light),
|
||||
animations: {
|
||||
duration: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<BodyTheme />
|
||||
<React.Suspense fallback={null}>
|
||||
<NavBar
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
<Grid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
<AppToasts alertStore={alertStore} />
|
||||
<FaviconBadge alertStore={alertStore} />
|
||||
</React.Suspense>
|
||||
</ThemeContext.Provider>
|
||||
</ErrorBoundary>
|
||||
));
|
||||
};
|
||||
: ReactSelectStyles(ReactSelectColors.Light),
|
||||
animations: {
|
||||
duration: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<BodyTheme />
|
||||
<React.Suspense fallback={null}>
|
||||
<NavBar
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
<Grid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
<AppToasts alertStore={alertStore} />
|
||||
<FaviconBadge alertStore={alertStore} />
|
||||
</React.Suspense>
|
||||
</ThemeContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { App };
|
||||
|
||||
@@ -103,7 +103,7 @@ describe("<AlertAck />", () => {
|
||||
it("is null when acks are disabled", () => {
|
||||
alertStore.settings.values.alertAcknowledgement.enabled = false;
|
||||
const tree = MountedAlertAck();
|
||||
expect(tree.html()).toBeNull();
|
||||
expect(tree.html()).toBe("");
|
||||
});
|
||||
|
||||
it("uses faCheck icon when idle", () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
|
||||
import { toJS } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import addSeconds from "date-fns/addSeconds";
|
||||
|
||||
@@ -35,7 +35,7 @@ const AlertAck: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
group: APIAlertGroupT;
|
||||
}> = ({ alertStore, silenceFormStore, group }) => {
|
||||
}> = observer(({ alertStore, silenceFormStore, group }) => {
|
||||
const [clusters, setClusters] = useState<ClusterT[]>([]);
|
||||
const [upstreams, setUpstreams] = useState<UpstreamT[]>([]);
|
||||
const [currentCluster, setCurrentCluster] = useState<number>(0);
|
||||
@@ -145,43 +145,42 @@ const AlertAck: FC<{
|
||||
return () => clearTimeout(timer);
|
||||
}, [isAcking, error, reset]);
|
||||
|
||||
return useObserver(() =>
|
||||
alertStore.settings.values.alertAcknowledgement.enabled === false ? null : (
|
||||
<TooltipWrapper
|
||||
title={
|
||||
return alertStore.settings.values.alertAcknowledgement.enabled ===
|
||||
false ? null : (
|
||||
<TooltipWrapper
|
||||
title={
|
||||
!isAcking && error
|
||||
? error
|
||||
: "Acknowledge this alert with a short lived silence"
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={`badge badge-pill components-label components-label-with-hover px-2 ${
|
||||
!isAcking && error
|
||||
? error
|
||||
: "Acknowledge this alert with a short lived silence"
|
||||
}
|
||||
? "badge-warning"
|
||||
: !isAcking && response
|
||||
? "badge-success"
|
||||
: "badge-secondary"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (!isAcking && !(response || error)) {
|
||||
setIsAcking(true);
|
||||
onACK();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`badge badge-pill components-label components-label-with-hover px-2 ${
|
||||
!isAcking && error
|
||||
? "badge-warning"
|
||||
: !isAcking && response
|
||||
? "badge-success"
|
||||
: "badge-secondary"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (!isAcking && !(response || error)) {
|
||||
setIsAcking(true);
|
||||
onACK();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!isAcking && error ? (
|
||||
<FontAwesomeIcon icon={faExclamationCircle} fixedWidth />
|
||||
) : !isAcking && response ? (
|
||||
<FontAwesomeIcon icon={faCheckCircle} fixedWidth />
|
||||
) : isAcking ? (
|
||||
<FontAwesomeIcon icon={faSpinner} fixedWidth spin />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} fixedWidth />
|
||||
)}
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)
|
||||
{!isAcking && error ? (
|
||||
<FontAwesomeIcon icon={faExclamationCircle} fixedWidth />
|
||||
) : !isAcking && response ? (
|
||||
<FontAwesomeIcon icon={faCheckCircle} fixedWidth />
|
||||
) : isAcking ? (
|
||||
<FontAwesomeIcon icon={faSpinner} fixedWidth spin />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} fixedWidth />
|
||||
)}
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { AlertAck };
|
||||
|
||||
@@ -5,7 +5,7 @@ import { mount } from "enzyme";
|
||||
import Favico from "favico.js";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { FaviconBadge } from ".";
|
||||
import FaviconBadge from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
|
||||
|
||||
@@ -31,4 +31,4 @@ const FaviconBadge: FC<{
|
||||
return null;
|
||||
};
|
||||
|
||||
export { FaviconBadge };
|
||||
export default FaviconBadge;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useRef, useState, FC } from "react";
|
||||
|
||||
import { reaction } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import addSeconds from "date-fns/addSeconds";
|
||||
import differenceInSeconds from "date-fns/differenceInSeconds";
|
||||
@@ -59,43 +59,42 @@ const PlayButton: FC<{ alertStore: AlertStore }> = ({ alertStore }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Dots: FC<{ alertStore: AlertStore; dots: number }> = ({
|
||||
alertStore,
|
||||
dots,
|
||||
}) => {
|
||||
return useObserver(() => (
|
||||
<div
|
||||
className={`cursor-pointer components-fetcher ${
|
||||
alertStore.info.isRetrying ? "retrying" : ""
|
||||
} ${
|
||||
alertStore.status.value.toString() ===
|
||||
AlertStoreStatuses.Processing.toString()
|
||||
? "processing"
|
||||
: ""
|
||||
} ${
|
||||
dots === 0 ||
|
||||
alertStore.status.value.toString() ===
|
||||
AlertStoreStatuses.Fetching.toString()
|
||||
? "fetching"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{Array.from(Array(9).keys()).map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`dot ${i === 4 ? "dot-middle" : ""} ${
|
||||
i < dots ? "visible" : "hidden"
|
||||
}`}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
));
|
||||
};
|
||||
const Dots: FC<{ alertStore: AlertStore; dots: number }> = observer(
|
||||
({ alertStore, dots }) => {
|
||||
return (
|
||||
<div
|
||||
className={`cursor-pointer components-fetcher ${
|
||||
alertStore.info.isRetrying ? "retrying" : ""
|
||||
} ${
|
||||
alertStore.status.value.toString() ===
|
||||
AlertStoreStatuses.Processing.toString()
|
||||
? "processing"
|
||||
: ""
|
||||
} ${
|
||||
dots === 0 ||
|
||||
alertStore.status.value.toString() ===
|
||||
AlertStoreStatuses.Fetching.toString()
|
||||
? "fetching"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{Array.from(Array(9).keys()).map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`dot ${i === 4 ? "dot-middle" : ""} ${
|
||||
i < dots ? "visible" : "hidden"
|
||||
}`}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const Fetcher: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, settingsStore }) => {
|
||||
}> = observer(({ alertStore, settingsStore }) => {
|
||||
const timer = useRef<number | undefined>(undefined);
|
||||
const [percentLeft, setPercentLeft] = useState<number>(100);
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
@@ -233,7 +232,7 @@ const Fetcher: FC<{
|
||||
|
||||
const dots = Math.max(0, Math.min(9, percentLeft / 10));
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div
|
||||
className="navbar-brand py-0 mr-2 d-none d-sm-block"
|
||||
onMouseEnter={() => setIsHover(true)}
|
||||
@@ -247,7 +246,7 @@ const Fetcher: FC<{
|
||||
<Dots alertStore={alertStore} dots={dots} />
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { Fetcher, Dots, PlayButton, PauseButton };
|
||||
|
||||
@@ -7,7 +7,7 @@ import React, {
|
||||
useCallback,
|
||||
} from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { Manager, Reference, Popper } from "react-popper";
|
||||
|
||||
@@ -128,61 +128,63 @@ const AlertMenu: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
setIsMenuOpen: (isOpen: boolean) => void;
|
||||
}> = ({ group, alert, alertStore, silenceFormStore, setIsMenuOpen }) => {
|
||||
const [isHidden, setIsHidden] = useState<boolean>(true);
|
||||
}> = observer(
|
||||
({ group, alert, alertStore, silenceFormStore, setIsMenuOpen }) => {
|
||||
const [isHidden, setIsHidden] = useState<boolean>(true);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
setIsMenuOpen(isHidden);
|
||||
setIsHidden(!isHidden);
|
||||
}, [isHidden, setIsMenuOpen]);
|
||||
const toggle = useCallback(() => {
|
||||
setIsMenuOpen(isHidden);
|
||||
setIsHidden(!isHidden);
|
||||
}, [isHidden, setIsMenuOpen]);
|
||||
|
||||
const hide = useCallback(() => {
|
||||
setIsHidden(true);
|
||||
setIsMenuOpen(false);
|
||||
}, [setIsMenuOpen]);
|
||||
const hide = useCallback(() => {
|
||||
setIsHidden(true);
|
||||
setIsMenuOpen(false);
|
||||
}, [setIsMenuOpen]);
|
||||
|
||||
const rootRef = useRef<HTMLSpanElement | null>(null);
|
||||
useOnClickOutside(rootRef, hide, !isHidden);
|
||||
const rootRef = useRef<HTMLSpanElement | null>(null);
|
||||
useOnClickOutside(rootRef, hide, !isHidden);
|
||||
|
||||
return useObserver(() => (
|
||||
<span ref={rootRef}>
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
className="components-label components-label-with-hover px-1 mr-1 badge badge-secondary cursor-pointer"
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="pr-1"
|
||||
style={{ width: "0.8rem" }}
|
||||
icon={faCaretDown}
|
||||
/>
|
||||
<DateFromNow timestamp={alert.startsAt} />
|
||||
</span>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={!isHidden} unmountOnExit>
|
||||
<Popper placement="bottom-start" modifiers={PopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<MenuContent
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
group={group}
|
||||
alert={alert}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
afterClick={hide}
|
||||
/>
|
||||
return (
|
||||
<span ref={rootRef}>
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
className="components-label components-label-with-hover px-1 mr-1 badge badge-secondary cursor-pointer"
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="pr-1"
|
||||
style={{ width: "0.8rem" }}
|
||||
icon={faCaretDown}
|
||||
/>
|
||||
<DateFromNow timestamp={alert.startsAt} />
|
||||
</span>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
</span>
|
||||
));
|
||||
};
|
||||
</Reference>
|
||||
<DropdownSlide in={!isHidden} unmountOnExit>
|
||||
<Popper placement="bottom-start" modifiers={PopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<MenuContent
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
group={group}
|
||||
alert={alert}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
afterClick={hide}
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { AlertMenu, MenuContent };
|
||||
|
||||
@@ -19,7 +19,7 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { BorderClassMap } from "Common/Colors";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { Alert } from ".";
|
||||
import Alert from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import {
|
||||
APIAlertT,
|
||||
@@ -11,7 +11,7 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { BorderClassMap } from "Common/Colors";
|
||||
import { StaticLabels } from "Common/Query";
|
||||
import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
import FilteringLabel from "Components/Labels/FilteringLabel";
|
||||
import { InhibitedByModal } from "Components/InhibitedByModal";
|
||||
import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation";
|
||||
import { AlertMenu } from "./AlertMenu";
|
||||
@@ -81,7 +81,7 @@ const Alert: FC<{
|
||||
}
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<li className={classNames.join(" ")}>
|
||||
<div>
|
||||
{alert.annotations
|
||||
@@ -149,7 +149,7 @@ const Alert: FC<{
|
||||
))
|
||||
)}
|
||||
</li>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { Alert };
|
||||
export default observer(Alert);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { MockThemeContext } from "__mocks__/Theme";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { GroupFooter } from ".";
|
||||
import GroupFooter from ".";
|
||||
|
||||
let group: APIAlertGroupT;
|
||||
let alertStore: AlertStore;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { APIAlertGroupT } from "Models/APITypes";
|
||||
import { StaticLabels } from "Common/Query";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
import FilteringLabel from "Components/Labels/FilteringLabel";
|
||||
import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation";
|
||||
import { RenderSilence } from "../Silences";
|
||||
|
||||
@@ -17,7 +17,7 @@ const GroupFooter: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ group, alertmanagers, afterUpdate, alertStore, silenceFormStore }) => {
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="card-footer components-grid-alertgrid-alertgroup-footer px-2 py-1">
|
||||
<div className="mb-1">
|
||||
{group.shared.annotations
|
||||
@@ -77,7 +77,7 @@ const GroupFooter: FC<{
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { GroupFooter };
|
||||
export default observer(GroupFooter);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { FC, MouseEvent } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { APIAlertGroupT } from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
|
||||
import FilteringLabel from "Components/Labels/FilteringLabel";
|
||||
import FilteringCounterBadge from "Components/Labels/FilteringCounterBadge";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { AlertAck } from "Components/AlertAck";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
@@ -47,7 +47,7 @@ const GroupHeader: FC<{
|
||||
}
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<h5
|
||||
className={`card-header mb-0 d-flex flex-row p-1 ${
|
||||
isCollapsed ? "border-bottom-0" : ""
|
||||
@@ -113,7 +113,7 @@ const GroupHeader: FC<{
|
||||
</span>
|
||||
</span>
|
||||
</h5>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { GroupHeader };
|
||||
export default observer(GroupHeader);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings, CollapseStateT } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { AlertGroup } from ".";
|
||||
import AlertGroup from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let settingsStore: Settings;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useEffect, useCallback, useState, ReactNode } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -13,9 +13,9 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { BackgroundClassMap } from "Common/Colors";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { GroupHeader } from "./GroupHeader";
|
||||
import { Alert } from "./Alert";
|
||||
import { GroupFooter } from "./GroupFooter";
|
||||
import GroupHeader from "./GroupHeader";
|
||||
import Alert from "./Alert";
|
||||
import GroupFooter from "./GroupFooter";
|
||||
import { DefaultDetailsCollapseValue } from "./DetailsToggle";
|
||||
|
||||
const LoadButton: FC<{
|
||||
@@ -168,7 +168,7 @@ const AlertGroup: FC<{
|
||||
}
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div
|
||||
className="components-grid-alertgrid-alertgroup"
|
||||
style={{
|
||||
@@ -247,7 +247,7 @@ const AlertGroup: FC<{
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { AlertGroup };
|
||||
export default observer(AlertGroup);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useEffect, useState, useCallback, MouseEvent } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
@@ -21,7 +21,7 @@ import { APIGridT } from "Models/APITypes";
|
||||
import { useGrid } from "Hooks/useGrid";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { DefaultDetailsCollapseValue } from "./AlertGroup/DetailsToggle";
|
||||
import { AlertGroup } from "./AlertGroup";
|
||||
import AlertGroup from "./AlertGroup";
|
||||
import { Swimlane } from "./Swimlane";
|
||||
|
||||
const Grid: FC<{
|
||||
@@ -108,7 +108,7 @@ const Grid: FC<{
|
||||
repack();
|
||||
});
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CSSTransition
|
||||
key={grid.labelValue}
|
||||
@@ -189,7 +189,7 @@ const Grid: FC<{
|
||||
)}
|
||||
</TransitionGroup>
|
||||
</React.Fragment>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { Grid };
|
||||
export default observer(Grid);
|
||||
|
||||
@@ -5,8 +5,8 @@ import { faTh } from "@fortawesome/free-solid-svg-icons/faTh";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { APIGridT } from "Models/APITypes";
|
||||
import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
|
||||
import FilteringLabel from "Components/Labels/FilteringLabel";
|
||||
import FilteringCounterBadge from "Components/Labels/FilteringCounterBadge";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { GetGridElementWidth, GridSizesConfig } from "./GridSize";
|
||||
import { Grid } from "./Grid";
|
||||
import { AlertGrid } from ".";
|
||||
import Grid from "./Grid";
|
||||
import AlertGrid from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let settingsStore: Settings;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC, Ref, useEffect, useState } from "react";
|
||||
|
||||
import { autorun } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import useDimensions from "react-cool-dimensions";
|
||||
|
||||
@@ -13,7 +13,7 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { useWindowSize } from "Hooks/useWindowSize";
|
||||
import { Grid } from "./Grid";
|
||||
import Grid from "./Grid";
|
||||
import { GridSizesConfig, GetGridElementWidth } from "./GridSize";
|
||||
|
||||
const AlertGrid: FC<{
|
||||
@@ -56,7 +56,7 @@ const AlertGrid: FC<{
|
||||
|
||||
useHotkeys("alt+space", alertStore.status.togglePause);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div ref={ref as Ref<HTMLDivElement>} />
|
||||
{alertStore.data.grids.map((grid) => (
|
||||
@@ -72,7 +72,7 @@ const AlertGrid: FC<{
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { AlertGrid };
|
||||
export default observer(AlertGrid);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { FatalError } from "./FatalError";
|
||||
import { UpgradeNeeded } from "./UpgradeNeeded";
|
||||
import { ReloadNeeded } from "./ReloadNeeded";
|
||||
import { EmptyGrid } from "./EmptyGrid";
|
||||
import { Grid } from ".";
|
||||
import Grid from ".";
|
||||
import { InternalError } from "../../ErrorBoundary";
|
||||
|
||||
import "Styles/Percy.scss";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { shallow } from "enzyme";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { Grid } from ".";
|
||||
import Grid from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let settingsStore: Settings;
|
||||
@@ -40,7 +40,7 @@ const ShallowGrid = () => {
|
||||
describe("<Grid />", () => {
|
||||
it("renders only AlertGrid when all upstreams are healthy", () => {
|
||||
const tree = ShallowGrid();
|
||||
expect(tree.text()).toBe("<AlertGrid />");
|
||||
expect(tree.find("AlertGrid")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders FatalError if there's only one upstream and it's unhealthy", () => {
|
||||
@@ -129,7 +129,7 @@ describe("<Grid />", () => {
|
||||
alertStore.info.version = "unknown";
|
||||
alertStore.info.setTotalAlerts(0);
|
||||
const tree = ShallowGrid();
|
||||
expect(tree.text()).toBe("<AlertGrid />");
|
||||
expect(tree.find("AlertGrid")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders EmptyGrid after first fetch when totalAlerts is 0", () => {
|
||||
@@ -143,7 +143,7 @@ describe("<Grid />", () => {
|
||||
alertStore.info.version = "unknown";
|
||||
alertStore.info.setTotalAlerts(1);
|
||||
const tree = ShallowGrid();
|
||||
expect(tree.text()).toBe("<AlertGrid />");
|
||||
expect(tree.find("AlertGrid")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("unmounts without crashes", () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { AlertGrid } from "./AlertGrid";
|
||||
import AlertGrid from "./AlertGrid";
|
||||
import { FatalError } from "./FatalError";
|
||||
import { UpgradeNeeded } from "./UpgradeNeeded";
|
||||
import { ReloadNeeded } from "./ReloadNeeded";
|
||||
@@ -16,30 +16,28 @@ const Grid: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, settingsStore, silenceFormStore }) => {
|
||||
return useObserver(() =>
|
||||
alertStore.info.upgradeNeeded ? (
|
||||
<UpgradeNeeded newVersion={alertStore.info.version} reloadAfter={3000} />
|
||||
) : alertStore.info.reloadNeeded ? (
|
||||
<ReloadNeeded reloadAfter={4000} />
|
||||
) : alertStore.status.error ? (
|
||||
<FatalError message={alertStore.status.error} />
|
||||
) : alertStore.data.upstreams.counters &&
|
||||
alertStore.data.upstreams.counters.total === 1 &&
|
||||
alertStore.data.upstreams.counters.healthy === 0 &&
|
||||
alertStore.data.upstreams.instances[0] &&
|
||||
alertStore.data.upstreams.instances[0].error !== "" ? (
|
||||
<FatalError message={alertStore.data.upstreams.instances[0].error} />
|
||||
) : alertStore.info.version !== "unknown" &&
|
||||
alertStore.info.totalAlerts === 0 ? (
|
||||
<EmptyGrid />
|
||||
) : (
|
||||
<AlertGrid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)
|
||||
return alertStore.info.upgradeNeeded ? (
|
||||
<UpgradeNeeded newVersion={alertStore.info.version} reloadAfter={3000} />
|
||||
) : alertStore.info.reloadNeeded ? (
|
||||
<ReloadNeeded reloadAfter={4000} />
|
||||
) : alertStore.status.error ? (
|
||||
<FatalError message={alertStore.status.error} />
|
||||
) : alertStore.data.upstreams.counters &&
|
||||
alertStore.data.upstreams.counters.total === 1 &&
|
||||
alertStore.data.upstreams.counters.healthy === 0 &&
|
||||
alertStore.data.upstreams.instances[0] &&
|
||||
alertStore.data.upstreams.instances[0].error !== "" ? (
|
||||
<FatalError message={alertStore.data.upstreams.instances[0].error} />
|
||||
) : alertStore.info.version !== "unknown" &&
|
||||
alertStore.info.totalAlerts === 0 ? (
|
||||
<EmptyGrid />
|
||||
) : (
|
||||
<AlertGrid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Grid };
|
||||
export default observer(Grid);
|
||||
|
||||
@@ -4,7 +4,7 @@ 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";
|
||||
import StaticLabel from "Components/Labels/StaticLabel";
|
||||
import { PageSelect } from "Components/Pagination";
|
||||
|
||||
// take a list of groups and outputs a list of label sets, this ignores
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
@@ -16,7 +16,7 @@ import { InlineEdit } from "Components/InlineEdit";
|
||||
const FilterInputLabel: FC<{
|
||||
alertStore: AlertStore;
|
||||
filter: FilterT;
|
||||
}> = ({ alertStore, filter }) => {
|
||||
}> = observer(({ alertStore, filter }) => {
|
||||
const onChange = (val: string) => {
|
||||
alertStore.status.resume();
|
||||
// if filter is empty string then remove it
|
||||
@@ -42,7 +42,7 @@ const FilterInputLabel: FC<{
|
||||
...cs.baseClassNames,
|
||||
].join(" ");
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`${rootClasses} d-inline-flex flex-row align-items-center`}
|
||||
@@ -80,7 +80,7 @@ const FilterInputLabel: FC<{
|
||||
onClick={() => alertStore.filters.removeFilter(filter.raw)}
|
||||
/>
|
||||
</button>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { FilterInputLabel };
|
||||
|
||||
@@ -4,7 +4,7 @@ import { mount, render } from "enzyme";
|
||||
|
||||
import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
|
||||
import { QueryOperators } from "Common/Query";
|
||||
import { FilteringCounterBadge } from ".";
|
||||
import FilteringCounterBadge from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
|
||||
|
||||
@@ -22,73 +22,69 @@ const FilteringCounterBadge: FC<{
|
||||
alwaysVisible?: boolean;
|
||||
defaultColor?: "light" | "primary";
|
||||
isAppend?: boolean;
|
||||
}> = observer(
|
||||
({
|
||||
}> = ({
|
||||
alertStore,
|
||||
name,
|
||||
value,
|
||||
counter,
|
||||
themed,
|
||||
alwaysVisible = false,
|
||||
defaultColor = "light",
|
||||
isAppend = true,
|
||||
}) => {
|
||||
const { ref, props } = useFlashTransition(counter);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
// left click => apply foo=bar filter
|
||||
// left click + alt => apply foo!=bar filter
|
||||
const operator =
|
||||
event.altKey === true ? QueryOperators.NotEqual : QueryOperators.Equal;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (isAppend) {
|
||||
alertStore.filters.addFilter(FormatQuery(name, operator, value));
|
||||
} else {
|
||||
alertStore.filters.setFilters([FormatQuery(name, operator, value)]);
|
||||
}
|
||||
},
|
||||
[alertStore.filters, name, value, isAppend]
|
||||
);
|
||||
|
||||
if (!alwaysVisible && counter === 0) return null;
|
||||
|
||||
const cs = GetClassAndStyle(
|
||||
alertStore,
|
||||
name,
|
||||
value,
|
||||
counter,
|
||||
themed,
|
||||
alwaysVisible = false,
|
||||
defaultColor = "light",
|
||||
isAppend = true,
|
||||
}) => {
|
||||
const { ref, props } = useFlashTransition(counter);
|
||||
"badge-pill components-label-with-hover"
|
||||
);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
// left click => apply foo=bar filter
|
||||
// left click + alt => apply foo!=bar filter
|
||||
const operator =
|
||||
event.altKey === true
|
||||
? QueryOperators.NotEqual
|
||||
: QueryOperators.Equal;
|
||||
return (
|
||||
<TooltipWrapper
|
||||
title={`Click to only show ${name}=${value} alerts or Alt+Click to hide them`}
|
||||
>
|
||||
<CSSTransition {...props}>
|
||||
<span
|
||||
ref={ref}
|
||||
className={
|
||||
themed
|
||||
? cs.className
|
||||
: [
|
||||
`badge-${defaultColor}`,
|
||||
"badge-pill components-label-with-hover",
|
||||
...cs.baseClassNames,
|
||||
].join(" ")
|
||||
}
|
||||
style={themed ? {} : cs.style}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{counter}
|
||||
</span>
|
||||
</CSSTransition>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (isAppend) {
|
||||
alertStore.filters.addFilter(FormatQuery(name, operator, value));
|
||||
} else {
|
||||
alertStore.filters.setFilters([FormatQuery(name, operator, value)]);
|
||||
}
|
||||
},
|
||||
[alertStore.filters, name, value, isAppend]
|
||||
);
|
||||
|
||||
if (!alwaysVisible && counter === 0) return null;
|
||||
|
||||
const cs = GetClassAndStyle(
|
||||
alertStore,
|
||||
name,
|
||||
value,
|
||||
"badge-pill components-label-with-hover"
|
||||
);
|
||||
|
||||
return (
|
||||
<TooltipWrapper
|
||||
title={`Click to only show ${name}=${value} alerts or Alt+Click to hide them`}
|
||||
>
|
||||
<CSSTransition {...props}>
|
||||
<span
|
||||
ref={ref}
|
||||
className={
|
||||
themed
|
||||
? cs.className
|
||||
: [
|
||||
`badge-${defaultColor}`,
|
||||
"badge-pill components-label-with-hover",
|
||||
...cs.baseClassNames,
|
||||
].join(" ")
|
||||
}
|
||||
style={themed ? {} : cs.style}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{counter}
|
||||
</span>
|
||||
</CSSTransition>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { FilteringCounterBadge };
|
||||
export default observer(FilteringCounterBadge);
|
||||
|
||||
@@ -6,7 +6,7 @@ import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
|
||||
|
||||
import { FilteringLabel } from ".";
|
||||
import FilteringLabel from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useCallback, MouseEvent } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { QueryOperators, FormatQuery } from "Common/Query";
|
||||
@@ -33,14 +33,14 @@ const FilteringLabel: FC<{
|
||||
"components-label-with-hover"
|
||||
);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<TooltipWrapper title="Click to only show alerts with this label or Alt+Click to hide them">
|
||||
<span className={cs.className} style={cs.style} onClick={handleClick}>
|
||||
<span className="components-label-name">{name}:</span>{" "}
|
||||
<span className="components-label-value">{value}</span>
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { FilteringLabel };
|
||||
export default observer(FilteringLabel);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { shallow } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
import { HistoryLabel } from ".";
|
||||
import HistoryLabel from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { QueryOperators } from "Common/Query";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
@@ -19,12 +19,12 @@ const HistoryLabel: FC<{
|
||||
"components-label-history components-label-value"
|
||||
);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<span className={cs.className} style={cs.style}>
|
||||
{name ? `${name}${matcher}` : null}
|
||||
{value}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { HistoryLabel };
|
||||
export default observer(HistoryLabel);
|
||||
|
||||
@@ -6,7 +6,7 @@ import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
|
||||
|
||||
import { LabelWithPercent } from ".";
|
||||
import LabelWithPercent from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
|
||||
|
||||
@@ -17,82 +17,78 @@ const LabelWithPercent: FC<{
|
||||
percent: number;
|
||||
offset: number;
|
||||
isActive: boolean;
|
||||
}> = observer(
|
||||
({ alertStore, name, value, hits, percent, offset, isActive }) => {
|
||||
const handleClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
// left click => apply foo=bar filter
|
||||
// left click + alt => apply foo!=bar filter
|
||||
const operator =
|
||||
event.altKey === true
|
||||
? QueryOperators.NotEqual
|
||||
: QueryOperators.Equal;
|
||||
}> = ({ alertStore, name, value, hits, percent, offset, isActive }) => {
|
||||
const handleClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
// left click => apply foo=bar filter
|
||||
// left click + alt => apply foo!=bar filter
|
||||
const operator =
|
||||
event.altKey === true ? QueryOperators.NotEqual : QueryOperators.Equal;
|
||||
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
alertStore.filters.addFilter(FormatQuery(name, operator, value));
|
||||
},
|
||||
[alertStore.filters, name, value]
|
||||
alertStore.filters.addFilter(FormatQuery(name, operator, value));
|
||||
},
|
||||
[alertStore.filters, name, value]
|
||||
);
|
||||
|
||||
const removeFromFilters = () => {
|
||||
alertStore.filters.removeFilter(
|
||||
FormatQuery(name, QueryOperators.Equal, value)
|
||||
);
|
||||
};
|
||||
|
||||
const removeFromFilters = () => {
|
||||
alertStore.filters.removeFilter(
|
||||
FormatQuery(name, QueryOperators.Equal, value)
|
||||
);
|
||||
};
|
||||
const cs = GetClassAndStyle(
|
||||
alertStore,
|
||||
name,
|
||||
value,
|
||||
"components-label-with-hover mb-0 pl-0 text-left"
|
||||
);
|
||||
|
||||
const cs = GetClassAndStyle(
|
||||
alertStore,
|
||||
name,
|
||||
value,
|
||||
"components-label-with-hover mb-0 pl-0 text-left"
|
||||
);
|
||||
const progressBarBg =
|
||||
percent > 66 ? "bg-danger" : percent > 33 ? "bg-warning" : "bg-success";
|
||||
|
||||
const progressBarBg =
|
||||
percent > 66 ? "bg-danger" : percent > 33 ? "bg-warning" : "bg-success";
|
||||
|
||||
return (
|
||||
<div className="d-inline-block mw-100">
|
||||
<span className={cs.className} style={cs.style}>
|
||||
<span className="mr-1 px-1 bg-primary text-white components-labelWithPercent-percent">
|
||||
{hits}
|
||||
</span>
|
||||
<span onClick={handleClick}>
|
||||
<span className="components-label-name">{name}:</span>{" "}
|
||||
<span className="components-label-value">{value}</span>
|
||||
</span>
|
||||
{isActive ? (
|
||||
<FontAwesomeIcon
|
||||
className="cursor-pointer text-reset ml-1 close"
|
||||
style={{ fontSize: "100%" }}
|
||||
icon={faTimes}
|
||||
onClick={removeFromFilters}
|
||||
/>
|
||||
) : null}
|
||||
return (
|
||||
<div className="d-inline-block mw-100">
|
||||
<span className={cs.className} style={cs.style}>
|
||||
<span className="mr-1 px-1 bg-primary text-white components-labelWithPercent-percent">
|
||||
{hits}
|
||||
</span>
|
||||
<div className="progress components-labelWithPercent-progress mr-1">
|
||||
{offset === 0 ? null : (
|
||||
<div
|
||||
className="progress-bar bg-transparent"
|
||||
role="progressbar"
|
||||
style={{ width: offset + "%" }}
|
||||
aria-valuenow={offset}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
)}
|
||||
<span onClick={handleClick}>
|
||||
<span className="components-label-name">{name}:</span>{" "}
|
||||
<span className="components-label-value">{value}</span>
|
||||
</span>
|
||||
{isActive ? (
|
||||
<FontAwesomeIcon
|
||||
className="cursor-pointer text-reset ml-1 close"
|
||||
style={{ fontSize: "100%" }}
|
||||
icon={faTimes}
|
||||
onClick={removeFromFilters}
|
||||
/>
|
||||
) : null}
|
||||
</span>
|
||||
<div className="progress components-labelWithPercent-progress mr-1">
|
||||
{offset === 0 ? null : (
|
||||
<div
|
||||
className={`progress-bar ${progressBarBg}`}
|
||||
className="progress-bar bg-transparent"
|
||||
role="progressbar"
|
||||
style={{ width: percent + "%" }}
|
||||
aria-valuenow={percent}
|
||||
style={{ width: offset + "%" }}
|
||||
aria-valuenow={offset}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`progress-bar ${progressBarBg}`}
|
||||
role="progressbar"
|
||||
style={{ width: percent + "%" }}
|
||||
aria-valuenow={percent}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { LabelWithPercent };
|
||||
export default observer(LabelWithPercent);
|
||||
|
||||
@@ -6,7 +6,7 @@ import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
import { StaticLabel } from ".";
|
||||
import StaticLabel from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
@@ -13,12 +13,12 @@ const StaticLabel: FC<{
|
||||
}> = ({ alertStore, name, value }) => {
|
||||
const cs = GetClassAndStyle(alertStore, name, value);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<span className={cs.className} style={cs.style}>
|
||||
<span className="components-label-name">{name}:</span>{" "}
|
||||
<span className="components-label-value">{value}</span>
|
||||
</span>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { StaticLabel };
|
||||
export default observer(StaticLabel);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import Select from "react-select";
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const AlertGroupCollapseConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
}> = observer(({ settingsStore }) => {
|
||||
if (
|
||||
!Object.values(settingsStore.alertGroupConfig.options)
|
||||
.map((o) => o.value)
|
||||
@@ -32,7 +32,7 @@ const AlertGroupCollapseConfiguration: FC<{
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="form-group mb-0">
|
||||
<Select
|
||||
styles={context.reactSelectStyles}
|
||||
@@ -48,7 +48,7 @@ const AlertGroupCollapseConfiguration: FC<{
|
||||
hideSelectedOptions
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { AlertGroupCollapseConfiguration };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import Select from "react-select";
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SortLabelName } from "./SortLabelName";
|
||||
|
||||
const AlertGroupSortConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
}> = observer(({ settingsStore }) => {
|
||||
if (
|
||||
!Object.values(settingsStore.gridConfig.options)
|
||||
.map((o) => o.value)
|
||||
@@ -40,7 +40,7 @@ const AlertGroupSortConfiguration: FC<{
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="form-group mb-0">
|
||||
<div className="d-flex flex-fill flex-lg-row flex-column justify-content-between">
|
||||
<div className="flex-shrink-0 flex-grow-1 flex-basis-auto">
|
||||
@@ -86,7 +86,7 @@ const AlertGroupSortConfiguration: FC<{
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { AlertGroupSortConfiguration };
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const AlertGroupTitleBarColor: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
}> = observer(({ settingsStore }) => {
|
||||
const onChange = (value: boolean) => {
|
||||
settingsStore.alertGroupConfig.setColorTitleBar(value);
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="form-group mb-0">
|
||||
<div className="form-check form-check-inline">
|
||||
<span className="custom-control custom-switch">
|
||||
@@ -34,7 +34,7 @@ const AlertGroupTitleBarColor: FC<{
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { AlertGroupTitleBarColor };
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const FilterBarConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
}> = observer(({ settingsStore }) => {
|
||||
const onAutohideChange = (value: boolean) => {
|
||||
settingsStore.filterBarConfig.setAutohide(value);
|
||||
};
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="form-group mb-0">
|
||||
<div className="form-check form-check-inline">
|
||||
<span className="custom-control custom-switch">
|
||||
@@ -31,7 +31,7 @@ const FilterBarConfiguration: FC<{
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { FilterBarConfiguration };
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { GridLabelName } from "./GridLabelName";
|
||||
|
||||
const MultiGridConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
}> = observer(({ settingsStore }) => {
|
||||
const onSortReverseChange = (value: boolean) => {
|
||||
settingsStore.multiGridConfig.setGridSortReverse(value);
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="form-group mb-0">
|
||||
<div className="d-flex flex-fill flex-lg-row flex-column justify-content-between">
|
||||
<div className="flex-shrink-0 flex-grow-1 flex-basis-auto mx-0 mx-lg-1 mt-1 mt-lg-0">
|
||||
@@ -40,7 +40,7 @@ const MultiGridConfiguration: FC<{
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { MultiGridConfiguration };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import Select from "react-select";
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const ThemeConfiguration: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
}> = observer(({ settingsStore }) => {
|
||||
if (
|
||||
!Object.values(settingsStore.themeConfig.options)
|
||||
.map((o) => o.value)
|
||||
@@ -32,7 +32,7 @@ const ThemeConfiguration: FC<{
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="form-group mb-2">
|
||||
<Select
|
||||
styles={context.reactSelectStyles}
|
||||
@@ -46,7 +46,7 @@ const ThemeConfiguration: FC<{
|
||||
hideSelectedOptions
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { ThemeConfiguration };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useState } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { Observer } from "mobx-react-lite";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
@@ -25,7 +25,7 @@ const MainModalContent: FC<{
|
||||
}) => {
|
||||
const [tab, setTab] = useState<OpenTabT>(openTab);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="modal-header py-2">
|
||||
<nav className="nav nav-pills nav-justified w-100">
|
||||
@@ -54,15 +54,25 @@ const MainModalContent: FC<{
|
||||
) : null}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
{alertStore.info.authentication.enabled && (
|
||||
<span className="text-muted mr-2">
|
||||
Username: {alertStore.info.authentication.username}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-muted">Version: {alertStore.info.version}</span>
|
||||
<Observer>
|
||||
{() =>
|
||||
alertStore.info.authentication.enabled ? (
|
||||
<span className="text-muted mr-2">
|
||||
Username: {alertStore.info.authentication.username}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
</Observer>
|
||||
<Observer>
|
||||
{() => (
|
||||
<span className="text-muted">
|
||||
Version: {alertStore.info.version}
|
||||
</span>
|
||||
)}
|
||||
</Observer>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { MainModalContent };
|
||||
|
||||
@@ -54,8 +54,8 @@ describe("<MainModal />", () => {
|
||||
const toggle = tree.find(".nav-link");
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
|
||||
expect(tree.find("MainModalContent")).toHaveLength(0);
|
||||
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1);
|
||||
expect(tree.find("MainModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("renders modal content if fallback is not used", () => {
|
||||
@@ -63,8 +63,8 @@ describe("<MainModal />", () => {
|
||||
const toggle = tree.find(".nav-link");
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0);
|
||||
expect(tree.find("MainModalContent")).toHaveLength(1);
|
||||
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(0);
|
||||
expect(tree.find("MainModalContent")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("hides the modal when toggle() is called twice", () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
|
||||
|
||||
import { APISilenceT } from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
|
||||
import FilteringCounterBadge from "Components/Labels/FilteringCounterBadge";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
import { SilenceProgress } from "./SilenceProgress";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import parseISO from "date-fns/parseISO";
|
||||
import getUnixTime from "date-fns/getUnixTime";
|
||||
@@ -18,7 +18,7 @@ const calculatePercent = (startsAt: string, endsAt: string) => {
|
||||
|
||||
const SilenceProgress: FC<{
|
||||
silence: APISilenceT;
|
||||
}> = ({ silence }) => {
|
||||
}> = observer(({ silence }) => {
|
||||
const [progress, setProgress] = useState<number>(
|
||||
calculatePercent(silence.startsAt, silence.endsAt)
|
||||
);
|
||||
@@ -30,33 +30,31 @@ const SilenceProgress: FC<{
|
||||
return () => clearInterval(timer);
|
||||
}, [silence.startsAt, silence.endsAt]);
|
||||
|
||||
return useObserver(() =>
|
||||
parseISO(silence.endsAt) < new Date() ? (
|
||||
<span className="badge badge-danger align-text-bottom p-1">
|
||||
Expired <DateFromNow timestamp={silence.endsAt} />
|
||||
</span>
|
||||
) : (
|
||||
<span className="badge badge-light nmb-05 align-text-bottom p-1">
|
||||
Expires <DateFromNow timestamp={silence.endsAt} />
|
||||
<div className="progress silence-progress">
|
||||
<div
|
||||
className={
|
||||
progress > 90
|
||||
? "progress-bar bg-danger"
|
||||
: progress > 75
|
||||
? "progress-bar bg-warning"
|
||||
: "progress-bar bg-success"
|
||||
}
|
||||
role="progressbar"
|
||||
style={{ width: progress + "%" }}
|
||||
aria-valuenow={progress}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
)
|
||||
return parseISO(silence.endsAt) < new Date() ? (
|
||||
<span className="badge badge-danger align-text-bottom p-1">
|
||||
Expired <DateFromNow timestamp={silence.endsAt} />
|
||||
</span>
|
||||
) : (
|
||||
<span className="badge badge-light nmb-05 align-text-bottom p-1">
|
||||
Expires <DateFromNow timestamp={silence.endsAt} />
|
||||
<div className="progress silence-progress">
|
||||
<div
|
||||
className={
|
||||
progress > 90
|
||||
? "progress-bar bg-danger"
|
||||
: progress > 75
|
||||
? "progress-bar bg-warning"
|
||||
: "progress-bar bg-success"
|
||||
}
|
||||
role="progressbar"
|
||||
style={{ width: progress + "%" }}
|
||||
aria-valuenow={progress}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { SilenceProgress };
|
||||
|
||||
@@ -9,7 +9,7 @@ import React, {
|
||||
ReactNode,
|
||||
} from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { localStored } from "mobx-stored";
|
||||
|
||||
import { Manager, Reference, Popper } from "react-popper";
|
||||
@@ -27,7 +27,7 @@ import { Settings } from "Stores/Settings";
|
||||
import { IsMobile } from "Common/Device";
|
||||
import { CommonPopperModifiers } from "Common/Popper";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import { HistoryLabel } from "Components/Labels/HistoryLabel";
|
||||
import HistoryLabel from "Components/Labels/HistoryLabel";
|
||||
import { useOnClickOutside } from "Hooks/useOnClickOutside";
|
||||
|
||||
interface ReduceFilterT {
|
||||
@@ -164,7 +164,7 @@ interface HistoryStorageT {
|
||||
const History: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, settingsStore }) => {
|
||||
}> = observer(({ alertStore, settingsStore }) => {
|
||||
// this will be dumped to local storage via mobx-stored
|
||||
const history: HistoryStorageT = localStored(
|
||||
"history.filters",
|
||||
@@ -214,7 +214,7 @@ const History: FC<{
|
||||
const ref = useRef<HTMLSpanElement | null>(null);
|
||||
useOnClickOutside(ref, hide, isVisible);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
// data-filters is there to register filters for observation in mobx
|
||||
// it needs to be using full filter object to notice changes to
|
||||
// name & value but ignore hits
|
||||
@@ -264,7 +264,7 @@ const History: FC<{
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
</span>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { History, HistoryMenu, ReduceFilter };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useEffect, useState, useRef, useCallback } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import Autosuggest from "react-autosuggest";
|
||||
import Highlight from "react-highlighter";
|
||||
@@ -21,7 +21,7 @@ import { History } from "./History";
|
||||
const FilterInput: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, settingsStore }) => {
|
||||
}> = observer(({ alertStore, settingsStore }) => {
|
||||
const autosuggestRef = useRef<Autosuggest | null>(null);
|
||||
const inputRef = useRef<HTMLElement | null>(null);
|
||||
const formRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -135,7 +135,7 @@ const FilterInput: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
// data-filters is there to register filters for observation in mobx
|
||||
// in order to re-render input component
|
||||
<form className="form-inline mw-100" onSubmit={onSubmit}>
|
||||
@@ -184,7 +184,7 @@ const FilterInput: FC<{
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { FilterInput };
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AlertStore, NewUnappliedFilter, FilterT } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { HistoryMenu } from "./FilterInput/History";
|
||||
import { NavBar } from ".";
|
||||
import NavBar from ".";
|
||||
|
||||
import "Styles/Percy.scss";
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import { MockThemeContext } from "__mocks__/Theme";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { NavBar, MobileIdleTimeout, DesktopIdleTimeout } from ".";
|
||||
import NavBar from ".";
|
||||
import { MobileIdleTimeout, DesktopIdleTimeout } from "./timeouts";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let settingsStore: Settings;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC, useState, useEffect, useCallback } from "react";
|
||||
|
||||
import { reaction } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import useDimensions from "react-cool-dimensions";
|
||||
|
||||
@@ -15,13 +15,11 @@ import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { IsMobile } from "Common/Device";
|
||||
import { OverviewModal } from "Components/OverviewModal";
|
||||
import { MainModal } from "Components/MainModal";
|
||||
import { SilenceModal } from "Components/SilenceModal";
|
||||
import SilenceModal from "Components/SilenceModal";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { Fetcher } from "Components/Fetcher";
|
||||
import { FilterInput } from "./FilterInput";
|
||||
|
||||
const DesktopIdleTimeout = 1000 * 60 * 3;
|
||||
const MobileIdleTimeout = 1000 * 12;
|
||||
import { MobileIdleTimeout, DesktopIdleTimeout } from "./timeouts";
|
||||
|
||||
const NavBar: FC<{
|
||||
alertStore: AlertStore;
|
||||
@@ -87,7 +85,7 @@ const NavBar: FC<{
|
||||
[] // eslint-disable-line react-hooks/exhaustive-deps
|
||||
);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className={`container p-0 m-0 mw-100 ${containerClass}`}>
|
||||
<CSSTransition
|
||||
classNames="components-animation-navbar"
|
||||
@@ -128,7 +126,7 @@ const NavBar: FC<{
|
||||
</nav>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { NavBar, MobileIdleTimeout, DesktopIdleTimeout };
|
||||
export default observer(NavBar);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
const DesktopIdleTimeout = 1000 * 60 * 3;
|
||||
const MobileIdleTimeout = 1000 * 12;
|
||||
|
||||
export { MobileIdleTimeout, DesktopIdleTimeout };
|
||||
@@ -5,7 +5,7 @@ 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 LabelWithPercent from "Components/Labels/LabelWithPercent";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
|
||||
const TableRows: FC<{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useState, useCallback } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
@@ -21,14 +21,14 @@ const OverviewModalContent = React.lazy(() =>
|
||||
|
||||
const OverviewModal: FC<{
|
||||
alertStore: AlertStore;
|
||||
}> = ({ alertStore }) => {
|
||||
}> = observer(({ alertStore }) => {
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
|
||||
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
|
||||
|
||||
const { ref, props } = useFlashTransition(alertStore.info.totalAlerts);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TooltipWrapper title="Show alert overview">
|
||||
<CSSTransition {...props}>
|
||||
@@ -58,7 +58,7 @@ const OverviewModal: FC<{
|
||||
</React.Suspense>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { OverviewModal };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC, useEffect } from "react";
|
||||
|
||||
import { autorun } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import Select from "react-select";
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ValidationError } from "Components/ValidationError";
|
||||
const AlertManagerInput: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ alertStore, silenceFormStore }) => {
|
||||
}> = observer(({ alertStore, silenceFormStore }) => {
|
||||
useEffect(() => {
|
||||
if (silenceFormStore.data.alertmanagers.length === 0) {
|
||||
silenceFormStore.data.setAlertmanagers(
|
||||
@@ -52,7 +52,7 @@ const AlertManagerInput: FC<{
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<Select
|
||||
styles={context.reactSelectStyles}
|
||||
classNamePrefix="react-select"
|
||||
@@ -77,7 +77,7 @@ const AlertManagerInput: FC<{
|
||||
}}
|
||||
isDisabled={silenceFormStore.data.silenceID !== null}
|
||||
/>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { AlertManagerInput };
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { useFetchGet } from "__mocks__/Hooks/useFetchGet";
|
||||
import { Browser } from ".";
|
||||
import Browser from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useState, useEffect, ReactNode } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import TransitionGroup from "react-transition-group/TransitionGroup";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
@@ -88,7 +88,7 @@ const Browser: FC<{
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="d-flex flex-fill flex-lg-row flex-column justify-content-between mb-3"
|
||||
@@ -178,7 +178,7 @@ const Browser: FC<{
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { Browser };
|
||||
export default observer(Browser);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useEffect, useState, useCallback, ReactNode } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import differenceInMinutes from "date-fns/differenceInMinutes";
|
||||
import differenceInHours from "date-fns/differenceInHours";
|
||||
@@ -56,8 +56,8 @@ const Tab: FC<{
|
||||
|
||||
const TabContentStart: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore }) => {
|
||||
return useObserver(() => (
|
||||
}> = observer(({ silenceFormStore }) => {
|
||||
return (
|
||||
<div className="d-flex flex-sm-row flex-column justify-content-around mx-3 mt-2">
|
||||
<div className="d-flex justify-content-center align-items-center">
|
||||
<DayPicker
|
||||
@@ -93,50 +93,50 @@ const TabContentStart: FC<{
|
||||
onMinuteDec={() => silenceFormStore.data.decStart(1)}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
const TabContentEnd: FC<{ silenceFormStore: SilenceFormStore }> = ({
|
||||
silenceFormStore,
|
||||
}) => {
|
||||
return useObserver(() => (
|
||||
<div className="d-flex flex-sm-row flex-column justify-content-around mx-3 mt-2">
|
||||
<div className="d-flex justify-content-center align-items-center">
|
||||
<DayPicker
|
||||
className="components-date-range"
|
||||
month={silenceFormStore.data.endsAt}
|
||||
disabledDays={{
|
||||
before: setSeconds(silenceFormStore.data.startsAt, 0),
|
||||
}}
|
||||
todayButton="Today"
|
||||
onDayClick={(val) => {
|
||||
const endsAt = new Date(val);
|
||||
endsAt.setHours(silenceFormStore.data.endsAt.getHours());
|
||||
endsAt.setMinutes(silenceFormStore.data.endsAt.getMinutes());
|
||||
endsAt.setSeconds(0);
|
||||
silenceFormStore.data.setEnd(endsAt);
|
||||
silenceFormStore.data.verifyStarEnd();
|
||||
}}
|
||||
selectedDays={{
|
||||
from: silenceFormStore.data.startsAt,
|
||||
to: silenceFormStore.data.endsAt,
|
||||
}}
|
||||
modifiers={{
|
||||
start: silenceFormStore.data.startsAt,
|
||||
end: silenceFormStore.data.endsAt,
|
||||
}}
|
||||
const TabContentEnd: FC<{ silenceFormStore: SilenceFormStore }> = observer(
|
||||
({ silenceFormStore }) => {
|
||||
return (
|
||||
<div className="d-flex flex-sm-row flex-column justify-content-around mx-3 mt-2">
|
||||
<div className="d-flex justify-content-center align-items-center">
|
||||
<DayPicker
|
||||
className="components-date-range"
|
||||
month={silenceFormStore.data.endsAt}
|
||||
disabledDays={{
|
||||
before: setSeconds(silenceFormStore.data.startsAt, 0),
|
||||
}}
|
||||
todayButton="Today"
|
||||
onDayClick={(val) => {
|
||||
const endsAt = new Date(val);
|
||||
endsAt.setHours(silenceFormStore.data.endsAt.getHours());
|
||||
endsAt.setMinutes(silenceFormStore.data.endsAt.getMinutes());
|
||||
endsAt.setSeconds(0);
|
||||
silenceFormStore.data.setEnd(endsAt);
|
||||
silenceFormStore.data.verifyStarEnd();
|
||||
}}
|
||||
selectedDays={{
|
||||
from: silenceFormStore.data.startsAt,
|
||||
to: silenceFormStore.data.endsAt,
|
||||
}}
|
||||
modifiers={{
|
||||
start: silenceFormStore.data.startsAt,
|
||||
end: silenceFormStore.data.endsAt,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<HourMinute
|
||||
dateValue={silenceFormStore.data.endsAt}
|
||||
onHourInc={() => silenceFormStore.data.incEnd(60)}
|
||||
onHourDec={() => silenceFormStore.data.decEnd(60)}
|
||||
onMinuteInc={() => silenceFormStore.data.incEnd(1)}
|
||||
onMinuteDec={() => silenceFormStore.data.decEnd(1)}
|
||||
/>
|
||||
</div>
|
||||
<HourMinute
|
||||
dateValue={silenceFormStore.data.endsAt}
|
||||
onHourInc={() => silenceFormStore.data.incEnd(60)}
|
||||
onHourDec={() => silenceFormStore.data.decEnd(60)}
|
||||
onMinuteInc={() => silenceFormStore.data.incEnd(1)}
|
||||
onMinuteDec={() => silenceFormStore.data.decEnd(1)}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// calculate value for duration increase button using a goal step
|
||||
const CalculateChangeValueUp = (currentValue: number, step: number): number => {
|
||||
@@ -163,8 +163,8 @@ const CalculateChangeValueDown = (
|
||||
|
||||
const TabContentDuration: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore }) => {
|
||||
return useObserver(() => (
|
||||
}> = observer(({ silenceFormStore }) => {
|
||||
return (
|
||||
<div className="d-flex flex-sm-row flex-column justify-content-around mt-2 mx-3">
|
||||
<Duration
|
||||
label="days"
|
||||
@@ -196,15 +196,15 @@ const TabContentDuration: FC<{
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
type tabT = "start" | "end" | "duration";
|
||||
|
||||
const DateTimeSelect: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
openTab?: tabT;
|
||||
}> = ({ silenceFormStore, openTab = "duration" }) => {
|
||||
}> = observer(({ silenceFormStore, openTab = "duration" }) => {
|
||||
const [currentTab, setCurrentTab] = useState<tabT>(openTab);
|
||||
const [timeNow, setTimeNow] = useState<Date>(nowZeroSeconds());
|
||||
|
||||
@@ -219,7 +219,7 @@ const DateTimeSelect: FC<{
|
||||
};
|
||||
}, [updateTimeNow]);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ul className="nav nav-tabs nav-fill">
|
||||
<Tab
|
||||
@@ -277,7 +277,7 @@ const DateTimeSelect: FC<{
|
||||
) : null}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
export { DateTimeSelect, TabContentStart, TabContentEnd, TabContentDuration };
|
||||
|
||||
@@ -5,7 +5,7 @@ import { render } from "enzyme";
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { PayloadPreview } from ".";
|
||||
import PayloadPreview from ".";
|
||||
|
||||
describe("<PayloadPreview />", () => {
|
||||
it("matches snapshot", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import JSONPretty from "react-json-pretty";
|
||||
import * as theme from "react-json-pretty/dist/monikai";
|
||||
@@ -10,13 +10,13 @@ import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
const PayloadPreview: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore }) => {
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<JSONPretty
|
||||
json={silenceFormStore.data.toAlertmanagerPayload}
|
||||
theme={theme}
|
||||
themeClassName="rounded p-1"
|
||||
/>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { PayloadPreview };
|
||||
export default observer(PayloadPreview);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore, NewEmptyMatcher } from "Stores/SilenceFormStore";
|
||||
import { QueryOperators, StaticLabels } from "Common/Query";
|
||||
import { SilenceForm } from "./SilenceForm";
|
||||
import SilenceForm from "./SilenceForm";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let settingsStore: Settings;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useEffect, useState, MouseEvent, FormEvent } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
@@ -29,14 +29,14 @@ import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
import { AlertManagerInput } from "./AlertManagerInput";
|
||||
import { SilenceMatch } from "./SilenceMatch";
|
||||
import SilenceMatch from "./SilenceMatch";
|
||||
import { DateTimeSelect } from "./DateTimeSelect";
|
||||
import { PayloadPreview } from "./PayloadPreview";
|
||||
import PayloadPreview from "./PayloadPreview";
|
||||
import { IconInput, AuthenticatedAuthorInput } from "./AuthorInput";
|
||||
|
||||
const ShareButton: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore }) => {
|
||||
}> = observer(({ silenceFormStore }) => {
|
||||
const [clickCount, setClickCount] = useState<number>(0);
|
||||
|
||||
const baseURL = [
|
||||
@@ -48,7 +48,7 @@ const ShareButton: FC<{
|
||||
|
||||
const { ref, props } = useFlashTransition(clickCount);
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="input-group mb-3">
|
||||
<div className="input-group-prepend">
|
||||
<span className="input-group-text">
|
||||
@@ -79,8 +79,8 @@ const ShareButton: FC<{
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
);
|
||||
});
|
||||
|
||||
const SilenceForm: FC<{
|
||||
alertStore: AlertStore;
|
||||
@@ -178,7 +178,7 @@ const SilenceForm: FC<{
|
||||
silenceFormStore.data.setWasValidated(true);
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<form onSubmit={handleSubmit} autoComplete="on">
|
||||
<div className="mb-3">
|
||||
<AlertManagerInput
|
||||
@@ -262,7 +262,7 @@ const SilenceForm: FC<{
|
||||
</div>
|
||||
) : null}
|
||||
</form>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { SilenceForm };
|
||||
export default observer(SilenceForm);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
@@ -16,7 +16,7 @@ import { MatcherToFilter, AlertManagersToFilter } from "../Matchers";
|
||||
const MatchCounter: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
matcher: MatcherWithIDT;
|
||||
}> = ({ silenceFormStore, matcher }) => {
|
||||
}> = observer(({ silenceFormStore, matcher }) => {
|
||||
const filters = [MatcherToFilter(matcher)];
|
||||
if (silenceFormStore.data.alertmanagers.length) {
|
||||
filters.push(AlertManagersToFilter(silenceFormStore.data.alertmanagers));
|
||||
@@ -26,33 +26,31 @@ const MatchCounter: FC<{
|
||||
APIAlertsResponseT
|
||||
>(FormatBackendURI("alerts.json?") + FormatAlertsQ(filters));
|
||||
|
||||
return useObserver(() =>
|
||||
error ? (
|
||||
<TooltipWrapper
|
||||
title={`Failed to fetch alerts matching this label: ${error}`}
|
||||
return error ? (
|
||||
<TooltipWrapper
|
||||
title={`Failed to fetch alerts matching this label: ${error}`}
|
||||
>
|
||||
<FontAwesomeIcon className="text-danger" icon={faExclamationCircle} />
|
||||
</TooltipWrapper>
|
||||
) : (
|
||||
<TooltipWrapper title="Number of alerts matching this label">
|
||||
<span
|
||||
className="badge badge-light badge-pill d-block"
|
||||
style={{ fontSize: "85%", lineHeight: "1rem" }}
|
||||
data-am={silenceFormStore.data.alertmanagers.length}
|
||||
>
|
||||
<FontAwesomeIcon className="text-danger" icon={faExclamationCircle} />
|
||||
</TooltipWrapper>
|
||||
) : (
|
||||
<TooltipWrapper title="Number of alerts matching this label">
|
||||
<span
|
||||
className="badge badge-light badge-pill d-block"
|
||||
style={{ fontSize: "85%", lineHeight: "1rem" }}
|
||||
data-am={silenceFormStore.data.alertmanagers.length}
|
||||
>
|
||||
{isLoading || response === null ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinner}
|
||||
spin
|
||||
className={isRetrying ? "text-danger" : ""}
|
||||
/>
|
||||
) : (
|
||||
Math.max(response.totalAlerts, 0)
|
||||
)}
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)
|
||||
{isLoading || response === null ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinner}
|
||||
spin
|
||||
className={isRetrying ? "text-danger" : ""}
|
||||
/>
|
||||
) : (
|
||||
Math.max(response.totalAlerts, 0)
|
||||
)}
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { MatchCounter };
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
MatcherWithIDT,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { StringToOption } from "Common/Select";
|
||||
import { SilenceMatch } from ".";
|
||||
import SilenceMatch from ".";
|
||||
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
let matcher: MatcherWithIDT;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
|
||||
@@ -17,7 +17,7 @@ const SilenceMatch: FC<{
|
||||
onDelete: () => void;
|
||||
isValid: boolean;
|
||||
}> = ({ silenceFormStore, matcher, showDelete, onDelete, isValid }) => {
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<div className="d-flex flex-fill flex-lg-row flex-column mb-3">
|
||||
<div className="flex-shrink-0 flex-grow-0 flex-basis-25 pr-lg-2 pb-2 pb-lg-0">
|
||||
<LabelNameInput matcher={matcher} isValid={isValid} />
|
||||
@@ -64,7 +64,7 @@ const SilenceMatch: FC<{
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { SilenceMatch };
|
||||
export default observer(SilenceMatch);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { Observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
@@ -10,10 +10,10 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { Tab } from "Components/Modal/Tab";
|
||||
import { SilenceForm } from "./SilenceForm";
|
||||
import SilenceForm from "./SilenceForm";
|
||||
import { SilencePreview } from "./SilencePreview";
|
||||
import { SilenceSubmitController } from "./SilenceSubmit/SilenceSubmitController";
|
||||
import { Browser } from "./Browser";
|
||||
import SilenceSubmitController from "./SilenceSubmit/SilenceSubmitController";
|
||||
import Browser from "./Browser";
|
||||
|
||||
const ReadOnlyPlaceholder: FC = () => (
|
||||
<div className="jumbotron bg-transparent">
|
||||
@@ -37,79 +37,83 @@ const SilenceModalContent: FC<{
|
||||
onHide,
|
||||
previewOpen = false,
|
||||
}) => {
|
||||
return useObserver(() => (
|
||||
<React.Fragment>
|
||||
<div className="modal-header py-2">
|
||||
<nav className="nav nav-pills nav-justified w-100">
|
||||
<Tab
|
||||
title={
|
||||
silenceFormStore.data.currentStage === "form"
|
||||
? silenceFormStore.data.silenceID === null
|
||||
? "New silence"
|
||||
: "Editing silence"
|
||||
: silenceFormStore.data.currentStage === "preview"
|
||||
? "Preview silenced alerts"
|
||||
: "Silence submitted"
|
||||
}
|
||||
active={silenceFormStore.tab.current === "editor"}
|
||||
onClick={() => silenceFormStore.tab.setTab("editor")}
|
||||
/>
|
||||
<Tab
|
||||
title="Browse"
|
||||
active={silenceFormStore.tab.current === "browser"}
|
||||
onClick={() => silenceFormStore.tab.setTab("browser")}
|
||||
/>
|
||||
<button type="button" className="close" onClick={onHide}>
|
||||
<span>×</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div
|
||||
className={`modal-body ${
|
||||
silenceFormStore.toggle.blurred ? "modal-content-blur" : ""
|
||||
}`}
|
||||
>
|
||||
{silenceFormStore.tab.current === "editor" ? (
|
||||
silenceFormStore.data.currentStage === "form" ? (
|
||||
alertStore.data.upstreams.instances.length > 0 ? (
|
||||
Object.keys(alertStore.data.clustersWithoutReadOnly).length >
|
||||
0 ? (
|
||||
<SilenceForm
|
||||
return (
|
||||
<Observer>
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
<div className="modal-header py-2">
|
||||
<nav className="nav nav-pills nav-justified w-100">
|
||||
<Tab
|
||||
title={
|
||||
silenceFormStore.data.currentStage === "form"
|
||||
? silenceFormStore.data.silenceID === null
|
||||
? "New silence"
|
||||
: "Editing silence"
|
||||
: silenceFormStore.data.currentStage === "preview"
|
||||
? "Preview silenced alerts"
|
||||
: "Silence submitted"
|
||||
}
|
||||
active={silenceFormStore.tab.current === "editor"}
|
||||
onClick={() => silenceFormStore.tab.setTab("editor")}
|
||||
/>
|
||||
<Tab
|
||||
title="Browse"
|
||||
active={silenceFormStore.tab.current === "browser"}
|
||||
onClick={() => silenceFormStore.tab.setTab("browser")}
|
||||
/>
|
||||
<button type="button" className="close" onClick={onHide}>
|
||||
<span>×</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div
|
||||
className={`modal-body ${
|
||||
silenceFormStore.toggle.blurred ? "modal-content-blur" : ""
|
||||
}`}
|
||||
>
|
||||
{silenceFormStore.tab.current === "editor" ? (
|
||||
silenceFormStore.data.currentStage === "form" ? (
|
||||
alertStore.data.upstreams.instances.length > 0 ? (
|
||||
Object.keys(alertStore.data.clustersWithoutReadOnly).length >
|
||||
0 ? (
|
||||
<SilenceForm
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
previewOpen={previewOpen}
|
||||
/>
|
||||
) : (
|
||||
<ReadOnlyPlaceholder />
|
||||
)
|
||||
) : (
|
||||
<h1 className="text-center display-1 text-placeholder p-5 m-auto">
|
||||
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
|
||||
</h1>
|
||||
)
|
||||
) : silenceFormStore.data.currentStage === "preview" ? (
|
||||
<SilencePreview
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
previewOpen={previewOpen}
|
||||
/>
|
||||
) : (
|
||||
<ReadOnlyPlaceholder />
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<h1 className="text-center display-1 text-placeholder p-5 m-auto">
|
||||
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
|
||||
</h1>
|
||||
)
|
||||
) : silenceFormStore.data.currentStage === "preview" ? (
|
||||
<SilencePreview
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
) : (
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
{silenceFormStore.tab.current === "browser" ? (
|
||||
<Browser
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
));
|
||||
) : null}
|
||||
{silenceFormStore.tab.current === "browser" ? (
|
||||
<Browser
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
settingsStore={settingsStore}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Observer>
|
||||
);
|
||||
};
|
||||
|
||||
export { SilenceModalContent };
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
|
||||
|
||||
const MultiClusterStatus: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore, alertStore }) => {
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
<table className="table table-borderless">
|
||||
<tbody>
|
||||
{Object.values(silenceFormStore.data.requestsByCluster).map(
|
||||
(clusterRequest) => (
|
||||
<tr key={clusterRequest.cluster}>
|
||||
<td className="align-middle" style={{ width: "1%" }}>
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="text-danger"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="text-success"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<SilenceSubmitProgress
|
||||
key={clusterRequest.cluster}
|
||||
cluster={clusterRequest.cluster}
|
||||
members={clusterRequest.members}
|
||||
payload={silenceFormStore.data.toAlertmanagerPayload}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">{clusterRequest.cluster}</td>
|
||||
<td>
|
||||
<div
|
||||
className={`rounded text-center ${
|
||||
clusterRequest.isDone && clusterRequest.error
|
||||
? "bg-light"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
clusterRequest.error
|
||||
) : (
|
||||
<a
|
||||
href={clusterRequest.silenceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{clusterRequest.silenceID}
|
||||
</a>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(MultiClusterStatus);
|
||||
@@ -4,11 +4,9 @@ import { shallow } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore, NewClusterRequest } from "Stores/SilenceFormStore";
|
||||
import {
|
||||
SilenceSubmitController,
|
||||
MultiClusterStatus,
|
||||
SingleClusterStatus,
|
||||
} from "./SilenceSubmitController";
|
||||
import SilenceSubmitController from "./SilenceSubmitController";
|
||||
import SingleClusterStatus from "./SingleClusterStatus";
|
||||
import MultiClusterStatus from "./MultiClusterStatus";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
|
||||
@@ -1,144 +1,20 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons/faArrowLeft";
|
||||
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
|
||||
|
||||
const SingleClusterStatus: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore, alertStore }) => {
|
||||
const clusterRequest = Object.values(
|
||||
silenceFormStore.data.requestsByCluster
|
||||
)[0];
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="text-center">
|
||||
<div className="display-1 mb-3">
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="text-danger"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="text-success" />
|
||||
)
|
||||
) : (
|
||||
<SilenceSubmitProgress
|
||||
key={clusterRequest.cluster}
|
||||
cluster={clusterRequest.cluster}
|
||||
members={clusterRequest.members}
|
||||
payload={silenceFormStore.data.toAlertmanagerPayload}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="badge badge-primary">{clusterRequest.cluster}</div>
|
||||
{clusterRequest.isDone ? (
|
||||
<p
|
||||
className={`mt-2 rounded text-center ${
|
||||
clusterRequest.isDone && clusterRequest.error ? "bg-light" : ""
|
||||
}`}
|
||||
>
|
||||
{clusterRequest.error ? (
|
||||
clusterRequest.error
|
||||
) : (
|
||||
<a
|
||||
href={clusterRequest.silenceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{clusterRequest.silenceID}
|
||||
</a>
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
));
|
||||
};
|
||||
|
||||
const MultiClusterStatus: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore, alertStore }) => {
|
||||
return useObserver(() => (
|
||||
<div className="table-responsive">
|
||||
<table className="table table-borderless">
|
||||
<tbody>
|
||||
{Object.values(silenceFormStore.data.requestsByCluster).map(
|
||||
(clusterRequest) => (
|
||||
<tr key={clusterRequest.cluster}>
|
||||
<td className="align-middle" style={{ width: "1%" }}>
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="text-danger"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="text-success"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<SilenceSubmitProgress
|
||||
key={clusterRequest.cluster}
|
||||
cluster={clusterRequest.cluster}
|
||||
members={clusterRequest.members}
|
||||
payload={silenceFormStore.data.toAlertmanagerPayload}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">{clusterRequest.cluster}</td>
|
||||
<td>
|
||||
<div
|
||||
className={`rounded text-center ${
|
||||
clusterRequest.isDone && clusterRequest.error
|
||||
? "bg-light"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
clusterRequest.error
|
||||
) : (
|
||||
<a
|
||||
href={clusterRequest.silenceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{clusterRequest.silenceID}
|
||||
</a>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
import SingleClusterStatus from "./SingleClusterStatus";
|
||||
import MultiClusterStatus from "./MultiClusterStatus";
|
||||
|
||||
const SilenceSubmitController: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore, alertStore }) => {
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
{Object.keys(silenceFormStore.data.requestsByCluster).length === 1 ? (
|
||||
<SingleClusterStatus
|
||||
@@ -162,7 +38,7 @@ const SilenceSubmitController: FC<{
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { SilenceSubmitController, MultiClusterStatus, SingleClusterStatus };
|
||||
export default observer(SilenceSubmitController);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
|
||||
|
||||
const SingleClusterStatus: FC<{
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
}> = ({ silenceFormStore, alertStore }) => {
|
||||
const clusterRequest = Object.values(
|
||||
silenceFormStore.data.requestsByCluster
|
||||
)[0];
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className="display-1 mb-3">
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="text-danger"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="text-success" />
|
||||
)
|
||||
) : (
|
||||
<SilenceSubmitProgress
|
||||
key={clusterRequest.cluster}
|
||||
cluster={clusterRequest.cluster}
|
||||
members={clusterRequest.members}
|
||||
payload={silenceFormStore.data.toAlertmanagerPayload}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="badge badge-primary">{clusterRequest.cluster}</div>
|
||||
{clusterRequest.isDone ? (
|
||||
<p
|
||||
className={`mt-2 rounded text-center ${
|
||||
clusterRequest.isDone && clusterRequest.error ? "bg-light" : ""
|
||||
}`}
|
||||
>
|
||||
{clusterRequest.error ? (
|
||||
clusterRequest.error
|
||||
) : (
|
||||
<a
|
||||
href={clusterRequest.silenceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{clusterRequest.silenceID}
|
||||
</a>
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(SingleClusterStatus);
|
||||
@@ -10,7 +10,7 @@ import { ThemeContext } from "Components/Theme";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { SilenceModal } from ".";
|
||||
import SilenceModal from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let settingsStore: Settings;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
|
||||
@@ -24,7 +24,7 @@ const SilenceModal: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, silenceFormStore, settingsStore }) => {
|
||||
return useObserver(() => (
|
||||
return (
|
||||
<React.Fragment>
|
||||
<li
|
||||
className={`nav-item components-navbar-button ${
|
||||
@@ -65,7 +65,7 @@ const SilenceModal: FC<{
|
||||
</React.Suspense>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export { SilenceModal };
|
||||
export default observer(SilenceModal);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { mount } from "enzyme";
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { AppToasts } from "./AppToasts";
|
||||
import AppToasts from "./AppToasts";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
|
||||
@@ -17,7 +17,7 @@ describe("<AppToasts />", () => {
|
||||
it("doesn't render anything when alertStore.info.upgradeNeeded=true", () => {
|
||||
alertStore.info.upgradeNeeded = true;
|
||||
const tree = mount(<AppToasts alertStore={alertStore} />);
|
||||
expect(tree.html()).toBeNull();
|
||||
expect(tree.html()).toBe("");
|
||||
});
|
||||
|
||||
it("renders upstream error toasts for each unhealthy upstream", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp";
|
||||
import { faExclamation } from "@fortawesome/free-solid-svg-icons/faExclamation";
|
||||
@@ -12,35 +12,33 @@ import { ToastMessage, UpgradeToastMessage } from "./ToastMessages";
|
||||
const AppToasts: FC<{
|
||||
alertStore: AlertStore;
|
||||
}> = ({ alertStore }) => {
|
||||
return useObserver(() =>
|
||||
alertStore.info.upgradeNeeded ? null : (
|
||||
<ToastContainer>
|
||||
{alertStore.data.upstreams.instances
|
||||
.filter((upstream) => upstream.error !== "")
|
||||
.map((upstream) => (
|
||||
<Toast
|
||||
key={upstream.name}
|
||||
icon={faExclamation}
|
||||
iconClass="text-danger"
|
||||
message={
|
||||
<ToastMessage
|
||||
title={`Alertmanager ${upstream.name} raised an error`}
|
||||
message={upstream.error}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{alertStore.info.upgradeReady ? (
|
||||
return alertStore.info.upgradeNeeded ? null : (
|
||||
<ToastContainer>
|
||||
{alertStore.data.upstreams.instances
|
||||
.filter((upstream) => upstream.error !== "")
|
||||
.map((upstream) => (
|
||||
<Toast
|
||||
key="upgrade"
|
||||
icon={faArrowUp}
|
||||
iconClass="text-success"
|
||||
message={<UpgradeToastMessage alertStore={alertStore} />}
|
||||
key={upstream.name}
|
||||
icon={faExclamation}
|
||||
iconClass="text-danger"
|
||||
message={
|
||||
<ToastMessage
|
||||
title={`Alertmanager ${upstream.name} raised an error`}
|
||||
message={upstream.error}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</ToastContainer>
|
||||
)
|
||||
))}
|
||||
{alertStore.info.upgradeReady ? (
|
||||
<Toast
|
||||
key="upgrade"
|
||||
icon={faArrowUp}
|
||||
iconClass="text-success"
|
||||
message={<UpgradeToastMessage alertStore={alertStore} />}
|
||||
/>
|
||||
) : null}
|
||||
</ToastContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export { AppToasts };
|
||||
export default observer(AppToasts);
|
||||
|
||||
Reference in New Issue
Block a user