fix(ui): add notifications icon when upstreams are failing

This commit is contained in:
Łukasz Mierzwa
2021-02-27 14:42:03 +00:00
committed by Łukasz Mierzwa
parent 41c63e4470
commit 0539b5b1d0
13 changed files with 265 additions and 93 deletions
+4
View File
@@ -8,6 +8,10 @@
- Easily change multi-grid source label via quick access dropdown on the grid
header.
### Changed
- Reworked how notifications are displayed.
## v0.79
### Fixed
-2
View File
@@ -28,7 +28,6 @@ import "Styles/App.scss";
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>;
@@ -131,7 +130,6 @@ const App: FunctionComponent<AppProps> = observer(
settingsStore={settingsStore}
silenceFormStore={silenceFormStore}
/>
<AppToasts alertStore={alertStore} />
<FaviconBadge alertStore={alertStore} />
</React.Suspense>
</ThemeContext.Provider>
+2 -2
View File
@@ -29,7 +29,7 @@ const MainModal: FC<{
<li
className={`nav-item components-navbar-button ${
isVisible ? "border-info" : ""
}`}
} ml-auto`}
>
<TooltipWrapper title="Settings">
<span
@@ -37,7 +37,7 @@ const MainModal: FC<{
className="nav-link cursor-pointer"
onClick={toggle}
>
<FontAwesomeIcon icon={faCog} />
<FontAwesomeIcon icon={faCog} fixedWidth />
</span>
</TooltipWrapper>
</li>
@@ -147,7 +147,11 @@ const FilterInput: FC<{
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}>
<form
className="form-inline flex-grow-1 flex-shrink-1 mr-auto"
style={{ minWidth: "0px" }}
onSubmit={onSubmit}
>
<div
ref={formRef}
className={`input-group w-100 mr-2 components-filterinput-outer ${
-25
View File
@@ -59,15 +59,6 @@ const MountedNavbar = (fixedTop?: boolean) => {
);
};
const ValidateNavClass = (totalFilters: number, expectedClass: string) => {
for (let i = 0; i < totalFilters; i++) {
alertStore.filters.addFilter(`foo=${i}`);
}
const tree = MountedNavbar();
const nav = tree.find("ul.navbar-nav");
expect((nav.props().className as string).split(" ")).toContain(expectedClass);
};
describe("<NavBar />", () => {
it("navbar-brand shows 15 alerts with totalAlerts=15", () => {
alertStore.info.setTotalAlerts(15);
@@ -76,22 +67,6 @@ describe("<NavBar />", () => {
expect(brand.text()).toBe("15");
});
it("navbar-nav includes 'flex-row' class with 0 filters", () => {
ValidateNavClass(0, "flex-row");
});
it("navbar-nav includes 'flex-row' class with 1 filter", () => {
ValidateNavClass(1, "flex-column");
});
it("navbar-nav includes 'flex-column' class with 2 filters", () => {
ValidateNavClass(2, "flex-column");
});
it("navbar-nav includes 'flex-column' class with 3 filters", () => {
ValidateNavClass(3, "flex-column");
});
it("navbar includes 'fixed-top' class by default", () => {
const tree = MountedNavbar();
const nav = tree.find(".navbar");
+7 -11
View File
@@ -16,6 +16,7 @@ import { IsMobile } from "Common/Device";
import { OverviewModal } from "Components/OverviewModal";
import { MainModal } from "Components/MainModal";
import SilenceModal from "Components/SilenceModal";
import AppToasts from "Components/Toast/AppToasts";
import { ThemeContext } from "Components/Theme";
import { Fetcher } from "Components/Fetcher";
import { FilterInput } from "./FilterInput";
@@ -102,23 +103,19 @@ const NavBar: FC<{
>
<nav
ref={ref}
className={`navbar navbar-expand navbar-dark p-1 bg-primary-transparent d-inline-block ${
className={`navbar navbar-expand navbar-dark p-1 bg-primary-transparent d-flex ${
fixedTop ? "fixed-top" : "w-100"
}`}
} align-items-start`}
>
<span className="navbar-nav float-left d-flex flex-row">
<span className="navbar-nav d-flex flex-row">
<span className="navbar-brand p-0 my-0 mx-2 h1 d-none d-sm-block">
<OverviewModal alertStore={alertStore} />
</span>
<Fetcher alertStore={alertStore} settingsStore={settingsStore} />
</span>
<ul
className={`navbar-nav float-right d-flex ${
alertStore.filters.values.length >= 1
? "flex-column flex-sm-row flex-md-row flex-lg-row flex-xl-row"
: "flex-row"
}`}
>
<FilterInput alertStore={alertStore} settingsStore={settingsStore} />
<ul className="navbar-nav flex-wrap flex-shrink-1">
<AppToasts alertStore={alertStore} />
<SilenceModal
alertStore={alertStore}
silenceFormStore={silenceFormStore}
@@ -126,7 +123,6 @@ const NavBar: FC<{
/>
<MainModal alertStore={alertStore} settingsStore={settingsStore} />
</ul>
<FilterInput alertStore={alertStore} settingsStore={settingsStore} />
</nav>
</CSSTransition>
</div>
+2 -2
View File
@@ -29,7 +29,7 @@ const SilenceModal: FC<{
<li
className={`nav-item components-navbar-button ${
silenceFormStore.toggle.visible ? "border-info" : ""
}`}
} ml-auto`}
>
<TooltipWrapper title="New silence">
<span
@@ -37,7 +37,7 @@ const SilenceModal: FC<{
className="nav-link cursor-pointer"
onClick={silenceFormStore.toggle.toggle}
>
<FontAwesomeIcon icon={faBellSlash} />
<FontAwesomeIcon icon={faBellSlash} fixedWidth />
</span>
</TooltipWrapper>
</li>
+83 -5
View File
@@ -1,5 +1,7 @@
import React from "react";
import { act } from "react-dom/test-utils";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
@@ -13,6 +15,51 @@ beforeEach(() => {
alertStore = new AlertStore([]);
});
const makeErrors = () => {
alertStore.data.setUpstreams({
counters: { total: 3, healthy: 1, failed: 2 },
instances: [
{
name: "am1",
cluster: "am",
clusterMembers: ["am1"],
uri: "http://am1",
publicURI: "http://am1",
error: "error 1",
version: "0.21.0",
readonly: false,
corsCredentials: "include",
headers: {},
},
{
name: "am2",
cluster: "am",
clusterMembers: ["am2"],
uri: "file:///mock",
publicURI: "file:///mock",
error: "",
version: "0.21.0",
readonly: false,
corsCredentials: "include",
headers: {},
},
{
name: "am3",
cluster: "am",
clusterMembers: ["am3"],
uri: "http://am3",
publicURI: "http://am3",
error: "error 2",
version: "0.21.0",
readonly: false,
corsCredentials: "include",
headers: {},
},
],
clusters: { am1: ["am1"], am2: ["am2"], am3: ["am3"] },
});
};
describe("<AppToasts />", () => {
it("doesn't render anything when alertStore.info.upgradeNeeded=true", () => {
alertStore.info.upgradeNeeded = true;
@@ -20,9 +67,25 @@ describe("<AppToasts />", () => {
expect(tree.html()).toBe("");
});
it("doesn't render anything when there are no notifications to show", () => {
const tree = mount(<AppToasts alertStore={alertStore} />);
expect(tree.html()).toBe("");
});
it("renders upstream error toasts for each unhealthy upstream", () => {
makeErrors();
const tree = mount(<AppToasts alertStore={alertStore} />);
expect(tree.find("Toast")).toHaveLength(2);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("removes notifications when upstream recovers", () => {
makeErrors();
const tree = mount(<AppToasts alertStore={alertStore} />);
expect(tree.find("Toast")).toHaveLength(2);
alertStore.data.setUpstreams({
counters: { total: 3, healthy: 1, failed: 2 },
counters: { total: 3, healthy: 3, failed: 0 },
instances: [
{
name: "am1",
@@ -30,7 +93,7 @@ describe("<AppToasts />", () => {
clusterMembers: ["am1"],
uri: "http://am1",
publicURI: "http://am1",
error: "error 1",
error: "",
version: "0.21.0",
readonly: false,
corsCredentials: "include",
@@ -54,7 +117,7 @@ describe("<AppToasts />", () => {
clusterMembers: ["am3"],
uri: "http://am3",
publicURI: "http://am3",
error: "error 2",
error: "",
version: "0.21.0",
readonly: false,
corsCredentials: "include",
@@ -63,9 +126,24 @@ describe("<AppToasts />", () => {
],
clusters: { am1: ["am1"], am2: ["am2"], am3: ["am3"] },
});
tree.update();
expect(tree.find("Toast")).toHaveLength(0);
});
it("clicking navbar icon toggles all notifications", () => {
makeErrors();
alertStore.info.upgradeNeeded = false;
alertStore.info.upgradeReady = false;
const tree = mount(<AppToasts alertStore={alertStore} />);
expect(tree.find("Toast")).toHaveLength(2);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
expect(tree.find("div.bg-toast")).toHaveLength(2);
expect(tree.find("span.badge.cursor-pointer.with-click")).toHaveLength(2);
tree.find("span.badge.cursor-pointer.with-click").at(1).simulate("click");
expect(tree.find("div.bg-toast")).toHaveLength(1);
tree.find("span#components-notifications").simulate("click");
tree.update();
expect(tree.find("div.bg-toast")).toHaveLength(2);
});
it("renders UpgradeToastMessage when alertStore.info.upgradeReady=true", () => {
+45 -15
View File
@@ -1,22 +1,51 @@
import React, { FC } from "react";
import React, { FC, Fragment, useCallback } from "react";
import { observer } from "mobx-react-lite";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp";
import { faExclamation } from "@fortawesome/free-solid-svg-icons/faExclamation";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
import { AlertStore } from "Stores/AlertStore";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { ToastContainer, Toast } from ".";
import { ToastMessage, UpgradeToastMessage } from "./ToastMessages";
const AppToasts: FC<{
alertStore: AlertStore;
}> = ({ alertStore }) => {
return alertStore.info.upgradeNeeded ? null : (
<ToastContainer>
{alertStore.data.upstreams.instances
.filter((upstream) => upstream.error !== "")
.map((upstream) => (
const show = useCallback(() => {
const e = new CustomEvent("showNotifications");
window.dispatchEvent(e);
}, []);
if (alertStore.info.upgradeNeeded) {
return null;
}
if (
alertStore.data.upstreamsWithErrors.length === 0 &&
alertStore.info.upgradeReady === false
) {
return null;
}
return (
<Fragment>
<li className="nav-item components-navbar-button ml-auto">
<TooltipWrapper title="Show all notifications">
<span
id="components-notifications"
className="nav-link cursor-pointer"
onClick={show}
>
<FontAwesomeIcon icon={faInfoCircle} fixedWidth />
</span>
</TooltipWrapper>
</li>
<ToastContainer>
{alertStore.data.upstreamsWithErrors.map((upstream) => (
<Toast
key={upstream.name}
icon={faExclamation}
@@ -29,15 +58,16 @@ const AppToasts: FC<{
}
/>
))}
{alertStore.info.upgradeReady ? (
<Toast
key="upgrade"
icon={faArrowUp}
iconClass="text-success"
message={<UpgradeToastMessage alertStore={alertStore} />}
/>
) : null}
</ToastContainer>
{alertStore.info.upgradeReady ? (
<Toast
key="upgrade"
icon={faArrowUp}
iconClass="text-success"
message={<UpgradeToastMessage alertStore={alertStore} />}
/>
) : null}
</ToastContainer>
</Fragment>
);
};
@@ -2,6 +2,28 @@
exports[`<AppToasts /> renders UpgradeToastMessage when alertStore.info.upgradeReady=true 1`] = `
"
<li class=\\"nav-item components-navbar-button ml-auto\\">
<div class=\\" tooltip-trigger\\">
<span id=\\"components-notifications\\"
class=\\"nav-link cursor-pointer\\"
>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"info-circle\\"
class=\\"svg-inline--fa fa-info-circle fa-w-16 fa-fw \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 512 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\\"
>
</path>
</svg>
</span>
</div>
</li>
<div class=\\"toast-container d-flex flex-column\\">
<div class=\\"m-1 bg-toast text-white rounded shadow components-animation-fade-appear components-animation-fade-appear-active\\">
<div class=\\"d-flex flex-row p-1\\">
@@ -78,19 +100,18 @@ exports[`<AppToasts /> renders UpgradeToastMessage when alertStore.info.upgradeR
</div>
</div>
<div class=\\"flex-shrink-0 flex-grow-0 align-self-top\\">
<span class=\\"badge components-label with-click with-click-dark\\">
<span class=\\"badge components-label cursor-pointer with-click with-click-dark\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"chevron-down\\"
class=\\"svg-inline--fa fa-chevron-down fa-w-14 cursor-pointer m-2\\"
data-icon=\\"times\\"
class=\\"svg-inline--fa fa-times fa-w-11 \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 448 512\\"
style=\\"transition: transform 0.25s ease-in-out;\\"
viewbox=\\"0 0 352 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
d=\\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\\"
>
</path>
</svg>
@@ -104,6 +125,28 @@ exports[`<AppToasts /> renders UpgradeToastMessage when alertStore.info.upgradeR
exports[`<AppToasts /> renders upstream error toasts for each unhealthy upstream 1`] = `
"
<li class=\\"nav-item components-navbar-button ml-auto\\">
<div class=\\" tooltip-trigger\\">
<span id=\\"components-notifications\\"
class=\\"nav-link cursor-pointer\\"
>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"info-circle\\"
class=\\"svg-inline--fa fa-info-circle fa-w-16 fa-fw \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 512 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\\"
>
</path>
</svg>
</span>
</div>
</li>
<div class=\\"toast-container d-flex flex-column\\">
<div class=\\"m-1 bg-toast text-white rounded shadow components-animation-fade-appear components-animation-fade-appear-active\\">
<div class=\\"d-flex flex-row p-1\\">
@@ -150,19 +193,18 @@ exports[`<AppToasts /> renders upstream error toasts for each unhealthy upstream
</div>
</div>
<div class=\\"flex-shrink-0 flex-grow-0 align-self-top\\">
<span class=\\"badge components-label with-click with-click-dark\\">
<span class=\\"badge components-label cursor-pointer with-click with-click-dark\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"chevron-down\\"
class=\\"svg-inline--fa fa-chevron-down fa-w-14 cursor-pointer m-2\\"
data-icon=\\"times\\"
class=\\"svg-inline--fa fa-times fa-w-11 \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 448 512\\"
style=\\"transition: transform 0.25s ease-in-out;\\"
viewbox=\\"0 0 352 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
d=\\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\\"
>
</path>
</svg>
@@ -215,19 +257,18 @@ exports[`<AppToasts /> renders upstream error toasts for each unhealthy upstream
</div>
</div>
<div class=\\"flex-shrink-0 flex-grow-0 align-self-top\\">
<span class=\\"badge components-label with-click with-click-dark\\">
<span class=\\"badge components-label cursor-pointer with-click with-click-dark\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"chevron-down\\"
class=\\"svg-inline--fa fa-chevron-down fa-w-14 cursor-pointer m-2\\"
data-icon=\\"times\\"
class=\\"svg-inline--fa fa-times fa-w-11 \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 448 512\\"
style=\\"transition: transform 0.25s ease-in-out;\\"
viewbox=\\"0 0 352 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
d=\\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\\"
>
</path>
</svg>
+34 -3
View File
@@ -1,5 +1,7 @@
import React from "react";
import { act } from "react-dom/test-utils";
import { mount } from "enzyme";
import toDiffableHtml from "diffable-html";
@@ -20,7 +22,7 @@ describe("<Toast />", () => {
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
});
it("toggles body on toggle icon click", () => {
it("hides body on close icon click", () => {
const tree = mount(
<Toast
icon={faExclamation}
@@ -30,10 +32,39 @@ describe("<Toast />", () => {
);
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
tree.find("svg.cursor-pointer").simulate("click");
tree.find("span.badge.cursor-pointer").simulate("click");
expect(toDiffableHtml(tree.html())).not.toMatch(/fake error/);
});
it("shows hidden body on showNotifications event", () => {
const tree = mount(
<Toast
icon={faExclamation}
iconClass="text-danger"
message="fake error"
/>
);
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
tree.find("span.badge.cursor-pointer").simulate("click");
expect(toDiffableHtml(tree.html())).not.toMatch(/fake error/);
tree.find("svg.cursor-pointer").simulate("click");
const e = new CustomEvent("showNotifications");
act(() => {
window.dispatchEvent(e);
});
tree.update();
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
});
it("unmounts cleanly", () => {
const tree = mount(
<Toast
icon={faExclamation}
iconClass="text-danger"
message="fake error"
/>
);
tree.unmount();
});
});
+18 -9
View File
@@ -1,4 +1,4 @@
import React, { FC, ReactNode, useState } from "react";
import React, { FC, ReactNode, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import TransitionGroup from "react-transition-group/TransitionGroup";
@@ -7,9 +7,9 @@ import { CSSTransition } from "react-transition-group";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircle } from "@fortawesome/free-solid-svg-icons/faCircle";
import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes";
import { ThemeContext } from "Components/Theme";
import { ToggleIcon } from "Components/ToggleIcon";
const Toast: FC<{
icon: IconDefinition;
@@ -18,6 +18,16 @@ const Toast: FC<{
}> = ({ icon, iconClass, message }) => {
const [isOpen, setIsOpen] = useState(true);
useEffect(() => {
const show = () => setIsOpen(true);
window.addEventListener("showNotifications", show);
return () => {
window.removeEventListener("showNotifications", show);
};
}, []);
if (!isOpen) return null;
return (
<div className="m-1 bg-toast text-white rounded shadow">
<div className="d-flex flex-row p-1">
@@ -29,15 +39,14 @@ const Toast: FC<{
<FontAwesomeIcon icon={icon} className="fa-stack-1x text-white" />
</div>
<div className="flex-shrink-1 flex-grow-1 align-self-center ml-1 mr-3 text-break text-wrap">
{isOpen ? message : null}
{message}
</div>
<div className="flex-shrink-0 flex-grow-0 align-self-top">
<span className="badge components-label with-click with-click-dark">
<ToggleIcon
isOpen={isOpen}
className={`cursor-pointer ${isOpen ? "m-2" : "mr-2"}`}
onClick={() => setIsOpen((v) => !v)}
/>
<span
className="badge components-label cursor-pointer with-click with-click-dark"
onClick={() => setIsOpen(false)}
>
<FontAwesomeIcon icon={faTimes} />
</span>
</div>
</div>
+6
View File
@@ -152,6 +152,7 @@ interface AlertStoreDataT {
setUpstreams: (u: APIAlertsResponseUpstreamsT) => void;
setInstances: (i: APIAlertmanagerUpstreamT[]) => void;
setClusters: (c: APIAlertsResponseUpstreamsClusterMapT) => void;
readonly upstreamsWithErrors: APIAlertmanagerUpstreamT[];
}
interface AlertStoreInfoT {
@@ -343,6 +344,11 @@ class AlertStore {
setClusters(c: APIAlertsResponseUpstreamsClusterMapT) {
this.upstreams.clusters = c;
},
get upstreamsWithErrors(): APIAlertmanagerUpstreamT[] {
return this.upstreams.instances.filter(
(upstream) => upstream.error !== ""
);
},
},
{
gridPadding: computed,