mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-25 15:54:43 +00:00
Add time range to stats (#1199)
This commit is contained in:
parent
15f7b889e2
commit
e9719cba3a
@ -1,7 +1,10 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
core "k8s.io/api/core/v1"
|
core "k8s.io/api/core/v1"
|
||||||
|
|
||||||
@ -80,7 +83,24 @@ func GetGeneralStats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetTrafficStats(c *gin.Context) {
|
func GetTrafficStats(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, providers.GetTrafficStats())
|
startTime, endTime, err := getStartEndTime(c)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, providers.GetTrafficStats(startTime, endTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStartEndTime(c *gin.Context) (time.Time, time.Time, error) {
|
||||||
|
startTimeValue, err := strconv.Atoi(c.Query("startTimeMs"))
|
||||||
|
if err != nil {
|
||||||
|
return time.UnixMilli(0), time.UnixMilli(0), fmt.Errorf("invalid start time: %v", err)
|
||||||
|
}
|
||||||
|
endTimeValue, err := strconv.Atoi(c.Query("endTimeMs"))
|
||||||
|
if err != nil {
|
||||||
|
return time.UnixMilli(0), time.UnixMilli(0), fmt.Errorf("invalid end time: %v", err)
|
||||||
|
}
|
||||||
|
return time.UnixMilli(int64(startTimeValue)), time.UnixMilli(int64(endTimeValue)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCurrentResolvingInformation(c *gin.Context) {
|
func GetCurrentResolvingInformation(c *gin.Context) {
|
||||||
|
@ -85,8 +85,8 @@ func InitProtocolToColor(protocolMap map[string]*api.Protocol) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTrafficStats() *TrafficStatsResponse {
|
func GetTrafficStats(startTime time.Time, endTime time.Time) *TrafficStatsResponse {
|
||||||
bucketsStatsCopy := getBucketStatsCopy()
|
bucketsStatsCopy := getFilteredBucketStatsCopy(startTime, endTime)
|
||||||
|
|
||||||
return &TrafficStatsResponse{
|
return &TrafficStatsResponse{
|
||||||
Protocols: getAvailableProtocols(bucketsStatsCopy),
|
Protocols: getAvailableProtocols(bucketsStatsCopy),
|
||||||
@ -262,7 +262,7 @@ func convertAccumulativeStatsDictToArray(methodsPerProtocolAggregated map[string
|
|||||||
return protocolsData
|
return protocolsData
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBucketStatsCopy() BucketStats {
|
func getFilteredBucketStatsCopy(startTime time.Time, endTime time.Time) BucketStats {
|
||||||
bucketStatsCopy := BucketStats{}
|
bucketStatsCopy := BucketStats{}
|
||||||
bucketStatsLocker.Lock()
|
bucketStatsLocker.Lock()
|
||||||
if err := copier.Copy(&bucketStatsCopy, bucketsStats); err != nil {
|
if err := copier.Copy(&bucketStatsCopy, bucketsStats); err != nil {
|
||||||
@ -270,7 +270,18 @@ func getBucketStatsCopy() BucketStats {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
bucketStatsLocker.Unlock()
|
bucketStatsLocker.Unlock()
|
||||||
return bucketStatsCopy
|
|
||||||
|
filteredBucketStatsCopy := BucketStats{}
|
||||||
|
interval := InternalBucketThreshold
|
||||||
|
|
||||||
|
for _, bucket := range bucketStatsCopy {
|
||||||
|
if (bucket.BucketTime.After(startTime.Add(-1*interval/2).Round(interval)) && bucket.BucketTime.Before(endTime.Add(-1*interval/2).Round(interval))) ||
|
||||||
|
bucket.BucketTime.Equal(startTime.Add(-1*interval/2).Round(interval)) ||
|
||||||
|
bucket.BucketTime.Equal(endTime.Add(-1*interval/2).Round(interval)) {
|
||||||
|
filteredBucketStatsCopy = append(filteredBucketStatsCopy, bucket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredBucketStatsCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAggregatedResultTiming(stats BucketStats, interval time.Duration) map[time.Time]map[string]map[string]*AccumulativeStatsCounter {
|
func getAggregatedResultTiming(stats BucketStats, interval time.Duration) map[time.Time]map[string]map[string]*AccumulativeStatsCounter {
|
||||||
|
2426
ui-common/package-lock.json
generated
2426
ui-common/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -34,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "^6.4.3",
|
"@craco/craco": "^6.4.3",
|
||||||
|
"@elastic/eui": "^60.2.0",
|
||||||
"@emotion/react": "^11.9.0",
|
"@emotion/react": "^11.9.0",
|
||||||
"@emotion/styled": "^11.8.1",
|
"@emotion/styled": "^11.8.1",
|
||||||
"@mui/icons-material": "^5.8.2",
|
"@mui/icons-material": "^5.8.2",
|
||||||
@ -85,7 +86,7 @@
|
|||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-sass": "^1.2.12",
|
"rollup-plugin-sass": "^1.2.12",
|
||||||
"rollup-plugin-scss": "^3.0.0",
|
"rollup-plugin-scss": "^3.0.0",
|
||||||
"typescript": "^4.7.2"
|
"typescript": "^4.5.3"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
import React, { useState, Fragment } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EuiSuperDatePicker,
|
||||||
|
EuiSpacer,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import dateMath from '@elastic/datemath';
|
||||||
|
|
||||||
|
interface TimeRangePickerProps {
|
||||||
|
refreshStats: (startTime, endTime) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimeRangePicker: React.FC<TimeRangePickerProps> = ({ refreshStats }) => {
|
||||||
|
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [start, setStart] = useState('now-30m');
|
||||||
|
const [end, setEnd] = useState('now');
|
||||||
|
const [isPaused, setIsPaused] = useState(true);
|
||||||
|
const [refreshInterval, setRefreshInterval] = useState();
|
||||||
|
|
||||||
|
const dateConvertor = (inputStart, inputEnd) => {
|
||||||
|
const startMoment = dateMath.parse(inputStart);
|
||||||
|
if (!startMoment || !startMoment.isValid()) {
|
||||||
|
console.error("Unable to parse start string");
|
||||||
|
}
|
||||||
|
const endMoment = dateMath.parse(inputEnd, { roundUp: true });
|
||||||
|
if (!endMoment || !endMoment.isValid()) {
|
||||||
|
console.error("Unable to parse end string");
|
||||||
|
}
|
||||||
|
return { startMoment: startMoment.format("x"), endMoment: endMoment.format("x") }
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTimeChange = ({ start, end }) => {
|
||||||
|
const recentlyUsedRange = recentlyUsedRanges.filter(recentlyUsedRange => {
|
||||||
|
const isDuplicate =
|
||||||
|
recentlyUsedRange.start === start && recentlyUsedRange.end === end;
|
||||||
|
return !isDuplicate;
|
||||||
|
});
|
||||||
|
recentlyUsedRange.unshift({ start, end });
|
||||||
|
setStart(start);
|
||||||
|
setEnd(end);
|
||||||
|
setRecentlyUsedRanges(
|
||||||
|
recentlyUsedRange.length > 10
|
||||||
|
? recentlyUsedRange.slice(0, 9)
|
||||||
|
: recentlyUsedRange
|
||||||
|
);
|
||||||
|
const { startMoment, endMoment } = dateConvertor(start, end)
|
||||||
|
refreshStats(startMoment, endMoment)
|
||||||
|
setIsLoading(true);
|
||||||
|
startLoading();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRefresh = ({ start, end, refreshInterval }) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
}).then(() => {
|
||||||
|
const { startMoment, endMoment } = dateConvertor(start, end)
|
||||||
|
refreshStats(startMoment, endMoment)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const startLoading = () => {
|
||||||
|
setTimeout(stopLoading, 1000);
|
||||||
|
};
|
||||||
|
const stopLoading = () => {
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRefreshChange = ({ isPaused, refreshInterval }) => {
|
||||||
|
setIsPaused(isPaused);
|
||||||
|
setRefreshInterval(refreshInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiSuperDatePicker
|
||||||
|
width='auto'
|
||||||
|
isLoading={isLoading}
|
||||||
|
start={start}
|
||||||
|
end={end}
|
||||||
|
onTimeChange={onTimeChange}
|
||||||
|
onRefresh={onRefresh}
|
||||||
|
isPaused={isPaused}
|
||||||
|
refreshInterval={refreshInterval}
|
||||||
|
onRefreshChange={onRefreshChange}
|
||||||
|
recentlyUsedRanges={recentlyUsedRanges}
|
||||||
|
/>
|
||||||
|
<EuiSpacer />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -13,12 +13,12 @@
|
|||||||
top: 20px
|
top: 20px
|
||||||
|
|
||||||
.mainContainer
|
.mainContainer
|
||||||
padding: 30px
|
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
.selectContainer
|
.selectContainer
|
||||||
display: flex
|
display: flex
|
||||||
justify-content: space-evenly
|
justify-content: space-evenly
|
||||||
|
align-items: center
|
||||||
margin-bottom: 4%
|
margin-bottom: 4%
|
||||||
|
|
||||||
.select
|
.select
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Backdrop, Box, Button, debounce, Fade, Modal } from "@mui/material";
|
import { Backdrop, Box, debounce, Fade, Modal } from "@mui/material";
|
||||||
import styles from "./TrafficStatsModal.module.sass";
|
import styles from "./TrafficStatsModal.module.sass";
|
||||||
import closeIcon from "assets/close.svg";
|
import closeIcon from "assets/close.svg";
|
||||||
import { TrafficPieChart } from "./TrafficPieChart/TrafficPieChart";
|
import { TrafficPieChart } from "./TrafficPieChart/TrafficPieChart";
|
||||||
import { TimelineBarChart } from "./TimelineBarChart/TimelineBarChart";
|
import { TimelineBarChart } from "./TimelineBarChart/TimelineBarChart";
|
||||||
import refreshIcon from "assets/refresh.svg";
|
|
||||||
import { useCommonStyles } from "../../../helpers/commonStyle";
|
|
||||||
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
||||||
import { ALL_PROTOCOLS, StatsMode } from "./consts";
|
import { ALL_PROTOCOLS, StatsMode } from "./consts";
|
||||||
|
import { TimeRangePicker } from "./TimelineBarChart/TimeRangePicker/TimeTangePicker";
|
||||||
|
import { EuiProvider } from '@elastic/eui';
|
||||||
|
import '@elastic/eui/dist/eui_theme_light.css';
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -15,7 +16,7 @@ const modalStyle = {
|
|||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, 0%)',
|
transform: 'translate(-50%, 0%)',
|
||||||
width: '60vw',
|
width: '60vw',
|
||||||
height: '82vh',
|
height: '90vh',
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
@ -26,11 +27,10 @@ const modalStyle = {
|
|||||||
interface TrafficStatsModalProps {
|
interface TrafficStatsModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
getTrafficStatsDataApi: () => Promise<any>
|
getTrafficStatsDataApi: (start?, end?) => Promise<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getTrafficStatsDataApi }) => {
|
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getTrafficStatsDataApi }) => {
|
||||||
|
|
||||||
const modes = Object.keys(StatsMode).filter(x => !(parseInt(x) >= 0));
|
const modes = Object.keys(StatsMode).filter(x => !(parseInt(x) >= 0));
|
||||||
const [statsMode, setStatsMode] = useState(modes[0]);
|
const [statsMode, setStatsMode] = useState(modes[0]);
|
||||||
const [selectedProtocol, setSelectedProtocol] = useState(ALL_PROTOCOLS);
|
const [selectedProtocol, setSelectedProtocol] = useState(ALL_PROTOCOLS);
|
||||||
@ -38,14 +38,13 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
|||||||
const [timelineStatsData, setTimelineStatsData] = useState(null);
|
const [timelineStatsData, setTimelineStatsData] = useState(null);
|
||||||
const [protocols, setProtocols] = useState([])
|
const [protocols, setProtocols] = useState([])
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const commonClasses = useCommonStyles();
|
|
||||||
|
|
||||||
const getTrafficStats = useCallback(async () => {
|
const getTrafficStats = useCallback(async (startTime, endTime) => {
|
||||||
if (isOpen && getTrafficStatsDataApi) {
|
if (isOpen && getTrafficStatsDataApi) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const statsData = await getTrafficStatsDataApi();
|
const statsData = await getTrafficStatsDataApi(startTime, endTime);
|
||||||
setPieStatsData(statsData.pie);
|
setPieStatsData(statsData.pie);
|
||||||
setTimelineStatsData(statsData.timeline);
|
setTimelineStatsData(statsData.timeline);
|
||||||
setProtocols(statsData.protocols)
|
setProtocols(statsData.protocols)
|
||||||
@ -59,14 +58,17 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
|||||||
}, [isOpen, getTrafficStatsDataApi, setPieStatsData, setTimelineStatsData])
|
}, [isOpen, getTrafficStatsDataApi, setPieStatsData, setTimelineStatsData])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getTrafficStats();
|
const now = new Date().getTime();
|
||||||
|
const halfAnHourAgo = now - (30 * 60 * 1000);
|
||||||
|
getTrafficStats(halfAnHourAgo, now);
|
||||||
}, [getTrafficStats])
|
}, [getTrafficStats])
|
||||||
|
|
||||||
const refreshStats = debounce(() => {
|
const refreshStats = debounce((newStartTime, newEndTime) => {
|
||||||
getTrafficStats();
|
getTrafficStats(newStartTime, newEndTime);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<EuiProvider>
|
||||||
<Modal
|
<Modal
|
||||||
aria-labelledby="transition-modal-title"
|
aria-labelledby="transition-modal-title"
|
||||||
aria-describedby="transition-modal-description"
|
aria-describedby="transition-modal-description"
|
||||||
@ -82,18 +84,12 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.headlineContainer}>
|
<div className={styles.headlineContainer}>
|
||||||
<div className={styles.title}>Traffic Statistics</div>
|
<div className={styles.title}>Traffic Statistics</div>
|
||||||
<Button style={{ marginLeft: "2%", textTransform: 'unset' }}
|
|
||||||
startIcon={<img src={refreshIcon} className="custom" alt="refresh"></img>}
|
|
||||||
size="medium"
|
|
||||||
variant="contained"
|
|
||||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
|
||||||
onClick={refreshStats}
|
|
||||||
>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.mainContainer}>
|
<div className={styles.mainContainer}>
|
||||||
<div className={styles.selectContainer}>
|
<div className={styles.selectContainer}>
|
||||||
|
<div>
|
||||||
|
<TimeRangePicker refreshStats={refreshStats} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ marginRight: 15 }}>Breakdown By</span>
|
<span style={{ marginRight: 15 }}>Breakdown By</span>
|
||||||
<select className={styles.select} value={statsMode} onChange={(e) => setStatsMode(e.target.value)}>
|
<select className={styles.select} value={statsMode} onChange={(e) => setStatsMode(e.target.value)}>
|
||||||
@ -119,5 +115,6 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
|||||||
</Box>
|
</Box>
|
||||||
</Fade>
|
</Fade>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
</EuiProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
69916
ui/package-lock.json
generated
69916
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "^6.4.3",
|
"@craco/craco": "^6.4.3",
|
||||||
|
"@elastic/datemath": "^5.0.3",
|
||||||
|
"@elastic/eui": "^60.2.0",
|
||||||
"@emotion/react": "^11.9.0",
|
"@emotion/react": "^11.9.0",
|
||||||
"@emotion/styled": "^11.8.1",
|
"@emotion/styled": "^11.8.1",
|
||||||
"@mui/material": "^5.8.2",
|
"@mui/material": "^5.8.2",
|
||||||
@ -23,7 +25,6 @@
|
|||||||
"mobx": "^6.6.0",
|
"mobx": "^6.6.0",
|
||||||
"moment": "^2.29.3",
|
"moment": "^2.29.3",
|
||||||
"node-fetch": "^3.2.4",
|
"node-fetch": "^3.2.4",
|
||||||
"sass": "^1.52.3",
|
|
||||||
"numeral": "^2.0.6",
|
"numeral": "^2.0.6",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
@ -35,8 +36,9 @@
|
|||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"react-toastify": "^8.2.0",
|
"react-toastify": "^8.2.0",
|
||||||
"redoc": "^2.0.0-rc.71",
|
"redoc": "^2.0.0-rc.71",
|
||||||
|
"sass": "^1.52.3",
|
||||||
"styled-components": "^5.3.5",
|
"styled-components": "^5.3.5",
|
||||||
"typescript": "^4.7.2",
|
"typescript": "^4.5.3",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"xml-formatter": "^2.6.1"
|
"xml-formatter": "^2.6.1"
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Web site created using create-react-app"
|
||||||
/>
|
/>
|
||||||
|
<meta name="eui-style-insert">
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
@ -8,7 +8,7 @@ import oasModalOpenAtom from './recoil/oasModalOpen/atom';
|
|||||||
import trafficStatsModalOpenAtom from "./recoil/trafficStatsModalOpen";
|
import trafficStatsModalOpenAtom from "./recoil/trafficStatsModalOpen";
|
||||||
import { OasModal } from '@up9/mizu-common';
|
import { OasModal } from '@up9/mizu-common';
|
||||||
import Api from './helpers/api';
|
import Api from './helpers/api';
|
||||||
import {ThemeProvider, StyledEngineProvider, createTheme} from '@mui/material';
|
import { ThemeProvider, StyledEngineProvider, createTheme } from '@mui/material';
|
||||||
import { TrafficStatsModal } from '@up9/mizu-common';
|
import { TrafficStatsModal } from '@up9/mizu-common';
|
||||||
|
|
||||||
const api = Api.getInstance()
|
const api = Api.getInstance()
|
||||||
@ -36,7 +36,7 @@ const App = () => {
|
|||||||
openModal={oasModalOpen}
|
openModal={oasModalOpen}
|
||||||
handleCloseModal={() => setOasModalOpen(false)}
|
handleCloseModal={() => setOasModalOpen(false)}
|
||||||
/>}
|
/>}
|
||||||
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getTrafficStatsDataApi={api.getTrafficStats}/>
|
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getTrafficStatsDataApi={api.getTrafficStats} />
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StyledEngineProvider>
|
</StyledEngineProvider>
|
||||||
|
@ -116,8 +116,8 @@ export default class Api {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrafficStats = async () => {
|
getTrafficStats = async (startTimeMs, endTimeMs) => {
|
||||||
const response = await client.get("/status/trafficStats");
|
const response = await client.get("/status/trafficStats", {params: {startTimeMs, endTimeMs}});
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user