mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
[WIP] node js performance monitor
This commit is contained in:
@@ -16,7 +16,14 @@
|
||||
min-width: 250px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.db-select-in {
|
||||
width: 150px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.db-select-in-flexible {
|
||||
min-width: 150px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.db-select-ne {
|
||||
width: 120px;
|
||||
font-size: 14px;
|
||||
@@ -42,6 +49,7 @@
|
||||
.db-select-nw > div,
|
||||
.db-select-ne > div,
|
||||
.db-select-pr > div,
|
||||
.db-select-in > div,
|
||||
.db-select-nw-300 > div {
|
||||
-webkit-flex-flow: unset;
|
||||
-ms-flex-flow: unset;
|
||||
@@ -50,7 +58,8 @@
|
||||
}
|
||||
|
||||
.db-select-nw > span + div,
|
||||
.db-select-pr > span + div {
|
||||
.db-select-pr > span + div,
|
||||
.db-select-in > span + div {
|
||||
border: none;
|
||||
box-shadow: 0 0 0 1px rgba(50, 50, 93, 0), 0 0 0 1px rgba(50, 151, 211, 0.2),
|
||||
0 0 0 2px rgba(50, 151, 211, 0.25), 0 1px 1px rgba(0, 0, 0, 0.08);
|
||||
|
||||
17
dashboard/src/actions/performanceMonitoring.js
Normal file
17
dashboard/src/actions/performanceMonitoring.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export function setStartDate(date) {
|
||||
return function(dispatch) {
|
||||
dispatch({
|
||||
type: 'SET_START_DATE',
|
||||
payload: date,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function setEndDate(date) {
|
||||
return function(dispatch) {
|
||||
dispatch({
|
||||
type: 'SET_END_DATE',
|
||||
payload: date,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import originalMoment from 'moment';
|
||||
@@ -16,9 +16,13 @@ function DateTimeRangePicker({
|
||||
style,
|
||||
}) {
|
||||
const currentDate = moment();
|
||||
const [key, setkey] = useState(0);
|
||||
useEffect(() => {
|
||||
setkey(key + 1);
|
||||
}, [currentDateRange.startDate, currentDateRange.endDate]);
|
||||
return (
|
||||
<div>
|
||||
<form id={formId}>
|
||||
<form id={formId} key={key}>
|
||||
<ShouldRender if={currentDateRange}>
|
||||
<div className="db-DateRangeInputWithComparison">
|
||||
<div
|
||||
@@ -77,10 +81,10 @@ function DateTimeRangePicker({
|
||||
DateTimeRangePicker.displayName = 'DateTimeRangePicker';
|
||||
|
||||
DateTimeRangePicker.propTypes = {
|
||||
handleStartDateTimeChange: PropTypes.func,
|
||||
handleEndDateTimeChange: PropTypes.func,
|
||||
currentDateRange: PropTypes.object,
|
||||
formId: PropTypes.string,
|
||||
handleEndDateTimeChange: PropTypes.func,
|
||||
handleStartDateTimeChange: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
162
dashboard/src/components/basic/performanceChart.js
Normal file
162
dashboard/src/components/basic/performanceChart.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Spinner } from '../basic/Loader';
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
AreaChart as Chart,
|
||||
Area,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
YAxis,
|
||||
XAxis,
|
||||
} from 'recharts';
|
||||
import * as _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
const noDataStyle = {
|
||||
textAlign: 'center',
|
||||
flexBasis: 1,
|
||||
};
|
||||
|
||||
const CustomTooltip = ({ active, payload }) => {
|
||||
if (active) {
|
||||
return (
|
||||
<div className="custom-tooltip">
|
||||
{payload[0].payload.name ? (
|
||||
<>
|
||||
<h3>{payload[0].payload.name}</h3>
|
||||
<p className="label">{`${payload[0].name} : ${payload[0].payload.display}`}</p>
|
||||
</>
|
||||
) : (
|
||||
<h3>No data available</h3>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
CustomTooltip.displayName = 'CustomTooltip';
|
||||
|
||||
CustomTooltip.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
payload: PropTypes.array,
|
||||
};
|
||||
|
||||
class PerformanceChart extends Component {
|
||||
calcPercent = (val, total) => {
|
||||
val = parseFloat(val);
|
||||
total = parseFloat(total);
|
||||
|
||||
if (isNaN(val) || isNaN(total)) {
|
||||
return 0;
|
||||
}
|
||||
if (!total || total === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (!val || val === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (val / total) * 100;
|
||||
};
|
||||
|
||||
parseDate(a) {
|
||||
return new Date(a).toLocaleString();
|
||||
}
|
||||
getTime(a) {
|
||||
return moment(a).format('LT');
|
||||
}
|
||||
render() {
|
||||
const { type, data, name, symbol, requesting } = this.props;
|
||||
let processedData = [{ display: '', name: '', v: '' }];
|
||||
if (requesting) {
|
||||
return (
|
||||
<div style={noDataStyle}>
|
||||
<div
|
||||
className="Box-root Flex-flex Flex-alignItems--center Flex-justifyContent--center"
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
margin: 0,
|
||||
color: '#4c4c4c',
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
<Spinner style={{ stroke: '#8898aa' }} />{' '}
|
||||
<span style={{ width: 10 }} />
|
||||
We're currently in the process of collecting data
|
||||
for this monitor. <br />
|
||||
More info will be available in few minutes
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (data && data.length > 0) {
|
||||
processedData = data.map(a => {
|
||||
return {
|
||||
name: a.intervalDate || this.parseDate(a.createdAt),
|
||||
v: a.value,
|
||||
display: `${Math.round(a.value || 0)} ${symbol || 'ms'}`,
|
||||
xData: a.createdAt,
|
||||
};
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<Chart data={processedData}>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<YAxis
|
||||
type="number"
|
||||
domain={[0, 'dataMax']}
|
||||
tickCount={5}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
mirror={true}
|
||||
tickMargin={-10}
|
||||
/>
|
||||
<XAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
padding={{ left: 30 }}
|
||||
dataKey={'xData'}
|
||||
tickFormatter={this.getTime}
|
||||
tickMargin={10}
|
||||
/>
|
||||
<Area
|
||||
type="linear"
|
||||
isAnimationActive={false}
|
||||
name={_.startCase(_.toLower(`${name} ${type}`))}
|
||||
dataKey="v"
|
||||
stroke="#000000"
|
||||
strokeWidth={1.5}
|
||||
fill="#e2e1f2"
|
||||
/>
|
||||
</Chart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceChart.displayName = 'PerformanceChart';
|
||||
|
||||
PerformanceChart.propTypes = {
|
||||
data: PropTypes.array,
|
||||
type: PropTypes.string.isRequired,
|
||||
name: PropTypes.string,
|
||||
symbol: PropTypes.string,
|
||||
requesting: PropTypes.bool,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
requesting: state.monitor.fetchMonitorLogsRequest,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(PerformanceChart);
|
||||
@@ -363,9 +363,7 @@ class CreateIncident extends Component {
|
||||
]}
|
||||
onChange={(
|
||||
event,
|
||||
newValue,
|
||||
previousValue,
|
||||
name
|
||||
newValue
|
||||
) => {
|
||||
this.substituteVariables(
|
||||
newValue,
|
||||
|
||||
@@ -186,7 +186,10 @@ class SideNav extends Component {
|
||||
/project\/([A-Za-z0-9-]+)\/([A-Za-z0-9-]+)\/error-tracker/
|
||||
) ||
|
||||
location.pathname.match(
|
||||
/project\/([A-Za-z0-9-]+)\/([A-Za-z0-9-]+)\/settings\/basic/
|
||||
/project\/([A-Za-z0-9-]+)\/([0-9]|[a-z])*\/performance-monitor/
|
||||
) ||
|
||||
location.pathname.match(
|
||||
/project\/([A-Za-z0-9-]+)\/([0-9]|[a-z])*\/settings\/basic/
|
||||
) ||
|
||||
location.pathname.match(
|
||||
/project\/([A-Za-z0-9-]+)\/([A-Za-z0-9-]+)\/settings\/advanced/
|
||||
|
||||
213
dashboard/src/components/performanceMonitor/ChartComponent.js
Normal file
213
dashboard/src/components/performanceMonitor/ChartComponent.js
Normal file
@@ -0,0 +1,213 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { setStartDate, setEndDate } from '../../actions/performanceMonitoring';
|
||||
import PerformanceChart from '../basic/performanceChart';
|
||||
import DateTimeRangePicker from '../basic/DateTimeRangePicker';
|
||||
import moment from 'moment';
|
||||
//import ShouldRender from '../../components/basic/ShouldRender';
|
||||
|
||||
export class ChartComponent extends Component {
|
||||
render() {
|
||||
const {
|
||||
heading,
|
||||
title,
|
||||
subHeading,
|
||||
startDate,
|
||||
endDate,
|
||||
setStartDate,
|
||||
setEndDate,
|
||||
} = this.props;
|
||||
const status = {
|
||||
display: 'inline-block',
|
||||
borderRadius: '2px',
|
||||
height: '8px',
|
||||
width: '8px',
|
||||
margin: '0 8px 1px 0',
|
||||
backgroundColor: 'rgb(117, 211, 128)',
|
||||
};
|
||||
return (
|
||||
<div className="Box-root Margin-bottom--12">
|
||||
<div className="bs-ContentSection Card-root Card-shadow--medium">
|
||||
<div className="Box-root">
|
||||
<div className="bs-ContentSection-content Box-root Box-divider--surface-bottom-1 Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--20 Padding-vertical--16">
|
||||
<div className="Box-root">
|
||||
<span className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
|
||||
<span>{heading}</span>
|
||||
</span>
|
||||
<p>
|
||||
<span>{subHeading}</span>
|
||||
</p>
|
||||
<div
|
||||
className="db-Trends-controls"
|
||||
style={{ marginTop: '15px' }}
|
||||
>
|
||||
<div className="db-Trends-timeControls">
|
||||
<DateTimeRangePicker
|
||||
currentDateRange={{
|
||||
startDate,
|
||||
endDate,
|
||||
}}
|
||||
handleStartDateTimeChange={val =>
|
||||
setStartDate(moment(val))
|
||||
}
|
||||
handleEndDateTimeChange={val =>
|
||||
setEndDate(moment(val))
|
||||
}
|
||||
formId={`performanceMonitoringDateTime-${heading}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="db-ListViewItem-cellContent Box-root">
|
||||
<span className="db-ListViewItem-text Text-color--cyan Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
|
||||
<div className="Box-root Margin-right--16">
|
||||
<span className="Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap Text-color--dark">
|
||||
<span>1.5 ms</span>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<div className="Box-root Flex">
|
||||
<div className="Box-root Flex-flex">
|
||||
<div className="db-RadarRulesListUserName Box-root Flex-flex Flex-alignItems--center Flex-direction--row Flex-justifyContent--flexStart">
|
||||
<div className="Box-root Flex-inlineFlex Flex-alignItems--center">
|
||||
<span className="Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap Text-color--dark">
|
||||
<span>App Server</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bs-ContentSection-content Box-root Box-background--offset Box-divider--surface-bottom-1 Padding-vertical--2"
|
||||
style={{ boxShadow: 'none' }}
|
||||
>
|
||||
<div>
|
||||
<div className="bs-Fieldset-wrapper Box-root Margin-bottom--2">
|
||||
<div
|
||||
style={{
|
||||
margin: '30px 20px 10px 20px',
|
||||
}}
|
||||
>
|
||||
<PerformanceChart
|
||||
type={`url`}
|
||||
data={[
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-05T07:40:57.765+00:00',
|
||||
value: 50,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-06T07:40:57.765+00:00',
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-07T07:40:57.765+00:00',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-08T07:40:57.765+00:00',
|
||||
value: 80,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-09T07:40:57.765+00:00',
|
||||
value: 58,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-09T08:40:57.765+00:00',
|
||||
value: 70,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-09T09:40:57.765+00:00',
|
||||
value: 25,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-09T10:40:57.765+00:00',
|
||||
value: 40,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-10T07:40:57.765+00:00',
|
||||
value: 39,
|
||||
},
|
||||
{
|
||||
createdAt:
|
||||
'2020-11-11T07:40:57.765+00:00',
|
||||
value: 68,
|
||||
},
|
||||
]}
|
||||
name={'response time'}
|
||||
symbol="ms"
|
||||
requesting={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="bs-ContentSection-content Box-root Box-divider--surface-bottom-1 Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--20">
|
||||
<div className="Box-root">
|
||||
{title.map((t, i) => (
|
||||
<Fragment key={i}>
|
||||
<span className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
|
||||
<span
|
||||
style={status}
|
||||
></span>
|
||||
<span>{t}</span>
|
||||
</span>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bs-ContentSection-footer bs-ContentSection-content Box-root Box-background--white Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--20 Padding-vertical--12">
|
||||
<div className="bs-Tail-copy">
|
||||
<div className="Box-root Flex-flex Flex-alignItems--stretch Flex-direction--row Flex-justifyContent--flexStart">
|
||||
<div style={{ height: '20px' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ChartComponent.displayName = 'ChartComponent';
|
||||
|
||||
ChartComponent.propTypes = {
|
||||
endDate: PropTypes.any,
|
||||
heading: PropTypes.any,
|
||||
setEndDate: PropTypes.any,
|
||||
setStartDate: PropTypes.any,
|
||||
startDate: PropTypes.any,
|
||||
subHeading: PropTypes.any,
|
||||
title: PropTypes.shape({
|
||||
map: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ setStartDate, setEndDate }, dispatch);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentProject: state.project.currentProject,
|
||||
startDate: state.performanceMonitoring.dates.startDate,
|
||||
endDate: state.performanceMonitoring.dates.endDate,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChartComponent);
|
||||
262
dashboard/src/components/performanceMonitor/PerformanceView.js
Normal file
262
dashboard/src/components/performanceMonitor/PerformanceView.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import React, { Component } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getMonitorLogs } from '../../actions/monitor';
|
||||
//import MonitorLogsList from '../monitor/MonitorLogsList';
|
||||
import Select from '../../components/basic/react-select-fyipe';
|
||||
//import ShouldRender from '../../components/basic/ShouldRender';
|
||||
//import DateTimeRangePicker from '../basic/DateTimeRangePicker';
|
||||
//import moment from 'moment';
|
||||
|
||||
//const endDate = moment();
|
||||
//const startDate = moment().subtract(30, 'd');
|
||||
export class PerformanceView extends Component {
|
||||
/* constructor(props) {
|
||||
super(props);
|
||||
this.props = props;
|
||||
this.state = {
|
||||
probeValue: { value: '', label: 'All Probes' },
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
page: 1,
|
||||
};
|
||||
}
|
||||
|
||||
prevClicked = (monitorId, skip, limit) => {
|
||||
const { currentProject, getMonitorLogs } = this.props;
|
||||
const incidentId = this.props.incidentId ? this.props.incidentId : null;
|
||||
const start = incidentId ? '' : this.state.startDate.clone().utc();
|
||||
const end = incidentId ? '' : this.state.endDate.clone().utc();
|
||||
getMonitorLogs(
|
||||
currentProject._id,
|
||||
monitorId,
|
||||
skip ? parseInt(skip, 10) - 10 : 10,
|
||||
limit,
|
||||
start,
|
||||
end,
|
||||
this.state.probeValue.value,
|
||||
incidentId,
|
||||
this.props.monitorType
|
||||
);
|
||||
this.setState({ page: this.state.page - 1 });
|
||||
if (window.location.href.indexOf('localhost') <= -1) {
|
||||
this.context.mixpanel.track('Previous Incident Requested', {
|
||||
projectId: currentProject._id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
nextClicked = (monitorId, skip, limit) => {
|
||||
const { currentProject, getMonitorLogs } = this.props;
|
||||
const incidentId = this.props.incidentId ? this.props.incidentId : null;
|
||||
const start = incidentId ? '' : this.state.startDate.clone().utc();
|
||||
const end = incidentId ? '' : this.state.endDate.clone().utc();
|
||||
getMonitorLogs(
|
||||
currentProject._id,
|
||||
monitorId,
|
||||
skip ? parseInt(skip, 10) + 10 : 10,
|
||||
limit,
|
||||
start,
|
||||
end,
|
||||
this.state.probeValue.value,
|
||||
incidentId,
|
||||
this.props.monitorType
|
||||
);
|
||||
this.setState({ page: this.state.page + 1 });
|
||||
if (window.location.href.indexOf('localhost') <= -1) {
|
||||
this.context.mixpanel.track('Next Incident Requested', {
|
||||
projectId: currentProject._id,
|
||||
});
|
||||
}
|
||||
};
|
||||
handleStartDateTimeChange = val => {
|
||||
const startDate = moment(val);
|
||||
this.handleDateChange(startDate, this.state.endDate);
|
||||
};
|
||||
handleEndDateTimeChange = val => {
|
||||
const endDate = moment(val);
|
||||
this.handleDateChange(this.state.startDate, endDate);
|
||||
};
|
||||
|
||||
handleDateChange = (startDate, endDate) => {
|
||||
const { currentProject, getMonitorLogs, monitorId } = this.props;
|
||||
this.setState({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
getMonitorLogs(
|
||||
currentProject._id,
|
||||
monitorId,
|
||||
0,
|
||||
10,
|
||||
startDate.clone().utc(),
|
||||
endDate.clone().utc(),
|
||||
this.state.probeValue.value,
|
||||
null,
|
||||
this.props.monitorType
|
||||
);
|
||||
};
|
||||
|
||||
handleTimeChange = (startDate, endDate) => {
|
||||
const { currentProject, getMonitorLogs, monitorId } = this.props;
|
||||
getMonitorLogs(
|
||||
currentProject._id,
|
||||
monitorId,
|
||||
0,
|
||||
10,
|
||||
startDate.clone().utc(),
|
||||
endDate.clone().utc(),
|
||||
this.state.probeValue.value,
|
||||
null,
|
||||
this.props.monitorType
|
||||
);
|
||||
};
|
||||
|
||||
handleProbeChange = data => {
|
||||
this.setState({ probeValue: data });
|
||||
const { currentProject, getMonitorLogs, monitorId } = this.props;
|
||||
getMonitorLogs(
|
||||
currentProject._id,
|
||||
monitorId,
|
||||
0,
|
||||
10,
|
||||
this.state.startDate.clone().utc(),
|
||||
this.state.endDate.clone().utc(),
|
||||
data.value,
|
||||
null,
|
||||
this.props.monitorType
|
||||
);
|
||||
};*/
|
||||
|
||||
render() {
|
||||
/* const probeOptions =
|
||||
this.props.probes && this.props.probes.length > 0
|
||||
? this.props.probes.map(p => {
|
||||
return { value: p._id, label: p.probeName };
|
||||
})
|
||||
: [];
|
||||
probeOptions.unshift({ value: '', label: 'All Probes' });*/
|
||||
return (
|
||||
<div
|
||||
className="Box-root Card-shadow--medium"
|
||||
tabIndex="0"
|
||||
onKeyDown={this.handleKeyBoard}
|
||||
>
|
||||
<div className="db-Trends-header Box-background--white Box-divider--surface-bottom-1">
|
||||
<div
|
||||
className="db-Trends-controls"
|
||||
style={{ justifyContent: 'space-evenly' }}
|
||||
>
|
||||
<div
|
||||
className="bs-Fieldset-row"
|
||||
style={{ padding: '1px' }}
|
||||
>
|
||||
<label
|
||||
className="bs-Fieldset-label"
|
||||
style={{ flex: '1' }}
|
||||
>
|
||||
Transaction Type
|
||||
</label>
|
||||
<div className="bs-Fieldset-fields">
|
||||
<Select
|
||||
name="transaction_type"
|
||||
value={{ value: '', label: 'All' }}
|
||||
placeholder="Web"
|
||||
className="db-select-in"
|
||||
id="transaction_type"
|
||||
isDisabled={false}
|
||||
style={{ height: '28px' }}
|
||||
options={[
|
||||
{
|
||||
value: 'a',
|
||||
label: 'hello',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bs-Fieldset-row"
|
||||
style={{ padding: '1px' }}
|
||||
>
|
||||
<label
|
||||
className="bs-Fieldset-label"
|
||||
style={{ flex: '1' }}
|
||||
>
|
||||
Compare With
|
||||
</label>
|
||||
<div className="bs-Fieldset-fields">
|
||||
<Select
|
||||
name="compare_with"
|
||||
value={{ value: '', label: 'All' }}
|
||||
placeholder="All"
|
||||
className="db-select-in"
|
||||
id="compare_with"
|
||||
isDisabled={false}
|
||||
style={{ height: '28px' }}
|
||||
options={[
|
||||
{
|
||||
value: 'a',
|
||||
label: 'hello',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bs-Fieldset-row"
|
||||
style={{ padding: '1px' }}
|
||||
>
|
||||
<label
|
||||
className="bs-Fieldset-label"
|
||||
style={{ flex: '1' }}
|
||||
>
|
||||
Instances
|
||||
</label>
|
||||
<div className="bs-Fieldset-fields">
|
||||
<Select
|
||||
name="instances"
|
||||
value={{ value: '', label: 'All' }}
|
||||
placeholder="All"
|
||||
className="db-select-in"
|
||||
id="instances"
|
||||
isDisabled={false}
|
||||
style={{ height: '28px' }}
|
||||
options={[
|
||||
{
|
||||
value: 'a',
|
||||
label: 'hello',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceView.displayName = 'PerformanceView';
|
||||
|
||||
PerformanceView.propTypes = {};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ getMonitorLogs }, dispatch);
|
||||
|
||||
function mapStateToProps(state, props) {
|
||||
const monitorId = props.monitorId ? props.monitorId : null;
|
||||
return {
|
||||
monitorLogs: monitorId ? state.monitor.monitorLogs[monitorId] : {},
|
||||
probes: state.probe.probes.data,
|
||||
currentProject: state.project.currentProject,
|
||||
};
|
||||
}
|
||||
|
||||
PerformanceView.contextTypes = {
|
||||
mixpanel: PropTypes.object,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PerformanceView);
|
||||
@@ -0,0 +1,49 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
//import PropTypes from 'prop-types';
|
||||
import { getMonitorLogs } from '../../actions/monitor';
|
||||
import ChartComponent from './ChartComponent';
|
||||
|
||||
export class WebTransactionsChart extends Component {
|
||||
render() {
|
||||
const { heading, title, subHeading } = this.props;
|
||||
return (
|
||||
<div
|
||||
className="Box-root Card-shadow--medium"
|
||||
tabIndex="0"
|
||||
onKeyDown={this.handleKeyBoard}
|
||||
style={{ marginTop: '10px' }}
|
||||
>
|
||||
<ChartComponent
|
||||
heading={heading}
|
||||
title={title}
|
||||
subHeading={subHeading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WebTransactionsChart.displayName = 'WebTransactionsChart';
|
||||
|
||||
WebTransactionsChart.propTypes = {
|
||||
heading: PropTypes.any,
|
||||
subHeading: PropTypes.any,
|
||||
title: PropTypes.any,
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ getMonitorLogs }, dispatch);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentProject: state.project.currentProject,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(WebTransactionsChart);
|
||||
173
dashboard/src/pages/PerformanceMonitoring.js
Normal file
173
dashboard/src/pages/PerformanceMonitoring.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import React, { Component } from 'react';
|
||||
import BreadCrumbItem from '../components/breadCrumb/BreadCrumbItem';
|
||||
import Dashboard from '../components/Dashboard';
|
||||
import getParentRoute from '../utils/getParentRoute';
|
||||
import Fade from 'react-reveal/Fade';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import PerformanceView from '../components/performanceMonitor/PerformanceView';
|
||||
import WebTransactionsChart from '../components/performanceMonitor/WebTransactionsChart';
|
||||
//import ShouldRender from '../../components/basic/ShouldRender';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
|
||||
class PerformanceMonitoring extends Component {
|
||||
state = {
|
||||
tabIndex: 0,
|
||||
};
|
||||
tabSelected = index => {
|
||||
const tabSlider = document.getElementById('tab-slider');
|
||||
tabSlider.style.transform = `translate(calc(${tabSlider.offsetWidth}px*${index}), 0px)`;
|
||||
this.setState({
|
||||
tabIndex: index,
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
location: { pathname },
|
||||
component,
|
||||
} = this.props;
|
||||
const componentName = component ? component.name : '';
|
||||
return (
|
||||
<Dashboard>
|
||||
<Fade>
|
||||
<BreadCrumbItem
|
||||
route={getParentRoute(pathname)}
|
||||
name={componentName}
|
||||
/>
|
||||
<BreadCrumbItem
|
||||
route={pathname}
|
||||
name="Performance Monitoring"
|
||||
/>
|
||||
<Tabs
|
||||
selectedTabClassName={'custom-tab-selected'}
|
||||
onSelect={tabIndex => this.tabSelected(tabIndex)}
|
||||
selectedIndex={this.state.tabIndex}
|
||||
>
|
||||
<div className="Flex-flex Flex-direction--columnReverse">
|
||||
<TabList
|
||||
id="customTabList"
|
||||
className={'custom-tab-list'}
|
||||
>
|
||||
<Tab className={'custom-tab custom-tab-2'}>
|
||||
Charts
|
||||
</Tab>
|
||||
<Tab className={'custom-tab custom-tab-2'}>
|
||||
Data
|
||||
</Tab>
|
||||
<div
|
||||
id="tab-slider"
|
||||
className="custom-tab-2"
|
||||
></div>
|
||||
</TabList>
|
||||
</div>
|
||||
<TabPanel>
|
||||
<Fade>
|
||||
<div className="Box-root Margin-bottom--12">
|
||||
<div>
|
||||
<div>
|
||||
<PerformanceView />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<WebTransactionsChart
|
||||
heading="Web Transactions Time"
|
||||
title={[
|
||||
'Node.js',
|
||||
'Response time',
|
||||
]}
|
||||
subHeading="shows graph of web transactions initiated through http requests"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<WebTransactionsChart
|
||||
heading="Throughput"
|
||||
title={['Web.throughput']}
|
||||
subHeading="shows graph of number of web transactions per minute"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<WebTransactionsChart
|
||||
heading="Error rate"
|
||||
title={[
|
||||
'Web errors',
|
||||
'All errors',
|
||||
]}
|
||||
subHeading="shows graph of errors occuring per minute"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<WebTransactionsChart
|
||||
heading="Apdex Score"
|
||||
title={[
|
||||
'App server',
|
||||
'End user',
|
||||
]}
|
||||
subHeading="shows graph of satisfied requests against total requests"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fade>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<Fade>
|
||||
<div className="Box-root Margin-bottom--12">
|
||||
<div>
|
||||
<div>
|
||||
<WebTransactionsChart
|
||||
heading="Web Transactions Time"
|
||||
title={['Node.js']}
|
||||
subHeading="shows graph of web transactions initiated through http requests"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fade>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Fade>
|
||||
</Dashboard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceMonitoring.displayName = 'PerformanceMonitoring';
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return bindActionCreators({}, dispatch);
|
||||
};
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { componentId } = ownProps.match.params;
|
||||
const currentProject = state.project.currentProject;
|
||||
|
||||
let component;
|
||||
state.component.componentList.components.forEach(item => {
|
||||
item.components.forEach(c => {
|
||||
if (String(c._id) === String(componentId)) {
|
||||
component = c;
|
||||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
currentProject,
|
||||
component,
|
||||
componentId,
|
||||
};
|
||||
};
|
||||
PerformanceMonitoring.propTypes = {
|
||||
component: PropTypes.shape({
|
||||
name: PropTypes.any,
|
||||
}),
|
||||
location: PropTypes.any,
|
||||
};
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PerformanceMonitoring);
|
||||
@@ -57,6 +57,7 @@ import ComponentSettingsAdvanced from './ComponentSettingsAdvanced';
|
||||
import CallRouting from './CallRouting';
|
||||
import DomainSettings from './DomainSettings';
|
||||
import Groups from './Group';
|
||||
import PerformanceMonitoring from './PerformanceMonitoring';
|
||||
|
||||
export default {
|
||||
ChangePassword,
|
||||
@@ -107,4 +108,5 @@ export default {
|
||||
CallRouting,
|
||||
DomainSettings,
|
||||
Groups,
|
||||
PerformanceMonitoring,
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ import customField from './customField';
|
||||
import monitorCustomField from './monitorCustomField';
|
||||
import callRouting from './callRouting';
|
||||
import groups from './groups';
|
||||
import performanceMonitoring from './performanceMonitoring';
|
||||
const appReducer = combineReducers({
|
||||
routing: routerReducer,
|
||||
form: formReducer,
|
||||
@@ -94,6 +95,7 @@ const appReducer = combineReducers({
|
||||
customField,
|
||||
monitorCustomField,
|
||||
callRouting,
|
||||
performanceMonitoring,
|
||||
});
|
||||
|
||||
export default (state, action) => {
|
||||
|
||||
31
dashboard/src/reducers/performanceMonitoring.js
Normal file
31
dashboard/src/reducers/performanceMonitoring.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import moment from 'moment';
|
||||
|
||||
const initialState = {
|
||||
dates: {
|
||||
startDate: moment().subtract(30, 'd'),
|
||||
endDate: moment(),
|
||||
},
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'SET_START_DATE':
|
||||
return Object.assign({}, state, {
|
||||
dates: {
|
||||
...state.dates,
|
||||
startDate: action.payload,
|
||||
},
|
||||
});
|
||||
|
||||
case 'SET_END_DATE':
|
||||
return Object.assign({}, state, {
|
||||
dates: {
|
||||
...state.dates,
|
||||
endDate: action.payload,
|
||||
},
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@@ -52,6 +52,7 @@ const {
|
||||
CallRouting,
|
||||
DomainSettings,
|
||||
Groups,
|
||||
PerformanceMonitoring,
|
||||
} = pages;
|
||||
|
||||
export const groups = [
|
||||
@@ -186,6 +187,18 @@ export const groups = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Performance Monitoring',
|
||||
path:
|
||||
'/dashboard/project/:slug/:componentId/performance-monitor',
|
||||
icon: 'errorTracking',
|
||||
visible: true,
|
||||
exact: true,
|
||||
component: PerformanceMonitoring,
|
||||
index: 8,
|
||||
shortcut: 'f+p',
|
||||
subRoutes: [],
|
||||
},
|
||||
{
|
||||
title: 'Security',
|
||||
path:
|
||||
@@ -245,7 +258,7 @@ export const groups = [
|
||||
shortcut: 'r+n',
|
||||
},
|
||||
],
|
||||
index: 7,
|
||||
index: 9,
|
||||
},
|
||||
{
|
||||
title: 'Component Settings',
|
||||
@@ -255,7 +268,7 @@ export const groups = [
|
||||
exact: true,
|
||||
component: ComponentSettings,
|
||||
shortcut: 'f+s',
|
||||
index: 8,
|
||||
index: 10,
|
||||
subRoutes: [
|
||||
{
|
||||
title: 'Basic',
|
||||
|
||||
31171
js-sdk/package-lock.json
generated
31171
js-sdk/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,5 +4,6 @@ import ErrorTracker from './tracker.js';
|
||||
// we need to build the server-monitor project into the build folder then point to it there
|
||||
// This way we won't worry about whether we are using module/commonjs syntax
|
||||
import ServerMonitor from '../build/server-monitor/lib/api';
|
||||
import PerformanceMonitor from './performanceMonitor.js';
|
||||
|
||||
export default { Logger, ErrorTracker, ServerMonitor };
|
||||
export default { Logger, ErrorTracker, ServerMonitor ,PerformanceMonitor};
|
||||
|
||||
89
js-sdk/src/performanceMonitor.js
Normal file
89
js-sdk/src/performanceMonitor.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/* eslint-disable no-console */
|
||||
/*eslint-disable no-unused-vars*/
|
||||
import Module from 'module';
|
||||
import FyipeTimelineManager from './timelineManager';
|
||||
import Util from './util';
|
||||
import Http from 'http';
|
||||
import Https from 'https';
|
||||
import mongooseListener from './utils/mongoose';
|
||||
|
||||
class PerformanceMonitor {
|
||||
#BASE_URL = 'http://localhost:3002/api'; // TODO proper base url config
|
||||
#timelineObj;
|
||||
#currentEventId;
|
||||
#utilObj;
|
||||
#isWindow;
|
||||
#options;
|
||||
constructor(eventId, isWindow, options) {
|
||||
this.#options = options;
|
||||
this.#isWindow = isWindow;
|
||||
this.#timelineObj = new FyipeTimelineManager(options);
|
||||
this.#utilObj = new Util();
|
||||
this.#currentEventId = eventId;
|
||||
|
||||
if (!this.#isWindow) {
|
||||
this._setUpHttpsListener();
|
||||
this._setUpDataBaseListener();
|
||||
this._setUpIncomingListener();
|
||||
}
|
||||
}
|
||||
_setUpHttpsListener() {
|
||||
override(Http);
|
||||
override(Https);
|
||||
const _this = this;
|
||||
function override(module) {
|
||||
const original = module.request;
|
||||
function wrapper(outgoing) {
|
||||
// Store a call to the original in req
|
||||
const req = original.apply(this, arguments);
|
||||
const emit = req.emit;
|
||||
const startHrTime = process.hrtime();
|
||||
req.emit = function(eventName, response) {
|
||||
switch (eventName) {
|
||||
case 'response': {
|
||||
response.on('end', () => {
|
||||
const elapsedHrTime = process.hrtime(
|
||||
startHrTime
|
||||
);
|
||||
const elapsedTimeInMs =
|
||||
elapsedHrTime[0] * 1000 +
|
||||
elapsedHrTime[1] / 1e6;
|
||||
console.log('outgoing', elapsedTimeInMs);
|
||||
});
|
||||
}
|
||||
}
|
||||
return emit.apply(this, arguments);
|
||||
};
|
||||
// return the original call
|
||||
return req;
|
||||
}
|
||||
module.request = wrapper;
|
||||
}
|
||||
}
|
||||
_logHttpRequestEvent(content, type) {
|
||||
const timelineObj = {
|
||||
category: type, // HTTP
|
||||
data: {
|
||||
content,
|
||||
},
|
||||
type,
|
||||
eventId: this.#currentEventId,
|
||||
};
|
||||
// add timeline to the stack
|
||||
this.#timelineObj.addToTimeline(timelineObj);
|
||||
}
|
||||
_setUpDataBaseListener() {
|
||||
const load = Module._load;
|
||||
Module._load = function(request, parent) {
|
||||
const res = load.apply(this, arguments);
|
||||
if (request === 'mongoose') {
|
||||
return mongooseListener(res);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
_setUpIncomingListener() {
|
||||
return require('./utils/incomingListener');
|
||||
}
|
||||
}
|
||||
export default PerformanceMonitor;
|
||||
56
js-sdk/src/utils/incomingListener.js
Normal file
56
js-sdk/src/utils/incomingListener.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
/* eslint-disable no-console */
|
||||
/*eslint-disable no-unused-vars*/
|
||||
const Http = require('http');
|
||||
//const { performance, PerformanceObserver } = require('perf_hooks');
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
//const obs = new PerformanceObserver((list, observer) => {
|
||||
// console.log(list.getEntries()[0]);
|
||||
// performance.clearMarks();
|
||||
//observer.disconnect();
|
||||
//});
|
||||
//obs.observe({ entryTypes: ['measure'] });
|
||||
|
||||
const context = new Map();
|
||||
|
||||
const emit = Http.Server.prototype.emit;
|
||||
Http.Server.prototype.emit = function(type) {
|
||||
if (type === 'request') {
|
||||
const [req, res] = [arguments[1], arguments[2]];
|
||||
|
||||
req.apm = {};
|
||||
req.apm.uuid = uuidv4();
|
||||
const startHrTime = process.hrtime();
|
||||
//performance.mark(`start-${req.apm.uuid}`);
|
||||
res.on('finish', () => {
|
||||
const elapsedHrTime = process.hrtime(startHrTime);
|
||||
const elapsedTimeInMs =
|
||||
elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6;
|
||||
console.log('incoming', elapsedTimeInMs);
|
||||
//performance.mark(`end-${req.apm.uuid}`);
|
||||
// performance.measure(
|
||||
// `request-${req.apm.uuid}`,
|
||||
// `start-${req.apm.uuid}`,
|
||||
// `end-${req.apm.uuid}`
|
||||
// );
|
||||
});
|
||||
}
|
||||
|
||||
return emit.apply(this, arguments);
|
||||
};
|
||||
|
||||
const init = function(asyncId, type, triggerAsyncId) {
|
||||
if (context.has(triggerAsyncId)) {
|
||||
context.set(asyncId, context.get(triggerAsyncId));
|
||||
}
|
||||
};
|
||||
|
||||
const destroy = function(asyncId) {
|
||||
if (context.has(asyncId)) {
|
||||
context.delete(asyncId);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { init, destroy };
|
||||
59
js-sdk/src/utils/mongoose.js
Normal file
59
js-sdk/src/utils/mongoose.js
Normal file
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
/* eslint-disable no-console */
|
||||
/*eslint-disable no-unused-vars*/
|
||||
//const { performance } = require('perf_hooks');
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const wrapAsync = function(orig, name) {
|
||||
return async function() {
|
||||
//const uuid = uuidv4();
|
||||
|
||||
name = name || `mongoose.${this.op}`; // mongose Query.exec specific
|
||||
const startHrTime = process.hrtime();
|
||||
try {
|
||||
//performance.mark(`start-${uuid}`);
|
||||
const res = await orig.apply(this, arguments);
|
||||
//performance.mark(`end-${uuid}`);
|
||||
//performance.measure(
|
||||
// `${name}-${uuid}`,
|
||||
// `start-${uuid}`,
|
||||
// `end-${uuid}`
|
||||
//);
|
||||
const elapsedHrTime = process.hrtime(startHrTime);
|
||||
const elapsedTimeInMs =
|
||||
elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6;
|
||||
console.log(name, elapsedTimeInMs);
|
||||
return res;
|
||||
} catch (err) {
|
||||
const elapsedHrTime = process.hrtime(startHrTime);
|
||||
const elapsedTimeInMs =
|
||||
elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6;
|
||||
console.log(name, elapsedTimeInMs);
|
||||
//performance.mark(`end-${uuid}`);
|
||||
//performance.measure(
|
||||
// `${name}-${uuid}`,
|
||||
// `start-${uuid}`,
|
||||
// `end-${uuid}`
|
||||
// );
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default function(mod) {
|
||||
const proto = Object.getPrototypeOf(mod);
|
||||
const exec = proto.Query.prototype.exec;
|
||||
|
||||
proto.Query.prototype.exec = wrapAsync(exec);
|
||||
|
||||
const Model = proto.Model;
|
||||
|
||||
const remove = Model.prototype.remove;
|
||||
Model.prototype.remove = wrapAsync(remove, 'mongoose.remove');
|
||||
|
||||
const save = Model.prototype.save;
|
||||
Model.prototype.save = wrapAsync(save, 'mongoose.save');
|
||||
|
||||
return mod;
|
||||
}
|
||||
@@ -33,6 +33,7 @@ const serverBuild = {
|
||||
fs: 'empty',
|
||||
child_process: 'empty',
|
||||
net: 'empty',
|
||||
module: 'empty',
|
||||
},
|
||||
};
|
||||
const webBuild = {
|
||||
|
||||
14792
probe/package-lock.json
generated
14792
probe/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user