feat(ui) - dashboard - wip
This commit is contained in:
parent
3ae300f73e
commit
7796ff8a6c
28 changed files with 713 additions and 242 deletions
|
|
@ -26,6 +26,7 @@ import { fetchList as fetchSiteList } from 'Duck/site';
|
|||
import { fetchList as fetchAnnouncements } from 'Duck/announcements';
|
||||
import { fetchList as fetchAlerts } from 'Duck/alerts';
|
||||
import { fetchWatchdogStatus } from 'Duck/watchdogs';
|
||||
import { dashboardService } from "App/services";
|
||||
|
||||
import APIClient from './api_client';
|
||||
import * as routes from './routes';
|
||||
|
|
@ -114,6 +115,7 @@ class Router extends React.Component {
|
|||
fetchInitialData = () => {
|
||||
Promise.all([
|
||||
this.props.fetchUserInfo().then(() => {
|
||||
dashboardService.initClient();
|
||||
this.props.fetchIntegrationVariables()
|
||||
}),
|
||||
this.props.fetchSiteList().then(() => {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ const siteIdRequiredPaths = [
|
|||
'/assist',
|
||||
'/heatmaps',
|
||||
'/custom_metrics',
|
||||
'/dashboards',
|
||||
'/metrics'
|
||||
// '/custom_metrics/sessions',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
|
|||
import { Switch, Route, Redirect } from 'react-router';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useDashboardStore } from './store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import DashboardView from './components/DashboardView';
|
||||
import {
|
||||
|
|
@ -18,71 +18,67 @@ import MetricsView from './components/MetricsView';
|
|||
|
||||
function NewDashboard(props) {
|
||||
const { history, match: { params: { siteId, dashboardId, metricId } } } = props;
|
||||
const store: any = useDashboardStore();
|
||||
const dashboard = store.selectedDashboard;
|
||||
const { dashboardStore } = useStore();
|
||||
const dashboard: any = dashboardStore.selectedDashboard;
|
||||
|
||||
useEffect(() => {
|
||||
store.setSiteId(siteId);
|
||||
dashboardStore.fetchList();
|
||||
dashboardStore.setSiteId(siteId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dashboardId) {
|
||||
store.selectDashboardById(dashboardId);
|
||||
dashboardStore.selectDashboardById(dashboardId);
|
||||
}
|
||||
if (!dashboardId) {
|
||||
if (dashboardId) {
|
||||
store.selectDashboardById(dashboardId);
|
||||
dashboardStore.selectDashboardById(dashboardId);
|
||||
} else {
|
||||
store.selectDefaultDashboard().then((resp) => {
|
||||
dashboardStore.selectDefaultDashboard().then((resp: any) => {
|
||||
history.push(withSiteId(dashboardSelected(resp.dashboardId), siteId));
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// console.log('rendering dashboard', props.match.params);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* { dashboard && dashboard.dashboardId && ( */}
|
||||
<Switch>
|
||||
<Route exact strict path={withSiteId(dashboardMetrics(), siteId)}>
|
||||
<Switch>
|
||||
<Route exact strict path={withSiteId(dashboardMetrics(), siteId)}>
|
||||
<div className="page-margin container-90">
|
||||
<div className="side-menu">
|
||||
<DashboardSideMenu />
|
||||
</div>
|
||||
<div className="side-menu-margined">
|
||||
<MetricsView />
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
<Route exact strict path={withSiteId(dashboardMetricCreate(dashboard.dashboardId), siteId)}>
|
||||
<WidgetView />
|
||||
</Route>
|
||||
{ dashboardId && (
|
||||
<>
|
||||
<Route exact strict path={withSiteId(dashboardSelected(dashboardId), siteId)}>
|
||||
<div className="page-margin container-90">
|
||||
<div className="side-menu">
|
||||
<DashboardSideMenu />
|
||||
</div>
|
||||
<div className="side-menu-margined">
|
||||
<MetricsView />
|
||||
<DashboardView dashboard={dashboard} />
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
<Route exact strict path={withSiteId(dashboardMetricCreate(dashboard.dashboardId), siteId)}>
|
||||
|
||||
|
||||
{/*
|
||||
<Route exact strict path={withSiteId(dashboardMetricDetails(dashboard.dashboardId, metricId), siteId)}>
|
||||
<WidgetView />
|
||||
</Route>
|
||||
{ dashboardId && (
|
||||
<>
|
||||
<Route exact strict path={withSiteId(dashboardSelected(dashboardId), siteId)}>
|
||||
<div className="page-margin container-90">
|
||||
<div className="side-menu">
|
||||
<DashboardSideMenu />
|
||||
</div>
|
||||
<div className="side-menu-margined">
|
||||
<DashboardView dashboard={dashboard} />
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
|
||||
|
||||
{/*
|
||||
<Route exact strict path={withSiteId(dashboardMetricDetails(dashboard.dashboardId, metricId), siteId)}>
|
||||
<WidgetView />
|
||||
</Route> */}
|
||||
{/* <Redirect exact strict to={withSiteId(dashboardSelected(dashboardId), siteId )} /> */}
|
||||
</>
|
||||
)}
|
||||
</Switch>
|
||||
{/* )} */}
|
||||
</>
|
||||
</Route> */}
|
||||
{/* <Redirect exact strict to={withSiteId(dashboardSelected(dashboardId), siteId )} /> */}
|
||||
</>
|
||||
)}
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { SideMenuitem, SideMenuHeader, Icon, Button } from 'UI';
|
||||
|
||||
function DashbaordListModal(props) {
|
||||
const { dashboardStore } = useStore();
|
||||
const dashboards = dashboardStore.dashboards;
|
||||
const activeDashboardId = dashboardStore.selectedDashboard?.dashboardId;
|
||||
return (
|
||||
<div className="bg-white h-screen" style={{ width: '300px'}}>
|
||||
<div className="color-gray-medium uppercase p-4 text-lg">Dashboards</div>
|
||||
<div>
|
||||
{dashboards.map((item: any) => (
|
||||
// <div className="px-4 py-3 hover:bg-gray-lightest cursor-pointer">
|
||||
// {item.name}
|
||||
// </div>
|
||||
<div key={ item.dashboardId } className="px-4">
|
||||
<SideMenuitem
|
||||
key={ item.dashboardId }
|
||||
active={item.dashboardId === activeDashboardId}
|
||||
title={ item.name }
|
||||
iconName={ item.icon }
|
||||
// onClick={() => onItemClick(item)}
|
||||
leading = {(
|
||||
<div className="ml-2 flex items-center">
|
||||
<div className="p-1"><Icon name="user-friends" color="gray-light" size="16" /></div>
|
||||
{item.isPinned && <div className="p-1"><Icon name="pin-fill" size="16" /></div>}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashbaordListModal;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './DashbaordListModal'
|
||||
|
|
@ -3,13 +3,14 @@ import React from 'react';
|
|||
import { Input } from 'UI';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import cn from 'classnames';
|
||||
import { useStore } from 'App/mstore';
|
||||
|
||||
interface Props {
|
||||
}
|
||||
|
||||
function DashboardForm(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const dashboard = store.newDashboard;
|
||||
function DashboardForm(props: Props) {
|
||||
const { dashboardStore } = useStore();
|
||||
const dashboard = dashboardStore.dashboardInstance;
|
||||
|
||||
const write = ({ target: { value, name } }) => dashboard.update({ [ name ]: value })
|
||||
const writeRadio = ({ target: { value, name } }) => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useDashboardStore } from '../../store/store';
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import cn from 'classnames';
|
||||
import { Button } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
|
||||
function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds, unSelectCategory }) {
|
||||
const selectedCategoryWidgetsCount = useObserver(() => {
|
||||
|
|
@ -27,9 +28,9 @@ function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds,
|
|||
}
|
||||
|
||||
function DashboardMetricSelection(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const widgetCategories = store?.widgetCategories;
|
||||
const widgetTemplates = store?.widgetTemplates;
|
||||
const { dashboardStore } = useStore();
|
||||
const widgetCategories = dashboardStore?.widgetCategories;
|
||||
const widgetTemplates = dashboardStore?.widgetTemplates;
|
||||
const [activeCategory, setActiveCategory] = React.useState<any>(widgetCategories[0]);
|
||||
const [selectedWidgets, setSelectedWidgets] = React.useState<any>([]);
|
||||
const selectedWidgetIds = selectedWidgets.map((widget: any) => widget.widgetId);
|
||||
|
|
@ -73,18 +74,22 @@ function DashboardMetricSelection(props) {
|
|||
</div>
|
||||
|
||||
<div className="col-span-9 flex items-center">
|
||||
<div className="flex items-center">
|
||||
<h2 className="text-2xl">Errors Tracking</h2>
|
||||
<span className="text-2xl color-gray-medium ml-2">12</span>
|
||||
</div>
|
||||
{activeCategory && (
|
||||
<>
|
||||
<div className="flex items-baseline">
|
||||
<h2 className="text-2xl">{activeCategory.name}</h2>
|
||||
<span className="text-2xl color-gray-medium ml-2">{activeCategory.widgets.length}</span>
|
||||
</div>
|
||||
|
||||
<div className="ml-auto flex items-center">
|
||||
<span className="color-gray-medium">Showing past 7 days data for visual clue</span>
|
||||
<div className="flex items-center ml-3">
|
||||
<input type="checkbox" onChange={toggleAllWidgets} />
|
||||
<span className="ml-2">Select All</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<span className="color-gray-medium">Showing past 7 days data for visual clue</span>
|
||||
<div className="flex items-center ml-3">
|
||||
<input type="checkbox" onChange={toggleAllWidgets} />
|
||||
<span className="ml-2">Select All</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
|
|
@ -104,7 +109,7 @@ function DashboardMetricSelection(props) {
|
|||
</div>
|
||||
<div className="col-span-9">
|
||||
<div className="grid grid-cols-2 gap-4 -mx-4 px-4 lg:grid-cols-2 sm:grid-cols-1">
|
||||
{activeCategory.widgets.map((widget: any) => (
|
||||
{activeCategory && activeCategory.widgets.map((widget: any) => (
|
||||
<div
|
||||
key={widget.widgetId}
|
||||
className={cn("border rounded cursor-pointer", { 'border-color-teal' : selectedWidgetIds.includes(widget.widgetId) })}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,27 @@
|
|||
import React from 'react';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import DashboardMetricSelection from '../DashboardMetricSelection';
|
||||
import DashboardForm from '../DashboardForm';
|
||||
import { Button } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
|
||||
|
||||
function DashboardModal(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const dashbaord = useObserver(() => store.newDashboard);
|
||||
const { dashboardStore } = useStore();
|
||||
const { hideModal } = useModal();
|
||||
const dashbaord = useObserver(() => dashboardStore.dashboardInstance);
|
||||
const loading = useObserver(() => dashboardStore.isSaving);
|
||||
|
||||
const onSave = () => {
|
||||
dashboardStore.save(dashbaord).then(hideModal)
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="fixed border-r shadow p-4 h-screen" style={{ backgroundColor: '#FAFAFA', zIndex: '9999', width: '80%'}}>
|
||||
<div
|
||||
className="fixed border-r shadow p-4 h-screen"
|
||||
style={{ backgroundColor: '#FAFAFA', zIndex: '9999', width: '80%'}}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl">Create Dashboard</h1>
|
||||
</div>
|
||||
|
|
@ -20,7 +30,12 @@ function DashboardModal(props) {
|
|||
<DashboardMetricSelection />
|
||||
|
||||
<div className="flex absolute bottom-0 left-0 right-0 bg-white border-t p-3">
|
||||
<Button primary className="" disabled={!dashbaord.isValid} onClick={() => store.save(dashbaord)}>
|
||||
<Button
|
||||
primary
|
||||
className=""
|
||||
disabled={!dashbaord.isValid || loading}
|
||||
onClick={onSave}
|
||||
>
|
||||
Add Selected to Dashboard
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,51 @@
|
|||
import { useObserver, observer, useLocalObservable } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { SideMenuitem, SideMenuHeader, Icon } from 'UI';
|
||||
import { withDashboardStore } from '../../store/store';
|
||||
import { SideMenuitem, SideMenuHeader, Icon, Button } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withSiteId, dashboardSelected, dashboardMetrics } from 'App/routes';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import DashbaordListModal from '../DashbaordListModal';
|
||||
import DashboardModal from '../DashboardModal';
|
||||
|
||||
const SHOW_COUNT = 5;
|
||||
function DashboardSideMenu(props) {
|
||||
const { store, history } = props;
|
||||
const { dashboardId } = store.selectedDashboard;
|
||||
const { hideModal, showModal } = useModal();
|
||||
const { history } = props;
|
||||
const { dashboardStore } = useStore();
|
||||
const dashboardId = dashboardStore.selectedDashboard?.dashboardId;
|
||||
const dashboardsPicked = dashboardStore.dashboards.slice(0, SHOW_COUNT);
|
||||
const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT;
|
||||
|
||||
// React.useEffect(() => {
|
||||
// showModal(<DashbaordListModal />, {});
|
||||
// }, []);
|
||||
|
||||
const redirect = (path) => {
|
||||
history.push(path);
|
||||
}
|
||||
|
||||
const onItemClick = (dashboard) => {
|
||||
store.selectDashboardById(dashboard.dashboardId);
|
||||
const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(store.siteId));
|
||||
dashboardStore.selectDashboardById(dashboard.dashboardId);
|
||||
const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(dashboardStore.siteId));
|
||||
history.push(path);
|
||||
};
|
||||
|
||||
const onAddDashboardClick = (e) => {
|
||||
dashboardStore.initDashboard();
|
||||
showModal(<DashboardModal />, {})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SideMenuHeader className="mb-4" text="Dashboards" />
|
||||
{store.dashboards.map(item => (
|
||||
{dashboardsPicked.map((item: any) => (
|
||||
<SideMenuitem
|
||||
key={ item.dashboardId }
|
||||
active={item.dashboardId === dashboardId}
|
||||
title={ item.name }
|
||||
iconName={ item.icon }
|
||||
onClick={() => onItemClick(item)}
|
||||
|
||||
leading = {(
|
||||
<div className="ml-2 flex items-center">
|
||||
<div className="p-1"><Icon name="user-friends" color="gray-light" size="16" /></div>
|
||||
|
|
@ -38,13 +54,32 @@ function DashboardSideMenu(props) {
|
|||
)}
|
||||
/>
|
||||
))}
|
||||
<div>
|
||||
{remainingDashboardsCount > 0 && (
|
||||
<div
|
||||
className="my-2 py-2 color-teal cursor-pointer"
|
||||
onClick={() => showModal(<DashbaordListModal />, {})}
|
||||
>
|
||||
{remainingDashboardsCount} More
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="border-t w-full my-2" />
|
||||
<div className="w-full">
|
||||
<SideMenuitem
|
||||
id="menu-manage-alerts"
|
||||
title="Create Dashboard"
|
||||
iconName="plus"
|
||||
onClick={onAddDashboardClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t w-full my-2" />
|
||||
<div className="w-full">
|
||||
<SideMenuitem
|
||||
id="menu-manage-alerts"
|
||||
title="Metrics"
|
||||
iconName="bar-chart-line"
|
||||
onClick={() => redirect(withSiteId(dashboardMetrics(), store.siteId))}
|
||||
onClick={() => redirect(withSiteId(dashboardMetrics(), dashboardStore.siteId))}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t w-full my-2" />
|
||||
|
|
@ -60,4 +95,4 @@ function DashboardSideMenu(props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default withDashboardStore(withRouter(observer(DashboardSideMenu)));
|
||||
export default withRouter(observer(DashboardSideMenu));
|
||||
|
|
@ -1,27 +1,24 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import WidgetWrapper from '../../WidgetWrapper';
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { withDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Button, PageTitle, Link } from 'UI';
|
||||
import { withSiteId, dashboardMetricCreate } from 'App/routes';
|
||||
import withModal from 'App/components/Modal/withModal';
|
||||
import DashboardModal from '../DashboardModal'
|
||||
import DashboardWidgetGrid from '../DashboardWidgetGrid';
|
||||
|
||||
function DashboardView(props) {
|
||||
// let { handleModal } = React.useContext(ModalContext);
|
||||
const { store } = props;
|
||||
const dashboard = store.selectedDashboard
|
||||
const list = dashboard?.widgets;
|
||||
useEffect(() => {
|
||||
// props.showModal(DashboardModal)
|
||||
}, [])
|
||||
interface Props {
|
||||
|
||||
}
|
||||
function DashboardView(props: Props) {
|
||||
const { dashboardStore } = useStore();
|
||||
const dashboard: any = dashboardStore.selectedDashboard
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center mb-4 justify-between">
|
||||
<div className="flex items-center">
|
||||
<PageTitle title={dashboard.name} className="mr-3" />
|
||||
<Link to={withSiteId(dashboardMetricCreate(dashboard.dashboardId), store.siteId)}><Button primary size="small">Add Metric</Button></Link>
|
||||
<Link to={withSiteId(dashboardMetricCreate(dashboard.dashboardId), dashboardStore.siteId)}><Button primary size="small">Add Metric</Button></Link>
|
||||
</div>
|
||||
<div>
|
||||
Right
|
||||
|
|
@ -32,4 +29,4 @@ function DashboardView(props) {
|
|||
)
|
||||
}
|
||||
|
||||
export default withDashboardStore(withModal(observer(DashboardView)));
|
||||
export default withModal(observer(DashboardView));
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import React from 'react';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import WidgetWrapper from '../../WidgetWrapper';
|
||||
import { NoContent, Button, Loader } from 'UI';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
// import { divider } from '../../Filters/filters.css';
|
||||
|
||||
function DashboardWidgetGrid(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const loading = store.isLoading;
|
||||
const dashbaord = store.selectedDashboard;
|
||||
const list = dashbaord.widgets;
|
||||
const { dashboardStore } = useStore();
|
||||
const loading = useObserver(() => dashboardStore.isLoading);
|
||||
const dashbaord: any = dashboardStore.selectedDashboard;
|
||||
const list: any = dashbaord?.widgets;
|
||||
|
||||
return useObserver(() => (
|
||||
<Loader loading={loading}>
|
||||
<NoContent
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { Icon, NoContent, Label, Link, Pagination } from 'UI';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { getRE } from 'App/utils';
|
||||
|
||||
interface Props { }
|
||||
function MetricsList(props: Props) {
|
||||
const store: any = useDashboardStore();
|
||||
const widgets = store.widgets;
|
||||
const { dashboardStore } = useStore();
|
||||
const widgets = dashboardStore.widgets;
|
||||
const lenth = widgets.length;
|
||||
const currentPage = useObserver(() => store.metricsPage);
|
||||
const metricsSearch = useObserver(() => store.metricsSearch);
|
||||
const currentPage = useObserver(() => dashboardStore.metricsPage);
|
||||
const metricsSearch = useObserver(() => dashboardStore.metricsSearch);
|
||||
|
||||
const filterRE = getRE(metricsSearch, 'i');
|
||||
const list = widgets.filter(w => filterRE.test(w.name))
|
||||
|
||||
const totalPages = list.length;
|
||||
const pageSize = store.metricsPageSize;
|
||||
const pageSize = dashboardStore.metricsPageSize;
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const end = currentPage * pageSize;
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ function MetricsList(props: Props) {
|
|||
<Pagination
|
||||
page={currentPage}
|
||||
totalPages={Math.ceil(totalPages / pageSize)}
|
||||
onPageChange={(page) => store.updateKey('metricsPage', page)}
|
||||
onPageChange={(page) => dashboardStore.updateKey('metricsPage', page)}
|
||||
limit={pageSize}
|
||||
debounceRequest={100}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Icon } from 'UI';
|
||||
|
||||
function MetricsSearch(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const metricsSearch = useObserver(() => store.metricsSearch);
|
||||
const { dashboardStore } = useStore();
|
||||
const metricsSearch = useObserver(() => dashboardStore.metricsSearch);
|
||||
|
||||
|
||||
return useObserver(() => (
|
||||
|
|
@ -16,7 +16,7 @@ function MetricsSearch(props) {
|
|||
name="metricsSearch"
|
||||
className="bg-white p-2 border rounded w-full pl-10"
|
||||
placeholder="Filter by title, type, dashboard and owner"
|
||||
onChange={({ target: { name, value } }) => store.updateKey(name, value)}
|
||||
onChange={({ target: { name, value } }) => dashboardStore.updateKey(name, value)}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@ import React from 'react';
|
|||
import DropdownPlain from 'Shared/DropdownPlain';
|
||||
import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import { HelpText, Button, Icon } from 'UI'
|
||||
import FilterSeries from '../FilterSeries';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
interface Props {
|
||||
// metric: any,
|
||||
// editWidget: (metric, shouldFetch?) => void
|
||||
history: any;
|
||||
match: any;
|
||||
}
|
||||
|
||||
function WidgetForm(props: Props) {
|
||||
// const { metric } = props;
|
||||
const store: any = useDashboardStore();
|
||||
const metric = store.currentWidget;
|
||||
const { history, match: { params: { siteId, dashboardId, metricId } } } = props;
|
||||
const { dashboardStore } = useStore();
|
||||
const metric: any = dashboardStore.currentWidget;
|
||||
|
||||
const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries');
|
||||
const tableOptions = metricOf.filter(i => i.type === 'table');
|
||||
|
|
@ -23,28 +24,32 @@ function WidgetForm(props: Props) {
|
|||
const isTimeSeries = metric.metricType === 'timeseries';
|
||||
const _issueOptions = [{ text: 'All', value: 'all' }].concat(issueOptions);
|
||||
|
||||
const write = ({ target: { value, name } }) => store.editWidget({ [ name ]: value }, false);
|
||||
const write = ({ target: { value, name } }) => dashboardStore.editWidget({ [ name ]: value });
|
||||
const writeOption = (e, { value, name }) => {
|
||||
store.editWidget({ [ name ]: value }, false);
|
||||
dashboardStore.editWidget({ [ name ]: value });
|
||||
|
||||
if (name === 'metricValue') {
|
||||
store.editWidget({ metricValue: [value] }, false);
|
||||
dashboardStore.editWidget({ metricValue: [value] });
|
||||
}
|
||||
|
||||
if (name === 'metricOf') {
|
||||
if (value === FilterKey.ISSUE) {
|
||||
store.editWidget({ metricValue: ['all'] }, false);
|
||||
dashboardStore.editWidget({ metricValue: ['all'] });
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'metricType') {
|
||||
if (value === 'timeseries') {
|
||||
store.editWidget({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' }, false);
|
||||
dashboardStore.editWidget({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' });
|
||||
} else if (value === 'table') {
|
||||
store.editWidget({ metricOf: tableOptions[0].value, viewType: 'table' }, false);
|
||||
dashboardStore.editWidget({ metricOf: tableOptions[0].value, viewType: 'table' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
dashboardStore.saveMetric(metric, dashboardId);
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="p-4">
|
||||
|
|
@ -141,20 +146,30 @@ function WidgetForm(props: Props) {
|
|||
</div>
|
||||
|
||||
<div className="form-groups flex items-center justify-between">
|
||||
<Button primary size="small">Save</Button>
|
||||
<Button
|
||||
primary
|
||||
size="small"
|
||||
onClick={onSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
<Button plain size="small" className="flex items-center">
|
||||
<Icon name="trash" size="14" className="mr-2" color="teal"/>
|
||||
Delete
|
||||
</Button>
|
||||
<Button plain size="small" className="flex items-center ml-2">
|
||||
<Icon name="columns-gap" size="14" className="mr-2" color="teal"/>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
{metric.widgetId && (
|
||||
<>
|
||||
<Button plain size="small" className="flex items-center">
|
||||
<Icon name="trash" size="14" className="mr-2" color="teal"/>
|
||||
Delete
|
||||
</Button>
|
||||
<Button plain size="small" className="flex items-center ml-2">
|
||||
<Icon name="columns-gap" size="14" className="mr-2" color="teal"/>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default WidgetForm;
|
||||
export default withRouter(WidgetForm);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import WidgetWrapper from '../../WidgetWrapper';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Loader, NoContent, SegmentSelection, Icon } from 'UI';
|
||||
import DateRange from 'Shared/DateRange';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
|
|
@ -11,8 +11,8 @@ interface Props {
|
|||
}
|
||||
function WidgetPreview(props: Props) {
|
||||
const { className = '' } = props;
|
||||
const store: any = useDashboardStore();
|
||||
const metric = store.currentWidget;
|
||||
const { dashboardStore } = useStore();
|
||||
const metric: any = dashboardStore.currentWidget;
|
||||
const isTimeSeries = metric.metricType === 'timeseries';
|
||||
const isTable = metric.metricType === 'table';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { NoContent } from 'UI';
|
||||
import cn from 'classnames';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -9,8 +9,8 @@ interface Props {
|
|||
}
|
||||
function WidgetSessions(props: Props) {
|
||||
const { className = '' } = props;
|
||||
const store: any = useDashboardStore();
|
||||
const widget = store.currentWidget;
|
||||
const { dashboardStore } = useStore();
|
||||
const widget = dashboardStore.currentWidget;
|
||||
|
||||
return (
|
||||
<div className={cn(className)}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useStore } from 'App/mstore';
|
||||
import WidgetForm from '../WidgetForm';
|
||||
import WidgetPreview from '../WidgetPreview';
|
||||
import WidgetSessions from '../WidgetSessions';
|
||||
|
|
@ -11,8 +11,8 @@ interface Props {
|
|||
}
|
||||
function WidgetView(props: Props) {
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const store: any = useDashboardStore();
|
||||
const widget = store.currentWidget;
|
||||
const { dashboardStore } = useStore();
|
||||
const widget = dashboardStore.currentWidget;
|
||||
return (
|
||||
<div className="page-margin container-70 mb-8">
|
||||
<div className="bg-white rounded border">
|
||||
|
|
|
|||
|
|
@ -1,15 +1,39 @@
|
|||
import { makeAutoObservable, observable, action, runInAction } from "mobx"
|
||||
import Widget from "./widget"
|
||||
// import APIClient from 'App/api_client';
|
||||
import Widget, { IWidget } from "./widget"
|
||||
import { dashboardService } from 'App/services'
|
||||
|
||||
export default class Dashboard {
|
||||
export interface IDashboard {
|
||||
dashboardId: any
|
||||
name: string
|
||||
isPublic: boolean
|
||||
widgets: IWidget[]
|
||||
isValid: boolean
|
||||
isPinned: boolean
|
||||
currentWidget: IWidget
|
||||
|
||||
update(data: any): void
|
||||
toJson(): any
|
||||
fromJson(json: any): void
|
||||
validate(): void
|
||||
addWidget(widget: IWidget): void
|
||||
removeWidget(widgetId: string): void
|
||||
updateWidget(widget: IWidget): void
|
||||
getWidget(widgetId: string): void
|
||||
getWidgetIndex(widgetId: string)
|
||||
getWidgetByIndex(index: number): void
|
||||
getWidgetCount(): void
|
||||
getWidgetIndexByWidgetId(widgetId: string): void
|
||||
swapWidgetPosition(positionA: number, positionB: number): void
|
||||
sortWidgets(): void
|
||||
}
|
||||
export default class Dashboard implements IDashboard {
|
||||
dashboardId: any = undefined
|
||||
name: string = "New Dashboard"
|
||||
isPublic: boolean = false
|
||||
widgets: Widget[] = []
|
||||
widgets: IWidget[] = []
|
||||
isValid: boolean = false
|
||||
isPinned: boolean = false
|
||||
currentWidget: Widget = new Widget()
|
||||
currentWidget: IWidget = new Widget()
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
|
|
@ -57,8 +81,8 @@ export default class Dashboard {
|
|||
runInAction(() => {
|
||||
this.dashboardId = json.dashboardId
|
||||
this.name = json.name
|
||||
this.isPublic = json.isPrivate
|
||||
this.widgets = json.widgets.map(w => new Widget().fromJson(w))
|
||||
this.isPublic = json.isPublic
|
||||
this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)) : []
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
|
@ -68,7 +92,7 @@ export default class Dashboard {
|
|||
return this.isValid = this.name.length > 0
|
||||
}
|
||||
|
||||
addWidget(widget: Widget) {
|
||||
addWidget(widget: IWidget) {
|
||||
this.widgets.push(widget)
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +100,7 @@ export default class Dashboard {
|
|||
this.widgets = this.widgets.filter(w => w.widgetId !== widgetId)
|
||||
}
|
||||
|
||||
updateWidget(widget: Widget) {
|
||||
updateWidget(widget: IWidget) {
|
||||
const index = this.widgets.findIndex(w => w.widgetId === widget.widgetId)
|
||||
if (index >= 0) {
|
||||
this.widgets[index] = widget
|
||||
|
|
|
|||
|
|
@ -1,20 +1,69 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
import Dashboard from "./dashboard"
|
||||
import Dashboard, { IDashboard } from "./dashboard"
|
||||
import APIClient from 'App/api_client';
|
||||
import Widget from "./widget";
|
||||
export default class DashboardStore {
|
||||
import Widget, { IWidget } from "./widget";
|
||||
import { dashboardService } from "App/services";
|
||||
|
||||
export interface IDashboardSotre {
|
||||
dashboards: IDashboard[]
|
||||
widgetTemplates: any[]
|
||||
selectedDashboard: IDashboard | null
|
||||
dashboardInstance: IDashboard
|
||||
siteId: any
|
||||
currentWidget: Widget
|
||||
widgetCategories: any[]
|
||||
widgets: Widget[]
|
||||
metricsPage: number
|
||||
metricsPageSize: number
|
||||
metricsSearch: string
|
||||
|
||||
isLoading: boolean
|
||||
isSaving: boolean
|
||||
|
||||
initDashboard(dashboard?: IDashboard): void
|
||||
updateKey(key: string, value: any): void
|
||||
resetCurrentWidget(): void
|
||||
editWidget(widget: any): void
|
||||
fetchList(): void
|
||||
fetch(dashboardId: string)
|
||||
save(dashboard: IDashboard): Promise<any>
|
||||
saveDashboardWidget(dashboard: Dashboard, widget: Widget)
|
||||
delete(dashboard: IDashboard)
|
||||
toJson(): void
|
||||
fromJson(json: any): void
|
||||
initDashboard(dashboard: IDashboard): void
|
||||
addDashboard(dashboard: IDashboard): void
|
||||
removeDashboard(dashboard: IDashboard): void
|
||||
getDashboard(dashboardId: string): void
|
||||
getDashboardIndex(dashboardId: string): number
|
||||
getDashboardCount(): void
|
||||
getDashboardIndexByDashboardId(dashboardId: string): number
|
||||
updateDashboard(dashboard: IDashboard): void
|
||||
selectDashboardById(dashboardId: string): void
|
||||
setSiteId(siteId: any): void
|
||||
selectDefaultDashboard(): Promise<IDashboard>
|
||||
|
||||
saveMetric(metric: IWidget, dashboardId?: string): Promise<any>
|
||||
}
|
||||
export default class DashboardStore implements IDashboardSotre {
|
||||
siteId: any = null
|
||||
// Dashbaord / Widgets
|
||||
dashboards: Dashboard[] = []
|
||||
widgetTemplates: any[] = []
|
||||
selectedDashboard: Dashboard | null = new Dashboard()
|
||||
newDashboard: Dashboard = new Dashboard()
|
||||
isLoading: boolean = false
|
||||
siteId: any = null
|
||||
dashboardInstance: IDashboard = new Dashboard()
|
||||
currentWidget: Widget = new Widget()
|
||||
widgetCategories: any[] = []
|
||||
widgets: Widget[] = []
|
||||
|
||||
// Metrics
|
||||
metricsPage: number = 1
|
||||
metricsPageSize: number = 10
|
||||
metricsSearch: string = ''
|
||||
|
||||
// Loading states
|
||||
isLoading: boolean = false
|
||||
isSaving: boolean = false
|
||||
|
||||
private client = new APIClient()
|
||||
|
||||
|
|
@ -40,25 +89,15 @@ export default class DashboardStore {
|
|||
|
||||
|
||||
// TODO remove this sample data
|
||||
this.dashboards = sampleDashboards
|
||||
// this.selectedDashboard = sampleDashboards[0]
|
||||
// this.dashboards = sampleDashboards
|
||||
|
||||
// setInterval(() => {
|
||||
// this.selectedDashboard?.addWidget(getRandomWidget())
|
||||
// }, 3000)
|
||||
|
||||
// setInterval(() => {
|
||||
// this.selectedDashboard?.widgets[4].update({ position: 2 })
|
||||
// this.selectedDashboard?.swapWidgetPosition(2, 0)
|
||||
// }, 3000)
|
||||
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const widget: any= {};
|
||||
widget.widgetId = `${i}`
|
||||
widget.name = `Widget ${i}`;
|
||||
widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
this.widgets.push(widget)
|
||||
}
|
||||
// for (let i = 0; i < 15; i++) {
|
||||
// const widget: any= {};
|
||||
// widget.widgetId = `${i}`
|
||||
// widget.name = `Widget ${i}`;
|
||||
// widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
// this.widgets.push(widget)
|
||||
// }
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const cat: any = {
|
||||
|
|
@ -79,7 +118,10 @@ export default class DashboardStore {
|
|||
|
||||
this.widgetCategories.push(cat)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
initDashboard(dashboard: Dashboard) {
|
||||
this.dashboardInstance = dashboard || new Dashboard()
|
||||
}
|
||||
|
||||
updateKey(key: any, value: any) {
|
||||
|
|
@ -90,59 +132,71 @@ export default class DashboardStore {
|
|||
this.currentWidget = new Widget()
|
||||
}
|
||||
|
||||
editWidget(widget: Widget) {
|
||||
editWidget(widget: any) {
|
||||
this.currentWidget.update(widget)
|
||||
}
|
||||
|
||||
fetchList() {
|
||||
this.isLoading = true
|
||||
|
||||
this.client.get('/dashboards')
|
||||
.then(response => {
|
||||
dashboardService.getDashboards()
|
||||
.then((list: any) => {
|
||||
runInAction(() => {
|
||||
this.dashboards = list.map(d => new Dashboard().fromJson(d))
|
||||
})
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.dashboards = response.data.map(d => new Dashboard().fromJson(d))
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fetch(dashboardId: string) {
|
||||
this.isLoading = true
|
||||
this.client.get(`/dashboards/${dashboardId}`)
|
||||
.then(response => {
|
||||
runInAction(() => {
|
||||
this.selectedDashboard = new Dashboard().fromJson(response.data)
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
)
|
||||
dashboardService.getDashboard(dashboardId).then(response => {
|
||||
runInAction(() => {
|
||||
this.selectedDashboard = new Dashboard().fromJson(response)
|
||||
})
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
save(dashboard: Dashboard) {
|
||||
dashboard.validate()
|
||||
if (dashboard.isValid) {
|
||||
this.isLoading = true
|
||||
if (dashboard.dashboardId) {
|
||||
this.client.put(`/dashboards/${dashboard.dashboardId}`, dashboard.toJson())
|
||||
.then(response => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this.client.post('/dashboards', dashboard.toJson())
|
||||
.then(response => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
alert("Invalid dashboard") // TODO show validation errors
|
||||
}
|
||||
save(dashboard: IDashboard): Promise<any> {
|
||||
this.isSaving = true
|
||||
const isCreating = !dashboard.dashboardId
|
||||
return dashboardService.saveDashboard(dashboard).then(response => {
|
||||
runInAction(() => {
|
||||
if (isCreating) {
|
||||
this.addDashboard(response.data)
|
||||
} else {
|
||||
this.updateDashboard(response.data)
|
||||
}
|
||||
})
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.isSaving = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
saveMetric(metric: IWidget, dashboardId: string): Promise<any> {
|
||||
const isCreating = !metric.widgetId
|
||||
return dashboardService.saveMetric(metric, dashboardId).then(metric => {
|
||||
runInAction(() => {
|
||||
if (isCreating) {
|
||||
this.selectedDashboard?.widgets.push(metric)
|
||||
} else {
|
||||
this.selectedDashboard?.widgets.map(w => {
|
||||
if (w.widgetId === metric.widgetId) {
|
||||
w.update(metric)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
saveDashboardWidget(dashboard: Dashboard, widget: Widget) {
|
||||
|
|
@ -193,10 +247,6 @@ export default class DashboardStore {
|
|||
return this
|
||||
}
|
||||
|
||||
initDashboard(dashboard: Dashboard | null) {
|
||||
this.selectedDashboard = dashboard || new Dashboard()
|
||||
}
|
||||
|
||||
addDashboard(dashboard: Dashboard) {
|
||||
this.dashboards.push(dashboard)
|
||||
}
|
||||
|
|
@ -234,13 +284,16 @@ export default class DashboardStore {
|
|||
|
||||
selectDashboardById = (dashboardId: any) => {
|
||||
this.selectedDashboard = this.dashboards.find(d => d.dashboardId == dashboardId) || new Dashboard();
|
||||
if (this.selectedDashboard.dashboardId) {
|
||||
this.fetch(this.selectedDashboard.dashboardId)
|
||||
}
|
||||
}
|
||||
|
||||
setSiteId = (siteId: any) => {
|
||||
this.siteId = siteId
|
||||
}
|
||||
|
||||
selectDefaultDashboard = () => {
|
||||
selectDefaultDashboard = (): Promise<Dashboard> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.dashboards.length > 0) {
|
||||
const pinnedDashboard = this.dashboards.find(d => d.isPinned)
|
||||
|
|
@ -250,7 +303,11 @@ export default class DashboardStore {
|
|||
this.selectedDashboard = this.dashboards[0]
|
||||
}
|
||||
}
|
||||
resolve(this.selectedDashboard)
|
||||
if (this.selectedDashboard) {
|
||||
resolve(this.selectedDashboard)
|
||||
}
|
||||
|
||||
reject(new Error("No dashboards found"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,36 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "m
|
|||
import Filter from 'Types/filter';
|
||||
import FilterSeries from "./filterSeries";
|
||||
|
||||
export default class Widget {
|
||||
export interface IWidget {
|
||||
widgetId: any
|
||||
name: string
|
||||
metricType: string
|
||||
metricOf: string
|
||||
metricValue: string
|
||||
viewType: string
|
||||
series: FilterSeries[]
|
||||
sessions: []
|
||||
isPublic: boolean
|
||||
owner: string
|
||||
lastModified: Date
|
||||
dashboardIds: any[]
|
||||
|
||||
position: number
|
||||
data: any
|
||||
isLoading: boolean
|
||||
isValid: boolean
|
||||
dashboardId: any
|
||||
colSpan: number
|
||||
|
||||
udpateKey(key: string, value: any): void
|
||||
removeSeries(index: number): void
|
||||
addSeries(): void
|
||||
fromJson(json: any): void
|
||||
toJson(): any
|
||||
validate(): void
|
||||
update(data: any): void
|
||||
}
|
||||
export default class Widget implements IWidget {
|
||||
widgetId: any = undefined
|
||||
name: string = "New Metric"
|
||||
metricType: string = "timeseries"
|
||||
|
|
@ -11,7 +40,7 @@ export default class Widget {
|
|||
viewType: string = "lineChart"
|
||||
series: FilterSeries[] = []
|
||||
sessions: [] = []
|
||||
isPrivate: boolean = false
|
||||
isPublic: boolean = false
|
||||
owner: string = ""
|
||||
lastModified: Date = new Date()
|
||||
dashboardIds: any[] = []
|
||||
|
|
@ -76,7 +105,12 @@ export default class Widget {
|
|||
return {
|
||||
widgetId: this.widgetId,
|
||||
name: this.name,
|
||||
data: this.data
|
||||
metricOf: this.metricOf,
|
||||
metricValue: this.metricValue,
|
||||
viewType: this.viewType,
|
||||
series: this.series,
|
||||
sessions: this.sessions,
|
||||
isPublic: this.isPublic,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { useModal } from '.';
|
||||
import ModalOverlay from './ModalOverlay';
|
||||
|
||||
export default class Modal extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.el = document.createElement('div');
|
||||
}
|
||||
export default function Modal({ children }){
|
||||
const { component } = useModal();
|
||||
|
||||
render() {
|
||||
return ReactDOM.createPortal(
|
||||
this.props.children,
|
||||
this.el,
|
||||
);
|
||||
}
|
||||
return component ? ReactDOM.createPortal(
|
||||
<ModalOverlay>
|
||||
{component}
|
||||
</ModalOverlay>,
|
||||
document.querySelector("#modal-root"),
|
||||
) : null;
|
||||
}
|
||||
32
frontend/app/components/Modal/ModalOverlay.css
Normal file
32
frontend/app/components/Modal/ModalOverlay.css
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
.overlay {
|
||||
/* absolute w-full h-screen cursor-pointer */
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
cursor: pointer;
|
||||
/* transition: all 0.3s ease-in-out; */
|
||||
animation: fade 1s forwards;
|
||||
}
|
||||
.slide {
|
||||
position: absolute;
|
||||
left: -100%;
|
||||
-webkit-animation: slide 0.5s forwards;
|
||||
animation: slide 0.5s forwards;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes slide {
|
||||
100% { left: 0; }
|
||||
}
|
||||
|
||||
@keyframes slide {
|
||||
100% { left: 0; }
|
||||
}
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
import React from 'react';
|
||||
import { ModalContext } from "App/components/Modal/modalContext";
|
||||
import useModal from 'App/components/Modal/useModal';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import stl from './ModalOverlay.css'
|
||||
|
||||
function ModalOverlay({ children }) {
|
||||
let modal = useModal();
|
||||
// console.log('m', m);
|
||||
|
||||
return (
|
||||
<div onClick={() => modal.handleModal(false)} style={{ background: "rgba(0,0,0,0.8)", zIndex: '9999' }}>
|
||||
{children}
|
||||
<div className="fixed w-full h-screen" style={{ zIndex: '99999' }}>
|
||||
<div
|
||||
onClick={() => modal.hideModal()}
|
||||
className={stl.overlay}
|
||||
style={{ background: "rgba(0,0,0,0.5)" }}
|
||||
/>
|
||||
<div className={stl.slide}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
44
frontend/app/components/Modal/index.tsx
Normal file
44
frontend/app/components/Modal/index.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import React, { Component, createContext } from 'react';
|
||||
import Modal from './Modal';
|
||||
|
||||
const ModalContext = createContext({
|
||||
component: null,
|
||||
props: {},
|
||||
showModal: (component: any, props: any) => {},
|
||||
hideModal: () => {}
|
||||
});
|
||||
|
||||
export class ModalProvider extends Component {
|
||||
showModal = (component, props = {}) => {
|
||||
this.setState({
|
||||
component,
|
||||
props
|
||||
});
|
||||
};
|
||||
|
||||
hideModal = () =>
|
||||
this.setState({
|
||||
component: null,
|
||||
props: {}
|
||||
});
|
||||
|
||||
state = {
|
||||
component: null,
|
||||
props: {},
|
||||
showModal: this.showModal,
|
||||
hideModal: this.hideModal
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ModalContext.Provider value={this.state}>
|
||||
<Modal />
|
||||
{this.props.children}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ModalConsumer = ModalContext.Consumer;
|
||||
|
||||
export const useModal = () => React.useContext(ModalContext);
|
||||
|
|
@ -5,28 +5,27 @@ import { Provider } from 'react-redux';
|
|||
|
||||
import store from './store';
|
||||
import Router from './Router';
|
||||
import DashboardStore from './components/Dashboard/store';
|
||||
import { DashboardStoreProvider } from './components/Dashboard/store/store';
|
||||
import { ModalProvider } from './components/Modal/ModalContext';
|
||||
import { StoreProvider, RootStore } from './mstore';
|
||||
import { ModalProvider } from './components/Modal';
|
||||
import ModalRoot from './components/Modal/ModalRoot';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||
import { DndProvider } from 'react-dnd'
|
||||
import Modal from 'react-modal';
|
||||
|
||||
|
||||
Modal.setAppElement('#modal-root');
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const dashboardStore = new DashboardStore();
|
||||
render(
|
||||
(
|
||||
<Provider store={ store }>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<DashboardStoreProvider store={ dashboardStore }>
|
||||
<StoreProvider store={new RootStore()}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<ModalProvider>
|
||||
<ModalRoot />
|
||||
<Router />
|
||||
<ModalRoot />
|
||||
<Router />
|
||||
</ModalProvider>
|
||||
</DashboardStoreProvider>
|
||||
</DndProvider>
|
||||
</DndProvider>
|
||||
</StoreProvider>
|
||||
</Provider>
|
||||
),
|
||||
document.getElementById('app'),
|
||||
|
|
|
|||
24
frontend/app/mstore/index.tsx
Normal file
24
frontend/app/mstore/index.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import DashboardStore, { IDashboardSotre } from 'App/components/Dashboard/store/DashboardStore';
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: IDashboardSotre;
|
||||
constructor() {
|
||||
this.dashboardStore = new DashboardStore();
|
||||
}
|
||||
}
|
||||
|
||||
const StoreContext = React.createContext<RootStore>({} as RootStore);
|
||||
|
||||
export const StoreProvider = ({ children, store }) => {
|
||||
return (
|
||||
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useStore = () => React.useContext(StoreContext);
|
||||
|
||||
export const withStore = (Component) => (props) => {
|
||||
return <Component {...props} store={useStore()} />;
|
||||
};
|
||||
|
||||
142
frontend/app/services/DashboardService.ts
Normal file
142
frontend/app/services/DashboardService.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { IDashboard } from "App/components/Dashboard/store/dashboard";
|
||||
import APIClient from 'App/api_client';
|
||||
import { IWidget } from "App/components/Dashboard/store/widget";
|
||||
|
||||
export interface IDashboardService {
|
||||
initClient(): void
|
||||
getWidgets(dashboardId: string): Promise<any>
|
||||
|
||||
getDashboards(): Promise<any[]>
|
||||
getDashboard(dashboardId: string): Promise<any>
|
||||
|
||||
saveDashboard(dashboard: IDashboard): Promise<any>
|
||||
deleteDashboard(dashboardId: string): Promise<any>
|
||||
|
||||
saveMetric(metric: IWidget, dashboardId?: string): Promise<any>
|
||||
deleteMetric(metricId: string): Promise<any>
|
||||
|
||||
saveWidget(dashboardId: string, widget: IWidget): Promise<any>
|
||||
deleteWidget(dashboardId: string, widgetId: string): Promise<any>
|
||||
}
|
||||
|
||||
|
||||
export class DashboardService implements IDashboardService {
|
||||
private client: APIClient;
|
||||
|
||||
constructor(client?: APIClient) {
|
||||
this.client = client ? client : new APIClient();
|
||||
}
|
||||
|
||||
initClient() {
|
||||
this.client = new APIClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all widgets from a dashboard.
|
||||
* @param dashboardId Required
|
||||
* @returns
|
||||
*/
|
||||
getWidgets(dashboardId: string): Promise<any> {
|
||||
return this.client.get(`/dashboards/${dashboardId}/widgets`)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all dashboards.
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
getDashboards(): Promise<any[]> {
|
||||
return this.client.get('/dashboards')
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dashboard by dashboardId.
|
||||
* @param dashboardId
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
getDashboard(dashboardId: string): Promise<any> {
|
||||
return this.client.get('/dashboards/' + dashboardId)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a dashboard.
|
||||
* @param dashboard Required
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
saveDashboard(dashboard: IDashboard): Promise<any> {
|
||||
const data = dashboard.toJson();
|
||||
if (dashboard.dashboardId) {
|
||||
return this.client.put(`/dashboards/${dashboard.dashboardId}`, data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
} else {
|
||||
return this.client.post('/dashboards', data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a dashboard.
|
||||
* @param dashboardId
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
deleteDashboard(dashboardId: string): Promise<any> {
|
||||
return this.client.delete(`/dashboards/${dashboardId}`)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new Meitrc, if the dashboardId is not provided,
|
||||
* it will add the metric to the dashboard.
|
||||
* @param metric Required
|
||||
* @param dashboardId Optional
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
saveMetric(metric: IWidget, dashboardId?: string): Promise<any> {
|
||||
const data = metric.toJson();
|
||||
|
||||
const path = dashboardId ? `/metrics` : '/metrics'; // TODO change to /dashboards/:dashboardId/widgets
|
||||
// const path = dashboardId ? `/dashboards/${dashboardId}/widgets` : '/widgets';
|
||||
if (metric.widgetId) {
|
||||
return this.client.put(path + '/' + metric.widgetId, data)
|
||||
} else {
|
||||
return this.client.post(path, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Metric by metricId.
|
||||
* @param metricId
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
deleteMetric(metricId: string): Promise<any> {
|
||||
return this.client.delete(`/metrics/${metricId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a widget from a dashboard.
|
||||
* @param dashboardId Required
|
||||
* @param widgetId Required
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
deleteWidget(dashboardId: string, widgetId: string): Promise<any> {
|
||||
return this.client.delete(`/dashboards/${dashboardId}/widgets/${widgetId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a widget to a dashboard.
|
||||
* @param dashboardId Required
|
||||
* @param widget Required
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
saveWidget(dashboardId: string, widget: IWidget): Promise<any> {
|
||||
return this.client.post(`/dashboards/${dashboardId}/widgets`, widget.toJson())
|
||||
}
|
||||
}
|
||||
3
frontend/app/services/index.ts
Normal file
3
frontend/app/services/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { DashboardService, IDashboardService } from "./DashboardService";
|
||||
|
||||
export const dashboardService: IDashboardService = new DashboardService();
|
||||
Loading…
Add table
Reference in a new issue