change(ui): modal context and alert form
This commit is contained in:
parent
2ea07b045a
commit
56a46b1ea5
7 changed files with 703 additions and 578 deletions
|
|
@ -1,193 +1,196 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { Loader } from 'UI';
|
||||
import { fetchUserInfo, setJwt } from 'Duck/user';
|
||||
import { fetchList as fetchSiteList } from 'Duck/site';
|
||||
import { withStore } from 'App/mstore';
|
||||
import { Map } from 'immutable';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
||||
import {connect, ConnectedProps} from 'react-redux';
|
||||
import {Loader} from 'UI';
|
||||
import {fetchUserInfo, setJwt} from 'Duck/user';
|
||||
import {fetchList as fetchSiteList} from 'Duck/site';
|
||||
import {withStore} from 'App/mstore';
|
||||
import {Map} from 'immutable';
|
||||
|
||||
import * as routes from './routes';
|
||||
import { fetchTenants } from 'Duck/user';
|
||||
import { setSessionPath } from 'Duck/sessions';
|
||||
import { ModalProvider } from 'Components/Modal';
|
||||
import { GLOBAL_DESTINATION_PATH, IFRAME, JWT_PARAM } from 'App/constants/storageKeys';
|
||||
import {fetchTenants} from 'Duck/user';
|
||||
import {setSessionPath} from 'Duck/sessions';
|
||||
import {ModalProvider} from 'Components/Modal';
|
||||
import {GLOBAL_DESTINATION_PATH, IFRAME, JWT_PARAM} from 'App/constants/storageKeys';
|
||||
import PublicRoutes from 'App/PublicRoutes';
|
||||
import Layout from 'App/layout/Layout';
|
||||
import { fetchListActive as fetchMetadata } from 'Duck/customField';
|
||||
import { init as initSite } from 'Duck/site';
|
||||
import {fetchListActive as fetchMetadata} from 'Duck/customField';
|
||||
import {init as initSite} from 'Duck/site';
|
||||
import PrivateRoutes from 'App/PrivateRoutes';
|
||||
import { checkParam } from 'App/utils';
|
||||
import {checkParam} from 'App/utils';
|
||||
import IFrameRoutes from 'App/IFrameRoutes';
|
||||
import {ModalProvider as NewModalProvider} from 'Components/ModalContext';
|
||||
|
||||
interface RouterProps extends RouteComponentProps, ConnectedProps<typeof connector> {
|
||||
isLoggedIn: boolean;
|
||||
sites: Map<string, any>;
|
||||
loading: boolean;
|
||||
changePassword: boolean;
|
||||
isEnterprise: boolean;
|
||||
fetchUserInfo: () => any;
|
||||
fetchTenants: () => any;
|
||||
setSessionPath: (path: any) => any;
|
||||
fetchSiteList: (siteId?: number) => any;
|
||||
match: {
|
||||
params: {
|
||||
siteId: string;
|
||||
}
|
||||
};
|
||||
mstore: any;
|
||||
setJwt: (jwt: string) => any;
|
||||
fetchMetadata: (siteId: string) => void;
|
||||
initSite: (site: any) => void;
|
||||
isLoggedIn: boolean;
|
||||
sites: Map<string, any>;
|
||||
loading: boolean;
|
||||
changePassword: boolean;
|
||||
isEnterprise: boolean;
|
||||
fetchUserInfo: () => any;
|
||||
fetchTenants: () => any;
|
||||
setSessionPath: (path: any) => any;
|
||||
fetchSiteList: (siteId?: number) => any;
|
||||
match: {
|
||||
params: {
|
||||
siteId: string;
|
||||
}
|
||||
};
|
||||
mstore: any;
|
||||
setJwt: (jwt: string) => any;
|
||||
fetchMetadata: (siteId: string) => void;
|
||||
initSite: (site: any) => void;
|
||||
}
|
||||
|
||||
const Router: React.FC<RouterProps> = (props) => {
|
||||
const {
|
||||
isLoggedIn,
|
||||
siteId,
|
||||
sites,
|
||||
loading,
|
||||
location,
|
||||
fetchUserInfo,
|
||||
fetchSiteList,
|
||||
history,
|
||||
match: { params: { siteId: siteIdFromPath } },
|
||||
setSessionPath,
|
||||
} = props;
|
||||
const [isIframe, setIsIframe] = React.useState(false);
|
||||
const [isJwt, setIsJwt] = React.useState(false);
|
||||
const {
|
||||
isLoggedIn,
|
||||
siteId,
|
||||
sites,
|
||||
loading,
|
||||
location,
|
||||
fetchUserInfo,
|
||||
fetchSiteList,
|
||||
history,
|
||||
match: {params: {siteId: siteIdFromPath}},
|
||||
setSessionPath,
|
||||
} = props;
|
||||
const [isIframe, setIsIframe] = React.useState(false);
|
||||
const [isJwt, setIsJwt] = React.useState(false);
|
||||
|
||||
const handleJwtFromUrl = () => {
|
||||
const urlJWT = new URLSearchParams(location.search).get('jwt');
|
||||
if (urlJWT) {
|
||||
props.setJwt(urlJWT);
|
||||
const handleJwtFromUrl = () => {
|
||||
const urlJWT = new URLSearchParams(location.search).get('jwt');
|
||||
if (urlJWT) {
|
||||
props.setJwt(urlJWT);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDestinationPath = () => {
|
||||
if (!isLoggedIn && location.pathname !== routes.login()) {
|
||||
localStorage.setItem(GLOBAL_DESTINATION_PATH, location.pathname + location.search);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUserLogin = async () => {
|
||||
await fetchUserInfo();
|
||||
const siteIdFromPath = parseInt(location.pathname.split('/')[1]);
|
||||
await fetchSiteList(siteIdFromPath);
|
||||
props.mstore.initClient();
|
||||
|
||||
const destinationPath = localStorage.getItem(GLOBAL_DESTINATION_PATH);
|
||||
if (
|
||||
destinationPath &&
|
||||
destinationPath !== routes.login() &&
|
||||
destinationPath !== routes.signup() &&
|
||||
destinationPath !== '/'
|
||||
) {
|
||||
const url = new URL(destinationPath, window.location.origin);
|
||||
checkParams(url.search)
|
||||
history.push(destinationPath);
|
||||
localStorage.removeItem(GLOBAL_DESTINATION_PATH);
|
||||
}
|
||||
};
|
||||
|
||||
const checkParams = (search?: string) => {
|
||||
const _isIframe = checkParam('iframe', IFRAME, search);
|
||||
const _isJwt = checkParam('jwt', JWT_PARAM, search);
|
||||
setIsIframe(_isIframe);
|
||||
setIsJwt(_isJwt);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDestinationPath = () => {
|
||||
if (!isLoggedIn && location.pathname !== routes.login()) {
|
||||
localStorage.setItem(GLOBAL_DESTINATION_PATH, location.pathname + location.search);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUserLogin = async () => {
|
||||
await fetchUserInfo();
|
||||
const siteIdFromPath = parseInt(location.pathname.split('/')[1]);
|
||||
await fetchSiteList(siteIdFromPath);
|
||||
props.mstore.initClient();
|
||||
|
||||
const destinationPath = localStorage.getItem(GLOBAL_DESTINATION_PATH);
|
||||
if (
|
||||
destinationPath &&
|
||||
destinationPath !== routes.login() &&
|
||||
destinationPath !== routes.signup() &&
|
||||
destinationPath !== '/'
|
||||
) {
|
||||
const url = new URL(destinationPath, window.location.origin);
|
||||
checkParams(url.search)
|
||||
history.push(destinationPath);
|
||||
localStorage.removeItem(GLOBAL_DESTINATION_PATH);
|
||||
}
|
||||
};
|
||||
|
||||
const checkParams = (search?: string) => {
|
||||
const _isIframe = checkParam('iframe', IFRAME, search);
|
||||
const _isJwt = checkParam('jwt', JWT_PARAM, search);
|
||||
setIsIframe(_isIframe);
|
||||
setIsJwt(_isJwt);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkParams();
|
||||
handleJwtFromUrl();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// handleJwtFromUrl();
|
||||
handleDestinationPath();
|
||||
|
||||
|
||||
setSessionPath(previousLocation ? previousLocation : location);
|
||||
}, [location]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevIsLoggedIn !== isLoggedIn && isLoggedIn) {
|
||||
handleUserLogin();
|
||||
}
|
||||
}, [isLoggedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (siteId && siteId !== lastFetchedSiteIdRef.current) {
|
||||
const activeSite = sites.find((s) => s.id == siteId);
|
||||
props.initSite(activeSite);
|
||||
props.fetchMetadata(siteId);
|
||||
lastFetchedSiteIdRef.current = siteId;
|
||||
}
|
||||
}, [siteId]);
|
||||
|
||||
const lastFetchedSiteIdRef = useRef<any>(null);
|
||||
|
||||
function usePrevious(value: any) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]);
|
||||
return ref.current;
|
||||
}
|
||||
checkParams();
|
||||
handleJwtFromUrl();
|
||||
}, []);
|
||||
|
||||
const prevIsLoggedIn = usePrevious(isLoggedIn);
|
||||
const previousLocation = usePrevious(location);
|
||||
useEffect(() => {
|
||||
// handleJwtFromUrl();
|
||||
handleDestinationPath();
|
||||
|
||||
const hideHeader = (location.pathname && location.pathname.includes('/session/')) ||
|
||||
location.pathname.includes('/assist/') || location.pathname.includes('multiview');
|
||||
|
||||
if (isIframe) {
|
||||
return <IFrameRoutes isJwt={isJwt} isLoggedIn={isLoggedIn} loading={loading} />;
|
||||
}
|
||||
setSessionPath(previousLocation ? previousLocation : location);
|
||||
}, [location]);
|
||||
|
||||
return isLoggedIn ? (
|
||||
<ModalProvider>
|
||||
<Loader loading={loading || !siteId} className='flex-1'>
|
||||
<Layout hideHeader={hideHeader} siteId={siteId}>
|
||||
<PrivateRoutes />
|
||||
</Layout>
|
||||
</Loader>
|
||||
</ModalProvider>
|
||||
) : <PublicRoutes />;
|
||||
useEffect(() => {
|
||||
if (prevIsLoggedIn !== isLoggedIn && isLoggedIn) {
|
||||
handleUserLogin();
|
||||
}
|
||||
}, [isLoggedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (siteId && siteId !== lastFetchedSiteIdRef.current) {
|
||||
const activeSite = sites.find((s) => s.id == siteId);
|
||||
props.initSite(activeSite);
|
||||
props.fetchMetadata(siteId);
|
||||
lastFetchedSiteIdRef.current = siteId;
|
||||
}
|
||||
}, [siteId]);
|
||||
|
||||
const lastFetchedSiteIdRef = useRef<any>(null);
|
||||
|
||||
function usePrevious(value: any) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]);
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
const prevIsLoggedIn = usePrevious(isLoggedIn);
|
||||
const previousLocation = usePrevious(location);
|
||||
|
||||
const hideHeader = (location.pathname && location.pathname.includes('/session/')) ||
|
||||
location.pathname.includes('/assist/') || location.pathname.includes('multiview');
|
||||
|
||||
if (isIframe) {
|
||||
return <IFrameRoutes isJwt={isJwt} isLoggedIn={isLoggedIn} loading={loading}/>;
|
||||
}
|
||||
|
||||
return isLoggedIn ? (
|
||||
<NewModalProvider>
|
||||
<ModalProvider>
|
||||
<Loader loading={loading || !siteId} className='flex-1'>
|
||||
<Layout hideHeader={hideHeader} siteId={siteId}>
|
||||
<PrivateRoutes/>
|
||||
</Layout>
|
||||
</Loader>
|
||||
</ModalProvider>
|
||||
</NewModalProvider>
|
||||
) : <PublicRoutes/>;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: Map<string, any>) => {
|
||||
const siteId = state.getIn(['site', 'siteId']);
|
||||
const jwt = state.getIn(['user', 'jwt']);
|
||||
const changePassword = state.getIn(['user', 'account', 'changePassword']);
|
||||
const userInfoLoading = state.getIn(['user', 'fetchUserInfoRequest', 'loading']);
|
||||
const sitesLoading = state.getIn(['site', 'fetchListRequest', 'loading']);
|
||||
const siteId = state.getIn(['site', 'siteId']);
|
||||
const jwt = state.getIn(['user', 'jwt']);
|
||||
const changePassword = state.getIn(['user', 'account', 'changePassword']);
|
||||
const userInfoLoading = state.getIn(['user', 'fetchUserInfoRequest', 'loading']);
|
||||
const sitesLoading = state.getIn(['site', 'fetchListRequest', 'loading']);
|
||||
|
||||
return {
|
||||
siteId,
|
||||
changePassword,
|
||||
sites: state.getIn(['site', 'list']),
|
||||
isLoggedIn: jwt !== null && !changePassword,
|
||||
loading: siteId === null || userInfoLoading || sitesLoading,
|
||||
email: state.getIn(['user', 'account', 'email']),
|
||||
account: state.getIn(['user', 'account']),
|
||||
organisation: state.getIn(['user', 'account', 'name']),
|
||||
tenantId: state.getIn(['user', 'account', 'tenantId']),
|
||||
tenants: state.getIn(['user', 'tenants']),
|
||||
isEnterprise:
|
||||
state.getIn(['user', 'account', 'edition']) === 'ee' ||
|
||||
state.getIn(['user', 'authDetails', 'edition']) === 'ee'
|
||||
};
|
||||
return {
|
||||
siteId,
|
||||
changePassword,
|
||||
sites: state.getIn(['site', 'list']),
|
||||
isLoggedIn: jwt !== null && !changePassword,
|
||||
loading: siteId === null || userInfoLoading || sitesLoading,
|
||||
email: state.getIn(['user', 'account', 'email']),
|
||||
account: state.getIn(['user', 'account']),
|
||||
organisation: state.getIn(['user', 'account', 'name']),
|
||||
tenantId: state.getIn(['user', 'account', 'tenantId']),
|
||||
tenants: state.getIn(['user', 'tenants']),
|
||||
isEnterprise:
|
||||
state.getIn(['user', 'account', 'edition']) === 'ee' ||
|
||||
state.getIn(['user', 'authDetails', 'edition']) === 'ee'
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchUserInfo,
|
||||
fetchTenants,
|
||||
setSessionPath,
|
||||
fetchSiteList,
|
||||
setJwt,
|
||||
fetchMetadata,
|
||||
initSite
|
||||
fetchUserInfo,
|
||||
fetchTenants,
|
||||
setSessionPath,
|
||||
fetchSiteList,
|
||||
setJwt,
|
||||
fetchMetadata,
|
||||
initSite
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
|
|
|||
|
|
@ -1,382 +1,384 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Button, Form, Input, SegmentSelection, Checkbox, Icon } from 'UI';
|
||||
import { alertConditions as conditions } from 'App/constants';
|
||||
import React, {useEffect} from 'react';
|
||||
import {Form, Input, SegmentSelection, Checkbox, Icon} from 'UI';
|
||||
import {alertConditions as conditions} from 'App/constants';
|
||||
import stl from './alertForm.module.css';
|
||||
import DropdownChips from './DropdownChips';
|
||||
import { validateEmail } from 'App/validate';
|
||||
import {validateEmail} from 'App/validate';
|
||||
import cn from 'classnames';
|
||||
import { useStore } from 'App/mstore'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import {useStore} from 'App/mstore'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import Select from 'Shared/Select';
|
||||
import {Button} from "antd";
|
||||
|
||||
const thresholdOptions = [
|
||||
{ label: '15 minutes', value: 15 },
|
||||
{ label: '30 minutes', value: 30 },
|
||||
{ label: '1 hour', value: 60 },
|
||||
{ label: '2 hours', value: 120 },
|
||||
{ label: '4 hours', value: 240 },
|
||||
{ label: '1 day', value: 1440 },
|
||||
{label: '15 minutes', value: 15},
|
||||
{label: '30 minutes', value: 30},
|
||||
{label: '1 hour', value: 60},
|
||||
{label: '2 hours', value: 120},
|
||||
{label: '4 hours', value: 240},
|
||||
{label: '1 day', value: 1440},
|
||||
];
|
||||
|
||||
const changeOptions = [
|
||||
{ label: 'change', value: 'change' },
|
||||
{ label: '% change', value: 'percent' },
|
||||
{label: 'change', value: 'change'},
|
||||
{label: '% change', value: 'percent'},
|
||||
];
|
||||
|
||||
const Circle = ({ text }) => (
|
||||
<div className="circle mr-4 w-6 h-6 rounded-full bg-gray-light flex items-center justify-center">
|
||||
{text}
|
||||
</div>
|
||||
const Circle = ({text}) => (
|
||||
<div className="circle mr-4 w-6 h-6 rounded-full bg-gray-light flex items-center justify-center">
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Section = ({ index, title, description, content }) => (
|
||||
<div className="w-full">
|
||||
<div className="flex items-start">
|
||||
<Circle text={index} />
|
||||
<div>
|
||||
<span className="font-medium">{title}</span>
|
||||
{description && <div className="text-sm color-gray-medium">{description}</div>}
|
||||
</div>
|
||||
</div>
|
||||
const Section = ({index, title, description, content}) => (
|
||||
<div className="w-full">
|
||||
<div className="flex items-start">
|
||||
<Circle text={index}/>
|
||||
<div>
|
||||
<span className="font-medium">{title}</span>
|
||||
{description && <div className="text-sm color-gray-medium">{description}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ml-10">{content}</div>
|
||||
</div>
|
||||
<div className="ml-10">{content}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
function AlertForm(props) {
|
||||
const {
|
||||
slackChannels,
|
||||
msTeamsChannels,
|
||||
webhooks,
|
||||
onDelete,
|
||||
style = { width: '580px', height: '100vh' },
|
||||
} = props;
|
||||
const { alertsStore } = useStore()
|
||||
const {
|
||||
triggerOptions,
|
||||
loading,
|
||||
} = alertsStore
|
||||
const instance = alertsStore.instance
|
||||
const deleting = loading
|
||||
const {
|
||||
slackChannels,
|
||||
msTeamsChannels,
|
||||
webhooks,
|
||||
onDelete,
|
||||
style = {height: "calc('100vh - 40px')"},
|
||||
} = props;
|
||||
const {alertsStore} = useStore()
|
||||
const {
|
||||
triggerOptions,
|
||||
loading,
|
||||
} = alertsStore
|
||||
const instance = alertsStore.instance
|
||||
const deleting = loading
|
||||
|
||||
const write = ({ target: { value, name } }) => alertsStore.edit({ [name]: value });
|
||||
const writeOption = (e, { name, value }) => alertsStore.edit({ [name]: value.value });
|
||||
const onChangeCheck = ({ target: { checked, name } }) => alertsStore.edit({ [name]: checked });
|
||||
const write = ({target: {value, name}}) => alertsStore.edit({[name]: value});
|
||||
const writeOption = (e, {name, value}) => alertsStore.edit({[name]: value.value});
|
||||
const onChangeCheck = ({target: {checked, name}}) => alertsStore.edit({[name]: checked});
|
||||
|
||||
useEffect(() => {
|
||||
void alertsStore.fetchTriggerOptions();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
void alertsStore.fetchTriggerOptions();
|
||||
}, []);
|
||||
|
||||
const writeQueryOption = (e, { name, value }) => {
|
||||
const { query } = instance;
|
||||
alertsStore.edit({ query: { ...query, [name]: value } });
|
||||
};
|
||||
const writeQueryOption = (e, {name, value}) => {
|
||||
const {query} = instance;
|
||||
alertsStore.edit({query: {...query, [name]: value}});
|
||||
};
|
||||
|
||||
const writeQuery = ({ target: { value, name } }) => {
|
||||
const { query } = instance;
|
||||
alertsStore.edit({ query: { ...query, [name]: value } });
|
||||
};
|
||||
const writeQuery = ({target: {value, name}}) => {
|
||||
const {query} = instance;
|
||||
alertsStore.edit({query: {...query, [name]: value}});
|
||||
};
|
||||
|
||||
const metric =
|
||||
instance && instance.query.left
|
||||
? triggerOptions.find((i) => i.value === instance.query.left)
|
||||
: null;
|
||||
const unit = metric ? metric.unit : '';
|
||||
const isThreshold = instance.detectionMethod === 'threshold';
|
||||
const metric =
|
||||
instance && instance.query.left
|
||||
? triggerOptions.find((i) => i.value === instance.query.left)
|
||||
: null;
|
||||
const unit = metric ? metric.unit : '';
|
||||
const isThreshold = instance.detectionMethod === 'threshold';
|
||||
|
||||
return (
|
||||
<Form
|
||||
className={cn('p-6 pb-10', stl.wrapper)}
|
||||
style={style}
|
||||
onSubmit={() => props.onSubmit(instance)}
|
||||
id="alert-form"
|
||||
>
|
||||
<div className={cn(stl.content, '-mx-6 px-6 pb-12')}>
|
||||
<input
|
||||
autoFocus={true}
|
||||
className="text-lg border border-gray-light rounded w-full"
|
||||
name="name"
|
||||
style={{ fontSize: '18px', padding: '10px', fontWeight: '600' }}
|
||||
value={instance && instance.name}
|
||||
onChange={write}
|
||||
placeholder="Untiltled Alert"
|
||||
id="name-field"
|
||||
/>
|
||||
<div className="mb-8" />
|
||||
<Section
|
||||
index="1"
|
||||
title={'What kind of alert do you want to set?'}
|
||||
content={
|
||||
<div>
|
||||
<SegmentSelection
|
||||
primary
|
||||
name="detectionMethod"
|
||||
className="my-3"
|
||||
onSelect={(e, { name, value }) => alertsStore.edit({ [name]: value })}
|
||||
value={{ value: instance.detectionMethod }}
|
||||
list={[
|
||||
{ name: 'Threshold', value: 'threshold' },
|
||||
{ name: 'Change', value: 'change' },
|
||||
]}
|
||||
/>
|
||||
<div className="text-sm color-gray-medium">
|
||||
{isThreshold &&
|
||||
'Eg. Alert me if memory.avg is greater than 500mb over the past 4 hours.'}
|
||||
{!isThreshold &&
|
||||
'Eg. Alert me if % change of memory.avg is greater than 10% over the past 4 hours compared to the previous 4 hours.'}
|
||||
</div>
|
||||
<div className="my-4" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<hr className="my-8" />
|
||||
|
||||
<Section
|
||||
index="2"
|
||||
title="Condition"
|
||||
content={
|
||||
<div>
|
||||
{!isThreshold && (
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">{'Trigger when'}</label>
|
||||
<Select
|
||||
className="w-4/6"
|
||||
placeholder="change"
|
||||
options={changeOptions}
|
||||
name="change"
|
||||
defaultValue={instance.change}
|
||||
onChange={({ value }) => writeOption(null, { name: 'change', value })}
|
||||
id="change-dropdown"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">
|
||||
{isThreshold ? 'Trigger when' : 'of'}
|
||||
</label>
|
||||
<Select
|
||||
className="w-4/6"
|
||||
placeholder="Select Metric"
|
||||
isSearchable={true}
|
||||
options={triggerOptions}
|
||||
name="left"
|
||||
value={triggerOptions.find((i) => i.value === instance.query.left)}
|
||||
// onChange={ writeQueryOption }
|
||||
onChange={({ value }) =>
|
||||
writeQueryOption(null, { name: 'left', value: value.value })
|
||||
}
|
||||
return (
|
||||
<Form
|
||||
className={cn('pb-10', stl.wrapper)}
|
||||
style={style}
|
||||
onSubmit={() => props.onSubmit(instance)}
|
||||
id="alert-form"
|
||||
>
|
||||
<div className={cn('-mx-6 px-6 pb-12')}>
|
||||
<input
|
||||
autoFocus={true}
|
||||
className="text-lg border border-gray-light rounded w-full"
|
||||
name="name"
|
||||
style={{fontSize: '18px', padding: '10px', fontWeight: '600'}}
|
||||
value={instance && instance.name}
|
||||
onChange={write}
|
||||
placeholder="Untiltled Alert"
|
||||
id="name-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">{'is'}</label>
|
||||
<div className="w-4/6 flex items-center">
|
||||
<Select
|
||||
placeholder="Select Condition"
|
||||
options={conditions}
|
||||
name="operator"
|
||||
defaultValue={instance.query.operator}
|
||||
// onChange={ writeQueryOption }
|
||||
onChange={({ value }) =>
|
||||
writeQueryOption(null, { name: 'operator', value: value.value })
|
||||
<div className="mb-8"/>
|
||||
<Section
|
||||
index="1"
|
||||
title={'What kind of alert do you want to set?'}
|
||||
content={
|
||||
<div>
|
||||
<SegmentSelection
|
||||
primary
|
||||
name="detectionMethod"
|
||||
className="my-3"
|
||||
onSelect={(e, {name, value}) => alertsStore.edit({[name]: value})}
|
||||
value={{value: instance.detectionMethod}}
|
||||
list={[
|
||||
{name: 'Threshold', value: 'threshold'},
|
||||
{name: 'Change', value: 'change'},
|
||||
]}
|
||||
/>
|
||||
<div className="text-sm color-gray-medium">
|
||||
{isThreshold &&
|
||||
'Eg. Alert me if memory.avg is greater than 500mb over the past 4 hours.'}
|
||||
{!isThreshold &&
|
||||
'Eg. Alert me if % change of memory.avg is greater than 10% over the past 4 hours compared to the previous 4 hours.'}
|
||||
</div>
|
||||
<div className="my-4"/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<hr className="my-8"/>
|
||||
|
||||
<Section
|
||||
index="2"
|
||||
title="Condition"
|
||||
content={
|
||||
<div>
|
||||
{!isThreshold && (
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">{'Trigger when'}</label>
|
||||
<Select
|
||||
className="w-4/6"
|
||||
placeholder="change"
|
||||
options={changeOptions}
|
||||
name="change"
|
||||
defaultValue={instance.change}
|
||||
onChange={({value}) => writeOption(null, {name: 'change', value})}
|
||||
id="change-dropdown"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">
|
||||
{isThreshold ? 'Trigger when' : 'of'}
|
||||
</label>
|
||||
<Select
|
||||
className="w-4/6"
|
||||
placeholder="Select Metric"
|
||||
isSearchable={true}
|
||||
options={triggerOptions}
|
||||
name="left"
|
||||
value={triggerOptions.find((i) => i.value === instance.query.left)}
|
||||
// onChange={ writeQueryOption }
|
||||
onChange={({value}) =>
|
||||
writeQueryOption(null, {name: 'left', value: value.value})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">{'is'}</label>
|
||||
<div className="w-4/6 flex items-center">
|
||||
<Select
|
||||
placeholder="Select Condition"
|
||||
options={conditions}
|
||||
name="operator"
|
||||
defaultValue={instance.query.operator}
|
||||
// onChange={ writeQueryOption }
|
||||
onChange={({value}) =>
|
||||
writeQueryOption(null, {name: 'operator', value: value.value})
|
||||
}
|
||||
/>
|
||||
{unit && (
|
||||
<>
|
||||
<Input
|
||||
className="px-4"
|
||||
style={{marginRight: '31px'}}
|
||||
// label={{ basic: true, content: unit }}
|
||||
// labelPosition='right'
|
||||
name="right"
|
||||
value={instance.query.right}
|
||||
onChange={writeQuery}
|
||||
placeholder="E.g. 3"
|
||||
/>
|
||||
<span className="ml-2">{'test'}</span>
|
||||
</>
|
||||
)}
|
||||
{!unit && (
|
||||
<Input
|
||||
wrapperClassName="ml-2"
|
||||
// className="pl-4"
|
||||
name="right"
|
||||
value={instance.query.right}
|
||||
onChange={writeQuery}
|
||||
placeholder="Specify value"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">{'over the past'}</label>
|
||||
<Select
|
||||
className="w-2/6"
|
||||
placeholder="Select timeframe"
|
||||
options={thresholdOptions}
|
||||
name="currentPeriod"
|
||||
defaultValue={instance.currentPeriod}
|
||||
// onChange={ writeOption }
|
||||
onChange={({value}) => writeOption(null, {name: 'currentPeriod', value})}
|
||||
/>
|
||||
</div>
|
||||
{!isThreshold && (
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">
|
||||
{'compared to previous'}
|
||||
</label>
|
||||
<Select
|
||||
className="w-2/6"
|
||||
placeholder="Select timeframe"
|
||||
options={thresholdOptions}
|
||||
name="previousPeriod"
|
||||
defaultValue={instance.previousPeriod}
|
||||
// onChange={ writeOption }
|
||||
onChange={({value}) => writeOption(null, {name: 'previousPeriod', value})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<hr className="my-8"/>
|
||||
|
||||
<Section
|
||||
index="3"
|
||||
title="Notify Through"
|
||||
description="You'll be noticed in app notifications. Additionally opt in to receive alerts on:"
|
||||
content={
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center my-4">
|
||||
<Checkbox
|
||||
name="slack"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={instance.slack}
|
||||
onClick={onChangeCheck}
|
||||
label="Slack"
|
||||
/>
|
||||
<Checkbox
|
||||
name="msteams"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={instance.msteams}
|
||||
onClick={onChangeCheck}
|
||||
label="MS Teams"
|
||||
/>
|
||||
<Checkbox
|
||||
name="email"
|
||||
type="checkbox"
|
||||
checked={instance.email}
|
||||
onClick={onChangeCheck}
|
||||
className="mr-8"
|
||||
label="Email"
|
||||
/>
|
||||
<Checkbox
|
||||
name="webhook"
|
||||
type="checkbox"
|
||||
checked={instance.webhook}
|
||||
onClick={onChangeCheck}
|
||||
label="Webhook"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{instance.slack && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'Slack'}</label>
|
||||
<div className="w-4/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.slackInput}
|
||||
options={slackChannels}
|
||||
placeholder="Select Channel"
|
||||
onChange={(selected) => alertsStore.edit({slackInput: selected})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{instance.msteams && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'MS Teams'}</label>
|
||||
<div className="w-4/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.msteamsInput}
|
||||
options={msTeamsChannels}
|
||||
placeholder="Select Channel"
|
||||
onChange={(selected) => alertsStore.edit({msteamsInput: selected})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{instance.email && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'Email'}</label>
|
||||
<div className="w-4/6">
|
||||
<DropdownChips
|
||||
textFiled
|
||||
validate={validateEmail}
|
||||
selected={instance.emailInput}
|
||||
placeholder="Type and press Enter key"
|
||||
onChange={(selected) => alertsStore.edit({emailInput: selected})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{instance.webhook && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'Webhook'}</label>
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.webhookInput}
|
||||
options={webhooks}
|
||||
placeholder="Select Webhook"
|
||||
onChange={(selected) => alertsStore.edit({webhookInput: selected})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{unit && (
|
||||
<>
|
||||
<Input
|
||||
className="px-4"
|
||||
style={{ marginRight: '31px' }}
|
||||
// label={{ basic: true, content: unit }}
|
||||
// labelPosition='right'
|
||||
name="right"
|
||||
value={instance.query.right}
|
||||
onChange={writeQuery}
|
||||
placeholder="E.g. 3"
|
||||
/>
|
||||
<span className="ml-2">{'test'}</span>
|
||||
</>
|
||||
)}
|
||||
{!unit && (
|
||||
<Input
|
||||
wrapperClassName="ml-2"
|
||||
// className="pl-4"
|
||||
name="right"
|
||||
value={instance.query.right}
|
||||
onChange={writeQuery}
|
||||
placeholder="Specify value"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">{'over the past'}</label>
|
||||
<Select
|
||||
className="w-2/6"
|
||||
placeholder="Select timeframe"
|
||||
options={thresholdOptions}
|
||||
name="currentPeriod"
|
||||
defaultValue={instance.currentPeriod}
|
||||
// onChange={ writeOption }
|
||||
onChange={({ value }) => writeOption(null, { name: 'currentPeriod', value })}
|
||||
/>
|
||||
</div>
|
||||
{!isThreshold && (
|
||||
<div className="flex items-center my-3">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal">
|
||||
{'compared to previous'}
|
||||
</label>
|
||||
<Select
|
||||
className="w-2/6"
|
||||
placeholder="Select timeframe"
|
||||
options={thresholdOptions}
|
||||
name="previousPeriod"
|
||||
defaultValue={instance.previousPeriod}
|
||||
// onChange={ writeOption }
|
||||
onChange={({ value }) => writeOption(null, { name: 'previousPeriod', value })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<hr className="my-8" />
|
||||
|
||||
<Section
|
||||
index="3"
|
||||
title="Notify Through"
|
||||
description="You'll be noticed in app notifications. Additionally opt in to receive alerts on:"
|
||||
content={
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center my-4">
|
||||
<Checkbox
|
||||
name="slack"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={instance.slack}
|
||||
onClick={onChangeCheck}
|
||||
label="Slack"
|
||||
/>
|
||||
<Checkbox
|
||||
name="msteams"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={instance.msteams}
|
||||
onClick={onChangeCheck}
|
||||
label="MS Teams"
|
||||
/>
|
||||
<Checkbox
|
||||
name="email"
|
||||
type="checkbox"
|
||||
checked={instance.email}
|
||||
onClick={onChangeCheck}
|
||||
className="mr-8"
|
||||
label="Email"
|
||||
/>
|
||||
<Checkbox
|
||||
name="webhook"
|
||||
type="checkbox"
|
||||
checked={instance.webhook}
|
||||
onClick={onChangeCheck}
|
||||
label="Webhook"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{instance.slack && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'Slack'}</label>
|
||||
<div className="w-4/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.slackInput}
|
||||
options={slackChannels}
|
||||
placeholder="Select Channel"
|
||||
onChange={(selected) => alertsStore.edit({ slackInput: selected })}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center justify-between absolute bottom-0 left-0 right-0 p-6 border-t z-10 bg-white">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
loading={loading}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
disabled={loading || !instance.validate()}
|
||||
id="submit-button"
|
||||
>
|
||||
{instance.exists() ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
<div className="mx-1"/>
|
||||
<Button onClick={props.onClose}>Cancel</Button>
|
||||
</div>
|
||||
)}
|
||||
{instance.msteams && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'MS Teams'}</label>
|
||||
<div className="w-4/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.msteamsInput}
|
||||
options={msTeamsChannels}
|
||||
placeholder="Select Channel"
|
||||
onChange={(selected) => alertsStore.edit({ msteamsInput: selected })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{instance.exists() && (
|
||||
<Button
|
||||
hover
|
||||
primary="text"
|
||||
loading={deleting}
|
||||
type="button"
|
||||
onClick={() => onDelete(instance)}
|
||||
id="trash-button"
|
||||
>
|
||||
<Icon name="trash" color="gray-medium" size="18"/>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{instance.email && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'Email'}</label>
|
||||
<div className="w-4/6">
|
||||
<DropdownChips
|
||||
textFiled
|
||||
validate={validateEmail}
|
||||
selected={instance.emailInput}
|
||||
placeholder="Type and press Enter key"
|
||||
onChange={(selected) => alertsStore.edit({ emailInput: selected })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{instance.webhook && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'Webhook'}</label>
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.webhookInput}
|
||||
options={webhooks}
|
||||
placeholder="Select Webhook"
|
||||
onChange={(selected) => alertsStore.edit({ webhookInput: selected })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between absolute bottom-0 left-0 right-0 p-6 border-t z-10 bg-white">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
loading={loading}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={loading || !instance.validate()}
|
||||
id="submit-button"
|
||||
>
|
||||
{instance.exists() ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
<div className="mx-1" />
|
||||
<Button onClick={props.onClose}>Cancel</Button>
|
||||
</div>
|
||||
<div>
|
||||
{instance.exists() && (
|
||||
<Button
|
||||
hover
|
||||
variant="text"
|
||||
loading={deleting}
|
||||
type="button"
|
||||
onClick={() => onDelete(instance)}
|
||||
id="trash-button"
|
||||
>
|
||||
<Icon name="trash" color="gray-medium" size="18" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(AlertForm);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { SlideModal } from 'UI';
|
||||
import { useStore } from 'App/mstore'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {SlideModal} from 'UI';
|
||||
import {useStore} from 'App/mstore'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import AlertForm from '../AlertForm';
|
||||
import { SLACK, TEAMS, WEBHOOK } from 'App/constants/schedule';
|
||||
import { confirm } from 'UI';
|
||||
import {SLACK, TEAMS, WEBHOOK} from 'App/constants/schedule';
|
||||
import {confirm} from 'UI';
|
||||
|
||||
interface Select {
|
||||
label: string;
|
||||
|
|
@ -17,9 +17,10 @@ interface Props {
|
|||
metricId?: number;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function AlertFormModal(props: Props) {
|
||||
const { alertsStore, settingsStore } = useStore()
|
||||
const { metricId = null, showModal = false } = props;
|
||||
const {alertsStore, settingsStore} = useStore()
|
||||
const {metricId = null, showModal = false} = props;
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const webhooks = settingsStore.webhooks
|
||||
useEffect(() => {
|
||||
|
|
@ -32,7 +33,7 @@ function AlertFormModal(props: Props) {
|
|||
const msTeamsChannels: Select[] = []
|
||||
|
||||
webhooks.forEach((hook) => {
|
||||
const option = { value: hook.webhookId, label: hook.name }
|
||||
const option = {value: hook.webhookId, label: hook.name}
|
||||
if (hook.type === SLACK) {
|
||||
slackChannels.push(option)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
import {useHistory} from "react-router";
|
||||
import {useStore} from "App/mstore";
|
||||
import {useObserver} from "mobx-react-lite";
|
||||
import {Button, Drawer, Dropdown, MenuProps, message, Modal} from "antd";
|
||||
import {BellIcon, EllipsisVertical, TrashIcon} from "lucide-react";
|
||||
import {toast} from "react-toastify";
|
||||
import React from "react";
|
||||
import {useModal} from "Components/ModalContext";
|
||||
import AlertFormModal from "Components/Alerts/AlertFormModal/AlertFormModal";
|
||||
|
||||
const CardViewMenu = () => {
|
||||
const history = useHistory();
|
||||
const {alertsStore, dashboardStore, metricStore} = useStore();
|
||||
const widget = useObserver(() => metricStore.instance);
|
||||
const {openModal, closeModal} = useModal();
|
||||
|
||||
const showAlertModal = () => {
|
||||
const seriesId = widget.series[0] && widget.series[0].seriesId || '';
|
||||
alertsStore.init({query: {left: seriesId}})
|
||||
openModal(<AlertFormModal
|
||||
onClose={closeModal}
|
||||
/>, {
|
||||
// title: 'Set Alerts',
|
||||
placement: 'right',
|
||||
width: 620,
|
||||
});
|
||||
}
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: 'alert',
|
||||
label: "Set Alerts",
|
||||
icon: <BellIcon size={16}/>,
|
||||
disabled: !widget.exists() || widget.metricType === 'predefined',
|
||||
onClick: showAlertModal,
|
||||
},
|
||||
{
|
||||
key: 'remove',
|
||||
danger: true,
|
||||
label: 'Remove',
|
||||
icon: <TrashIcon size={16}/>,
|
||||
onClick: () => {
|
||||
Modal.confirm({
|
||||
title: 'Are you sure you want to remove this card?',
|
||||
icon: null,
|
||||
// content: 'Bla bla ...',
|
||||
footer: (_, {OkBtn, CancelBtn}) => (
|
||||
<>
|
||||
<CancelBtn/>
|
||||
<OkBtn/>
|
||||
</>
|
||||
),
|
||||
onOk: () => {
|
||||
metricStore.delete(widget).then(r => {
|
||||
history.goBack();
|
||||
}).catch(() => {
|
||||
toast.error('Failed to remove card');
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const onClick: MenuProps['onClick'] = ({key}) => {
|
||||
if (key === 'alert') {
|
||||
message.info('Set Alerts');
|
||||
} else if (key === 'remove') {
|
||||
Modal.confirm({
|
||||
title: 'Are you sure you want to remove this card?',
|
||||
icon: null,
|
||||
// content: 'Bla bla ...',
|
||||
footer: (_, {OkBtn, CancelBtn}) => (
|
||||
<>
|
||||
<CancelBtn/>
|
||||
<OkBtn/>
|
||||
</>
|
||||
),
|
||||
onOk: () => {
|
||||
metricStore.delete(widget).then(r => {
|
||||
history.goBack();
|
||||
}).catch(() => {
|
||||
toast.error('Failed to remove card');
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<Dropdown menu={{items}}>
|
||||
<Button icon={<EllipsisVertical size={16}/>}/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardViewMenu;
|
||||
|
|
@ -5,10 +5,8 @@ import {useStore} from "App/mstore";
|
|||
import {useObserver} from "mobx-react-lite";
|
||||
import AddToDashboardButton from "Components/Dashboard/components/AddToDashboardButton";
|
||||
import WidgetDateRange from "Components/Dashboard/components/WidgetDateRange/WidgetDateRange";
|
||||
import {Button, Dropdown, MenuProps, Space, message, Modal} from "antd";
|
||||
import {BellIcon, EllipsisVertical, TrashIcon} from "lucide-react";
|
||||
import {useHistory} from "react-router";
|
||||
import {toast} from "react-toastify";
|
||||
import {Button, Space} from "antd";
|
||||
import CardViewMenu from "Components/Dashboard/components/WidgetView/CardViewMenu";
|
||||
|
||||
interface Props {
|
||||
onClick?: () => void;
|
||||
|
|
@ -48,55 +46,3 @@ function WidgetViewHeader({onClick, onSave, undoChanges}: Props) {
|
|||
}
|
||||
|
||||
export default WidgetViewHeader;
|
||||
|
||||
const CardViewMenu = () => {
|
||||
const history = useHistory();
|
||||
const {dashboardStore, metricStore} = useStore();
|
||||
const widget = useObserver(() => metricStore.instance);
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: 'alert',
|
||||
label: "Set Alerts",
|
||||
icon: <BellIcon size={16}/>,
|
||||
},
|
||||
{
|
||||
key: 'remove',
|
||||
danger: true,
|
||||
label: 'Remove',
|
||||
icon: <TrashIcon size={16}/>,
|
||||
},
|
||||
];
|
||||
|
||||
const onClick: MenuProps['onClick'] = ({key}) => {
|
||||
if (key === 'alert') {
|
||||
message.info('Set Alerts');
|
||||
} else if (key === 'remove') {
|
||||
Modal.confirm({
|
||||
title: 'Are you sure you want to remove this card?',
|
||||
icon: null,
|
||||
// content: 'Bla bla ...',
|
||||
footer: (_, {OkBtn, CancelBtn}) => (
|
||||
<>
|
||||
<CancelBtn/>
|
||||
<OkBtn/>
|
||||
</>
|
||||
),
|
||||
onOk: () => {
|
||||
metricStore.delete(widget).then(r => {
|
||||
history.goBack();
|
||||
}).catch(() => {
|
||||
toast.error('Failed to remove card');
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<Dropdown menu={{items, onClick}}>
|
||||
<Button icon={<EllipsisVertical size={16}/>}/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import WidgetIcon from './WidgetIcon';
|
|||
import {useStore} from 'App/mstore';
|
||||
import {Button} from "antd";
|
||||
import {BellIcon} from "lucide-react";
|
||||
import {useModal} from "Components/ModalContext";
|
||||
import AlertFormModal from "Components/Alerts/AlertFormModal/AlertFormModal";
|
||||
|
||||
interface Props {
|
||||
seriesId: string;
|
||||
|
|
@ -12,9 +14,17 @@ interface Props {
|
|||
function AlertButton(props: Props) {
|
||||
const {seriesId} = props;
|
||||
const {dashboardStore, alertsStore} = useStore();
|
||||
const {openModal, closeModal} = useModal();
|
||||
const onClick = () => {
|
||||
dashboardStore.toggleAlertModal(true);
|
||||
// dashboardStore.toggleAlertModal(true);
|
||||
alertsStore.init({query: {left: seriesId}})
|
||||
openModal(<AlertFormModal
|
||||
onClose={closeModal}
|
||||
/>, {
|
||||
// title: 'Set Alerts',
|
||||
placement: 'right',
|
||||
width: 620,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Button onClick={onClick} type="text" icon={<BellIcon size={16}/>}/>
|
||||
|
|
|
|||
64
frontend/app/components/ModalContext.tsx
Normal file
64
frontend/app/components/ModalContext.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import React, {createContext, useContext, useState, ReactNode} from 'react';
|
||||
import {Drawer} from 'antd';
|
||||
|
||||
interface ModalConfig {
|
||||
title?: string;
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left';
|
||||
width?: number;
|
||||
}
|
||||
|
||||
interface ModalContextType {
|
||||
openModal: (content: ReactNode, config?: ModalConfig) => void;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const defaultConfig: ModalConfig = {
|
||||
title: 'Modal Title',
|
||||
placement: 'right',
|
||||
width: 428
|
||||
};
|
||||
|
||||
const ModalContext = createContext<ModalContextType>({
|
||||
openModal: () => {
|
||||
},
|
||||
closeModal: () => {
|
||||
}
|
||||
});
|
||||
|
||||
export const useModal = () => useContext(ModalContext);
|
||||
|
||||
export const ModalProvider = ({children}: { children: ReactNode }) => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [modalContent, setModalContent] = useState<ReactNode>(null);
|
||||
const [modalConfig, setModalConfig] = useState<ModalConfig>(defaultConfig);
|
||||
|
||||
const openModal = (content: ReactNode, config: ModalConfig = defaultConfig) => {
|
||||
setModalContent(content);
|
||||
setModalConfig(config);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setShowModal(false);
|
||||
setTimeout(() => {
|
||||
setModalContent(null);
|
||||
setModalConfig(defaultConfig);
|
||||
}, 200)
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalContext.Provider value={{openModal, closeModal}}>
|
||||
{children}
|
||||
<Drawer
|
||||
open={showModal}
|
||||
closeIcon={null}
|
||||
title={modalConfig.title}
|
||||
placement={modalConfig.placement}
|
||||
onClose={closeModal}
|
||||
width={modalConfig.width}
|
||||
>
|
||||
{modalContent}
|
||||
</Drawer>
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue