mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-08-28 13:25:13 +00:00
time picker added
This commit is contained in:
parent
c010d336bb
commit
20d3d54b4b
2759
ui-common/package-lock.json
generated
2759
ui-common/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -26,14 +26,15 @@
|
|||||||
"@craco/craco": "^6.4.3",
|
"@craco/craco": "^6.4.3",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/node": "^12.20.54",
|
"@types/node": "^12.20.54",
|
||||||
"sass": "^1.52.3",
|
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"recoil": "^0.7.2"
|
"recoil": "^0.7.2",
|
||||||
|
"sass": "^1.52.3"
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
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()) {
|
||||||
|
throw new Error('Unable to parse start string');
|
||||||
|
}
|
||||||
|
const endMoment = dateMath.parse(inputEnd, { roundUp: true });
|
||||||
|
if (!endMoment || !endMoment.isValid()) {
|
||||||
|
throw new 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
|
||||||
|
isLoading={isLoading}
|
||||||
|
start={start}
|
||||||
|
end={end}
|
||||||
|
onTimeChange={onTimeChange}
|
||||||
|
onRefresh={onRefresh}
|
||||||
|
isPaused={isPaused}
|
||||||
|
refreshInterval={refreshInterval}
|
||||||
|
onRefreshChange={onRefreshChange}
|
||||||
|
recentlyUsedRanges={recentlyUsedRanges}
|
||||||
|
/>
|
||||||
|
<EuiSpacer />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -19,6 +19,7 @@
|
|||||||
.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,12 @@
|
|||||||
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";
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -15,7 +14,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 +25,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 +36,16 @@ 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 now = new Date().getTime();
|
||||||
|
const halfAnHourAgo = new Date().getTime() - (30 * 60 * 1000);
|
||||||
|
|
||||||
|
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,11 +59,11 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
|||||||
}, [isOpen, getTrafficStatsDataApi, setPieStatsData, setTimelineStatsData])
|
}, [isOpen, getTrafficStatsDataApi, setPieStatsData, setTimelineStatsData])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getTrafficStats();
|
getTrafficStats(halfAnHourAgo,now);
|
||||||
}, [getTrafficStats])
|
}, [getTrafficStats])
|
||||||
|
|
||||||
const refreshStats = debounce(() => {
|
const refreshStats = debounce((newStartTime, newEndTime) => {
|
||||||
getTrafficStats();
|
getTrafficStats(newStartTime,newEndTime);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -82,18 +82,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)}>
|
||||||
|
2280
ui/package-lock.json
generated
2280
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,6 +36,7 @@
|
|||||||
"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.7.2",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
@ -49,7 +51,7 @@
|
|||||||
"recoil": "^0.7.2"
|
"recoil": "^0.7.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prestart": "../devops/ui-common-pack.sh $PWD",
|
"_prestart": "../devops/ui-common-pack.sh $PWD",
|
||||||
"start": "craco start",
|
"start": "craco start",
|
||||||
"start-dev": "./node_modules/.bin/env-cmd -f .env.dev.basic craco start",
|
"start-dev": "./node_modules/.bin/env-cmd -f .env.dev.basic craco start",
|
||||||
"build": "./node_modules/.bin/env-cmd -f .env.basic craco build",
|
"build": "./node_modules/.bin/env-cmd -f .env.basic craco build",
|
||||||
|
@ -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
|
||||||
|
@ -10,6 +10,8 @@ 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';
|
||||||
|
import { EuiProvider } from '@elastic/eui';
|
||||||
|
import '@elastic/eui/dist/eui_theme_light.css';
|
||||||
|
|
||||||
const api = Api.getInstance()
|
const api = Api.getInstance()
|
||||||
|
|
||||||
@ -20,6 +22,7 @@ const App = () => {
|
|||||||
const [trafficStatsModalOpen, setTrafficStatsModalOpen] = useRecoilState(trafficStatsModalOpenAtom);
|
const [trafficStatsModalOpen, setTrafficStatsModalOpen] = useRecoilState(trafficStatsModalOpenAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<EuiProvider>
|
||||||
<StyledEngineProvider injectFirst>
|
<StyledEngineProvider injectFirst>
|
||||||
<ThemeProvider theme={createTheme(({}))}>
|
<ThemeProvider theme={createTheme(({}))}>
|
||||||
<div className="mizuApp">
|
<div className="mizuApp">
|
||||||
@ -40,6 +43,7 @@ const App = () => {
|
|||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StyledEngineProvider>
|
</StyledEngineProvider>
|
||||||
|
</EuiProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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