-
+
Right
@@ -32,4 +29,4 @@ function DashboardView(props) {
)
}
-export default withDashboardStore(withModal(observer(DashboardView)));
\ No newline at end of file
+export default withModal(observer(DashboardView));
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx
index 1b1256793..237d5fcb4 100644
--- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx
+++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx
@@ -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(() => (
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) {
store.updateKey('metricsPage', page)}
+ onPageChange={(page) => dashboardStore.updateKey('metricsPage', page)}
limit={pageSize}
debounceRequest={100}
/>
diff --git a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx
index 45c209350..ce4bdbe24 100644
--- a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx
+++ b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx
@@ -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)}
/>
));
diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
index 631a3b791..9cd0002dc 100644
--- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
@@ -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(() => (
@@ -141,20 +146,30 @@ function WidgetForm(props: Props) {
-
+
-
-
+ {metric.widgetId && (
+ <>
+
+
+ >
+ )}
));
}
-export default WidgetForm;
\ No newline at end of file
+export default withRouter(WidgetForm);
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx
index 12ec3113b..1e39c2c63 100644
--- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx
@@ -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';
diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx
index 60eb1a569..cb39f7d3d 100644
--- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx
@@ -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 (
diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx
index cbe2d38e4..4ad9eaa00 100644
--- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx
@@ -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 (
diff --git a/frontend/app/components/Dashboard/store/dashboard.ts b/frontend/app/components/Dashboard/store/dashboard.ts
index dbc6d221f..d2e4596d5 100644
--- a/frontend/app/components/Dashboard/store/dashboard.ts
+++ b/frontend/app/components/Dashboard/store/dashboard.ts
@@ -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
diff --git a/frontend/app/components/Dashboard/store/dashboardStore.ts b/frontend/app/components/Dashboard/store/dashboardStore.ts
index 8373c3fe9..8ca3f6c2e 100644
--- a/frontend/app/components/Dashboard/store/dashboardStore.ts
+++ b/frontend/app/components/Dashboard/store/dashboardStore.ts
@@ -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
+ 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
+
+ saveMetric(metric: IWidget, dashboardId?: string): Promise
+}
+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 {
+ 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 {
+ 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 => {
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"))
})
}
}
diff --git a/frontend/app/components/Dashboard/store/widget.ts b/frontend/app/components/Dashboard/store/widget.ts
index 5b0329cab..481e7c969 100644
--- a/frontend/app/components/Dashboard/store/widget.ts
+++ b/frontend/app/components/Dashboard/store/widget.ts
@@ -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,
}
}
diff --git a/frontend/app/components/Modal/Modal.js b/frontend/app/components/Modal/Modal.js
index af0eff6b3..baf226621 100644
--- a/frontend/app/components/Modal/Modal.js
+++ b/frontend/app/components/Modal/Modal.js
@@ -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(
+
+ {component}
+ ,
+ document.querySelector("#modal-root"),
+ ) : null;
}
\ No newline at end of file
diff --git a/frontend/app/components/Modal/ModalOverlay.css b/frontend/app/components/Modal/ModalOverlay.css
new file mode 100644
index 000000000..e3e33562a
--- /dev/null
+++ b/frontend/app/components/Modal/ModalOverlay.css
@@ -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; }
+}
\ No newline at end of file
diff --git a/frontend/app/components/Modal/ModalOverlay.tsx b/frontend/app/components/Modal/ModalOverlay.tsx
index 003f29ee6..8660f53a4 100644
--- a/frontend/app/components/Modal/ModalOverlay.tsx
+++ b/frontend/app/components/Modal/ModalOverlay.tsx
@@ -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 (
- modal.handleModal(false)} style={{ background: "rgba(0,0,0,0.8)", zIndex: '9999' }}>
- {children}
+
+
modal.hideModal()}
+ className={stl.overlay}
+ style={{ background: "rgba(0,0,0,0.5)" }}
+ />
+
{children}
);
}
diff --git a/frontend/app/components/Modal/index.tsx b/frontend/app/components/Modal/index.tsx
new file mode 100644
index 000000000..f822178d3
--- /dev/null
+++ b/frontend/app/components/Modal/index.tsx
@@ -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 (
+
+
+ {this.props.children}
+
+ );
+ }
+}
+
+export const ModalConsumer = ModalContext.Consumer;
+
+export const useModal = () => React.useContext(ModalContext);
\ No newline at end of file
diff --git a/frontend/app/initialize.js b/frontend/app/initialize.js
index 629ea33ad..df06dbbbe 100644
--- a/frontend/app/initialize.js
+++ b/frontend/app/initialize.js
@@ -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(
(
-
-
+
+
-
-
+
+
-
-
+
+
),
document.getElementById('app'),
diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx
new file mode 100644
index 000000000..ce928b399
--- /dev/null
+++ b/frontend/app/mstore/index.tsx
@@ -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
({} as RootStore);
+
+export const StoreProvider = ({ children, store }) => {
+ return (
+ {children}
+ );
+};
+
+export const useStore = () => React.useContext(StoreContext);
+
+export const withStore = (Component) => (props) => {
+ return ;
+};
+
diff --git a/frontend/app/services/DashboardService.ts b/frontend/app/services/DashboardService.ts
new file mode 100644
index 000000000..871fabb3f
--- /dev/null
+++ b/frontend/app/services/DashboardService.ts
@@ -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
+
+ getDashboards(): Promise
+ getDashboard(dashboardId: string): Promise
+
+ saveDashboard(dashboard: IDashboard): Promise
+ deleteDashboard(dashboardId: string): Promise
+
+ saveMetric(metric: IWidget, dashboardId?: string): Promise
+ deleteMetric(metricId: string): Promise
+
+ saveWidget(dashboardId: string, widget: IWidget): Promise
+ deleteWidget(dashboardId: string, widgetId: string): Promise
+}
+
+
+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 {
+ return this.client.get(`/dashboards/${dashboardId}/widgets`)
+ .then(response => response.json())
+ .then(response => response.data || []);
+ }
+
+
+ /**
+ * Get all dashboards.
+ * @returns {Promise}
+ */
+ getDashboards(): Promise {
+ return this.client.get('/dashboards')
+ .then(response => response.json())
+ .then(response => response.data || []);
+ }
+
+ /**
+ * Get a dashboard by dashboardId.
+ * @param dashboardId
+ * @returns {Promise}
+ */
+ getDashboard(dashboardId: string): Promise {
+ return this.client.get('/dashboards/' + dashboardId)
+ .then(response => response.json())
+ .then(response => response.data || {});
+ }
+
+ /**
+ * Create or update a dashboard.
+ * @param dashboard Required
+ * @returns {Promise}
+ */
+ saveDashboard(dashboard: IDashboard): Promise {
+ 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}
+ */
+ deleteDashboard(dashboardId: string): Promise {
+ 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}
+ */
+ saveMetric(metric: IWidget, dashboardId?: string): Promise {
+ 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}
+ */
+ deleteMetric(metricId: string): Promise {
+ return this.client.delete(`/metrics/${metricId}`)
+ }
+
+ /**
+ * Remove a widget from a dashboard.
+ * @param dashboardId Required
+ * @param widgetId Required
+ * @returns {Promise}
+ */
+ deleteWidget(dashboardId: string, widgetId: string): Promise {
+ return this.client.delete(`/dashboards/${dashboardId}/widgets/${widgetId}`)
+ }
+
+ /**
+ * Add a widget to a dashboard.
+ * @param dashboardId Required
+ * @param widget Required
+ * @returns {Promise}
+ */
+ saveWidget(dashboardId: string, widget: IWidget): Promise {
+ return this.client.post(`/dashboards/${dashboardId}/widgets`, widget.toJson())
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts
new file mode 100644
index 000000000..49ad0eb0c
--- /dev/null
+++ b/frontend/app/services/index.ts
@@ -0,0 +1,3 @@
+import { DashboardService, IDashboardService } from "./DashboardService";
+
+export const dashboardService: IDashboardService = new DashboardService();