mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-08-01 08:50:27 +00:00
TRA-4602_timeline bar charts to traffic statistics (#1159)
* pie chart for protocols and methods by requests and volume * protocols legend * timeline bar chart component created * timeline can view requests and volume * sorting the bra charts by timestemp * disable view of <1% pieces in pie * space added to the end of the file * package.json update * cr fixes * remove spave * remove unnecessary react fragment Co-authored-by: Liraz Yehezkel <lirazy@up9.com>
This commit is contained in:
parent
9a40895e9c
commit
4d64dd4b04
@ -0,0 +1,4 @@
|
||||
.barChartContainer
|
||||
width: 100%
|
||||
display: flex
|
||||
justify-content: center
|
@ -0,0 +1,83 @@
|
||||
import styles from "./TimelineBarChart.module.sass";
|
||||
import { StatsMode } from "../TrafficStatsModal"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
Legend
|
||||
} from "recharts";
|
||||
import { Utils } from "../../../../helpers/Utils";
|
||||
|
||||
interface TimelineBarChartProps {
|
||||
timeLineBarChartMode: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarChartMode, data }) => {
|
||||
const [protocolStats, setProtocolStats] = useState([]);
|
||||
const [protocolsNamesAndColors, setProtocolsNamesAndColors] = useState([]);
|
||||
|
||||
const padTo2Digits = useCallback((num) => {
|
||||
return String(num).padStart(2, '0');
|
||||
}, [])
|
||||
|
||||
const getHoursAndMinutes = useCallback((protocolTimeKey) => {
|
||||
const time = new Date(protocolTimeKey)
|
||||
const hoursAndMinutes = padTo2Digits(time.getHours()) + ':' + padTo2Digits(time.getMinutes());
|
||||
return hoursAndMinutes;
|
||||
}, [padTo2Digits])
|
||||
|
||||
const creatUniqueObjArray = useCallback((objArray) => {
|
||||
return [
|
||||
...new Map(objArray.map((item) => [item["name"], item])).values(),
|
||||
];
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
const protocolsBarsData = [];
|
||||
const prtcNames = [];
|
||||
data.map(protocolObj => {
|
||||
let obj: { [k: string]: any } = {};
|
||||
obj.timestamp = getHoursAndMinutes(protocolObj.timestamp);
|
||||
protocolObj.protocols.forEach(protocol => {
|
||||
obj[`${protocol.name}`] = protocol[StatsMode[timeLineBarChartMode]];
|
||||
prtcNames.push({ name: protocol.name, color: protocol.color });
|
||||
})
|
||||
protocolsBarsData.push(obj);
|
||||
})
|
||||
const uniqueObjArray = creatUniqueObjArray(prtcNames);
|
||||
protocolsBarsData.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1);
|
||||
setProtocolStats(protocolsBarsData);
|
||||
setProtocolsNamesAndColors(uniqueObjArray);
|
||||
}, [data, timeLineBarChartMode, setProtocolStats, setProtocolsNamesAndColors, creatUniqueObjArray, getHoursAndMinutes])
|
||||
|
||||
const bars = useMemo(() => protocolsNamesAndColors.map((protocolToDIsplay) => {
|
||||
return <Bar key={protocolToDIsplay.name} dataKey={protocolToDIsplay.name} stackId="a" fill={protocolToDIsplay.color} />
|
||||
}), [protocolsNamesAndColors])
|
||||
|
||||
return (
|
||||
<div className={styles.barChartContainer}>
|
||||
<BarChart
|
||||
width={730}
|
||||
height={250}
|
||||
data={protocolStats}
|
||||
margin={{
|
||||
top: 20,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5
|
||||
}}
|
||||
>
|
||||
<XAxis dataKey="timestamp" />
|
||||
<YAxis tickFormatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value} />
|
||||
<Tooltip formatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value + " Requests"} />
|
||||
<Legend />
|
||||
{bars}
|
||||
</BarChart>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -2,11 +2,7 @@ import React, {useEffect, useMemo, useState} from "react";
|
||||
import styles from "./TrafficPieChart.module.sass";
|
||||
import {Cell, Legend, Pie, PieChart, Tooltip} from "recharts";
|
||||
import {Utils} from "../../../../helpers/Utils";
|
||||
|
||||
enum PieChartMode {
|
||||
REQUESTS = "entriesCount",
|
||||
VOLUME = "volumeSizeBytes"
|
||||
}
|
||||
import {StatsMode as PieChartMode} from "../TrafficStatsModal"
|
||||
|
||||
const COLORS = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000'];
|
||||
|
||||
@ -24,6 +20,8 @@ const renderCustomizedLabel = ({
|
||||
const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
||||
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
||||
|
||||
if (Number((percent * 100).toFixed(0)) <= 1) return;
|
||||
|
||||
return (
|
||||
<text
|
||||
x={x}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Backdrop, Box, Fade, Modal} from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Backdrop, Box, Fade, Modal } from "@mui/material";
|
||||
import styles from "./TrafficStatsModal.module.sass";
|
||||
import closeIcon from "assets/close.svg";
|
||||
import {TrafficPieChart} from "./TrafficPieChart/TrafficPieChart";
|
||||
import { TrafficPieChart } from "./TrafficPieChart/TrafficPieChart";
|
||||
import { TimelineBarChart } from "./TimelineBarChart/TimelineBarChart";
|
||||
import spinnerImg from "assets/spinner.svg";
|
||||
|
||||
const modalStyle = {
|
||||
@ -19,7 +20,7 @@ const modalStyle = {
|
||||
color: '#000',
|
||||
};
|
||||
|
||||
enum StatsMode {
|
||||
export enum StatsMode {
|
||||
REQUESTS = "entriesCount",
|
||||
VOLUME = "volumeSizeBytes"
|
||||
}
|
||||
@ -27,23 +28,27 @@ enum StatsMode {
|
||||
interface TrafficStatsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
getTrafficStatsDataApi: () => Promise<any>
|
||||
getPieStatsDataApi: () => Promise<any>
|
||||
getTimelineStatsDataApi: () => Promise<any>
|
||||
}
|
||||
|
||||
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getTrafficStatsDataApi }) => {
|
||||
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getPieStatsDataApi, getTimelineStatsDataApi }) => {
|
||||
|
||||
const modes = Object.keys(StatsMode).filter(x => !(parseInt(x) >= 0));
|
||||
const [statsMode, setStatsMode] = useState(modes[0]);
|
||||
const [statsData, setStatsData] = useState(null);
|
||||
const [pieStatsData, setPieStatsData] = useState(null);
|
||||
const [timelineStatsData, setTimelineStatsData] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(isOpen && getTrafficStatsDataApi) {
|
||||
if (isOpen && getPieStatsDataApi) {
|
||||
(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data = await getTrafficStatsDataApi();
|
||||
setStatsData(data);
|
||||
const pieData = await getPieStatsDataApi();
|
||||
setPieStatsData(pieData);
|
||||
const timelineData = await getTimelineStatsDataApi();
|
||||
setTimelineStatsData(timelineData);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
@ -51,7 +56,7 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
}
|
||||
})()
|
||||
}
|
||||
}, [isOpen, getTrafficStatsDataApi])
|
||||
}, [isOpen, getPieStatsDataApi, getTimelineStatsDataApi, setPieStatsData, setTimelineStatsData])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -65,19 +70,25 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
<Fade in={isOpen}>
|
||||
<Box sx={modalStyle}>
|
||||
<div className={styles.closeIcon}>
|
||||
<img src={closeIcon} alt="close" onClick={() => onClose()} style={{ cursor: "pointer", userSelect: "none" }}/>
|
||||
<img src={closeIcon} alt="close" onClick={() => onClose()} style={{ cursor: "pointer", userSelect: "none" }} />
|
||||
</div>
|
||||
<div className={styles.title}>Traffic Statistics</div>
|
||||
<div className={styles.mainContainer}>
|
||||
<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)}>
|
||||
{modes.map(mode => <option value={mode}>{mode}</option>)}
|
||||
{modes.map(mode => <option key={mode} value={mode}>{mode}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
{isLoading ? <div style={{textAlign: "center", marginTop: 20}}>
|
||||
<div>
|
||||
{isLoading ? <div style={{ textAlign: "center", marginTop: 20 }}>
|
||||
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
||||
</div> : <TrafficPieChart pieChartMode={statsMode} data={statsData}/>}
|
||||
</div> :
|
||||
<div>
|
||||
<TrafficPieChart pieChartMode={statsMode} data={pieStatsData} />
|
||||
<TimelineBarChart timeLineBarChartMode={statsMode} data={timelineStatsData} />
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Fade>
|
||||
|
@ -36,7 +36,7 @@ const App = () => {
|
||||
openModal={oasModalOpen}
|
||||
handleCloseModal={() => setOasModalOpen(false)}
|
||||
/>}
|
||||
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getTrafficStatsDataApi={api.getStats}/>
|
||||
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getPieStatsDataApi={api.getPieStats} getTimelineStatsDataApi={api.getTimelineStats}/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
|
@ -111,8 +111,13 @@ export default class Api {
|
||||
});
|
||||
}
|
||||
|
||||
getStats = async () => {
|
||||
getPieStats = async () => {
|
||||
const response = await client.get("/status/accumulative");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getTimelineStats = async () => {
|
||||
const response = await client.get("/status/accumulativeTiming");
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user