mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-08-11 13:23:03 +00:00
TRA_4623 - methods view on timeline stats with coordination to the pie stats (#1175)
* Add select protocol → when selected, the view will be on commands of that exact protocol * CR fixes * added const instead of free string * remove redundant sass file
This commit is contained in:
parent
3a9236a381
commit
3b0b311e1e
@ -1,5 +1,5 @@
|
|||||||
import styles from "./TimelineBarChart.module.sass";
|
import styles from "./TimelineBarChart.module.sass";
|
||||||
import { StatsMode } from "../TrafficStatsModal"
|
import { ALL_PROTOCOLS, StatsMode } from "../TrafficStatsModal"
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
BarChart,
|
BarChart,
|
||||||
@ -7,31 +7,33 @@ import {
|
|||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend
|
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { Utils } from "../../../../helpers/Utils";
|
import { Utils } from "../../../../helpers/Utils";
|
||||||
|
|
||||||
interface TimelineBarChartProps {
|
interface TimelineBarChartProps {
|
||||||
timeLineBarChartMode: string;
|
timeLineBarChartMode: string;
|
||||||
data: any;
|
data: any;
|
||||||
|
selectedProtocol: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarChartMode, data }) => {
|
export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarChartMode, data, selectedProtocol }) => {
|
||||||
const [protocolStats, setProtocolStats] = useState([]);
|
const [protocolStats, setProtocolStats] = useState([]);
|
||||||
const [protocolsNamesAndColors, setProtocolsNamesAndColors] = useState([]);
|
const [protocolsNamesAndColors, setProtocolsNamesAndColors] = useState([]);
|
||||||
|
const [commandStats, setCommandStats] = useState(null);
|
||||||
|
const [commandNames, setcommandNames] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
const protocolsBarsData = [];
|
const protocolsBarsData = [];
|
||||||
const prtcNames = [];
|
const prtcNames = [];
|
||||||
data.forEach(protocolObj => {
|
data.forEach(protocolObj => {
|
||||||
let obj: { [k: string]: any } = {};
|
let newProtocolbj: { [k: string]: any } = {};
|
||||||
obj.timestamp = Utils.getHoursAndMinutes(protocolObj.timestamp);
|
newProtocolbj.timestamp = Utils.getHoursAndMinutes(protocolObj.timestamp);
|
||||||
protocolObj.protocols.forEach(protocol => {
|
protocolObj.protocols.forEach(protocol => {
|
||||||
obj[`${protocol.name}`] = protocol[StatsMode[timeLineBarChartMode]];
|
newProtocolbj[`${protocol.name}`] = protocol[StatsMode[timeLineBarChartMode]];
|
||||||
prtcNames.push({ name: protocol.name, color: protocol.color });
|
prtcNames.push({ name: protocol.name, color: protocol.color });
|
||||||
})
|
})
|
||||||
protocolsBarsData.push(obj);
|
protocolsBarsData.push(newProtocolbj);
|
||||||
})
|
})
|
||||||
const uniqueObjArray = Utils.creatUniqueObjArrayByProp(prtcNames, "name")
|
const uniqueObjArray = Utils.creatUniqueObjArrayByProp(prtcNames, "name")
|
||||||
protocolsBarsData.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1);
|
protocolsBarsData.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1);
|
||||||
@ -39,16 +41,39 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
|||||||
setProtocolsNamesAndColors(uniqueObjArray);
|
setProtocolsNamesAndColors(uniqueObjArray);
|
||||||
}, [data, timeLineBarChartMode])
|
}, [data, timeLineBarChartMode])
|
||||||
|
|
||||||
const bars = useMemo(() => protocolsNamesAndColors.map((protocolToDIsplay) => {
|
useEffect(() => {
|
||||||
return <Bar key={protocolToDIsplay.name} dataKey={protocolToDIsplay.name} stackId="a" fill={protocolToDIsplay.color} />
|
if (selectedProtocol === ALL_PROTOCOLS) {
|
||||||
}), [protocolsNamesAndColors])
|
setCommandStats(null);
|
||||||
|
setcommandNames(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const commandsNames = [];
|
||||||
|
const protocolsCommands = [];
|
||||||
|
data.forEach(protocolObj => {
|
||||||
|
let newCommandlbj: { [k: string]: any } = {};
|
||||||
|
newCommandlbj.timestamp = Utils.getHoursAndMinutes(protocolObj.timestamp);
|
||||||
|
protocolObj.protocols.find(protocol => protocol.name === selectedProtocol)?.methods.forEach(command => {
|
||||||
|
newCommandlbj[`${command.name}`] = command[StatsMode[timeLineBarChartMode]]
|
||||||
|
if (commandsNames.indexOf(command.name) === -1)
|
||||||
|
commandsNames.push(command.name);
|
||||||
|
})
|
||||||
|
protocolsCommands.push(newCommandlbj);
|
||||||
|
})
|
||||||
|
protocolsCommands.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1);
|
||||||
|
setcommandNames(commandsNames);
|
||||||
|
setCommandStats(protocolsCommands);
|
||||||
|
}, [data, timeLineBarChartMode, selectedProtocol])
|
||||||
|
|
||||||
|
const bars = useMemo(() => (commandNames || protocolsNamesAndColors).map((entry) => {
|
||||||
|
return <Bar key={entry.name || entry} dataKey={entry.name || entry} stackId="a" fill={entry.color || Utils.stringToColor(entry)} />
|
||||||
|
}), [protocolsNamesAndColors, commandNames])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.barChartContainer}>
|
<div className={styles.barChartContainer}>
|
||||||
{protocolStats.length > 0 && <BarChart
|
{protocolStats.length > 0 && <BarChart
|
||||||
width={730}
|
width={730}
|
||||||
height={250}
|
height={250}
|
||||||
data={protocolStats}
|
data={commandStats || protocolStats}
|
||||||
margin={{
|
margin={{
|
||||||
top: 20,
|
top: 20,
|
||||||
right: 30,
|
right: 30,
|
||||||
@ -59,7 +84,6 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
|||||||
<XAxis dataKey="timestamp" />
|
<XAxis dataKey="timestamp" />
|
||||||
<YAxis tickFormatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value} />
|
<YAxis tickFormatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value} />
|
||||||
<Tooltip formatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value + " Requests"} />
|
<Tooltip formatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value + " Requests"} />
|
||||||
<Legend />
|
|
||||||
{bars}
|
{bars}
|
||||||
</BarChart>}
|
</BarChart>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
.breadCrumbsContainer
|
|
||||||
margin-top: 15px
|
|
||||||
height: 15px
|
|
||||||
|
|
||||||
.breadCrumbs
|
|
||||||
color: #494677
|
|
||||||
text-align: left
|
|
||||||
|
|
||||||
.clickableTag
|
|
||||||
margin-right: 5px
|
|
||||||
border-bottom: 1px black solid
|
|
||||||
cursor: pointer
|
|
||||||
|
|
||||||
.nonClickableTag
|
|
||||||
margin-left: 5px
|
|
||||||
font-weight: 600
|
|
@ -1,10 +1,7 @@
|
|||||||
import React, {useEffect, useMemo, useState} from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import styles from "./TrafficPieChart.module.sass";
|
import { Cell, Legend, Pie, PieChart, Tooltip } from "recharts";
|
||||||
import {Cell, Legend, Pie, PieChart, Tooltip} from "recharts";
|
import { Utils } from "../../../../helpers/Utils";
|
||||||
import {Utils} from "../../../../helpers/Utils";
|
import { ALL_PROTOCOLS, StatsMode as PieChartMode } from "../TrafficStatsModal"
|
||||||
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'];
|
|
||||||
|
|
||||||
const RADIAN = Math.PI / 180;
|
const RADIAN = Math.PI / 180;
|
||||||
const renderCustomizedLabel = ({
|
const renderCustomizedLabel = ({
|
||||||
@ -15,7 +12,7 @@ const renderCustomizedLabel = ({
|
|||||||
outerRadius,
|
outerRadius,
|
||||||
percent,
|
percent,
|
||||||
index
|
index
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
|
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
|
||||||
const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
||||||
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
||||||
@ -38,13 +35,13 @@ const renderCustomizedLabel = ({
|
|||||||
interface TrafficPieChartProps {
|
interface TrafficPieChartProps {
|
||||||
pieChartMode: string;
|
pieChartMode: string;
|
||||||
data: any;
|
data: any;
|
||||||
|
selectedProtocol: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({pieChartMode , data}) => {
|
export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({ pieChartMode, data, selectedProtocol }) => {
|
||||||
|
|
||||||
const [protocolsStats, setProtocolsStats] = useState([]);
|
const [protocolsStats, setProtocolsStats] = useState([]);
|
||||||
const [commandStats, setCommandStats] = useState(null);
|
const [commandStats, setCommandStats] = useState(null);
|
||||||
const [selectedProtocol, setSelectedProtocol] = useState(null as string);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
@ -59,11 +56,11 @@ export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({pieChartMode ,
|
|||||||
}, [data, pieChartMode])
|
}, [data, pieChartMode])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedProtocol) {
|
if (selectedProtocol === ALL_PROTOCOLS) {
|
||||||
setCommandStats(null);
|
setCommandStats(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const commandsPieData = data.find(protocol => protocol.name === selectedProtocol).methods.map(command => {
|
const commandsPieData = data.find(protocol => protocol.name === selectedProtocol)?.methods.map(command => {
|
||||||
return {
|
return {
|
||||||
name: command.name,
|
name: command.name,
|
||||||
value: command[PieChartMode[pieChartMode]]
|
value: command[PieChartMode[pieChartMode]]
|
||||||
@ -75,18 +72,18 @@ export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({pieChartMode ,
|
|||||||
const pieLegend = useMemo(() => {
|
const pieLegend = useMemo(() => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
let legend;
|
let legend;
|
||||||
if (!selectedProtocol) {
|
if (selectedProtocol === ALL_PROTOCOLS) {
|
||||||
legend = data.map(protocol => <div style={{marginBottom: 5, display: "flex"}}>
|
legend = data.map(protocol => <div style={{ marginBottom: 5, display: "flex" }}>
|
||||||
<div style={{height: 15, width: 30, background: protocol?.color}}/>
|
<div style={{ height: 15, width: 30, background: protocol?.color }} />
|
||||||
<span style={{marginLeft: 5}}>
|
<span style={{ marginLeft: 5 }}>
|
||||||
{protocol.name}
|
{protocol.name}
|
||||||
</span>
|
</span>
|
||||||
</div>)
|
</div>)
|
||||||
} else {
|
} else {
|
||||||
legend = data.find(protocol => protocol.name === selectedProtocol).methods.map((method, index) => <div
|
legend = data.find(protocol => protocol.name === selectedProtocol)?.methods.map((method) => <div
|
||||||
style={{marginBottom: 5, display: "flex"}}>
|
style={{ marginBottom: 5, display: "flex" }}>
|
||||||
<div style={{height: 15, width: 30, background: COLORS[index % COLORS.length]}}/>
|
<div style={{ height: 15, width: 30, background: Utils.stringToColor(method.name)}} />
|
||||||
<span style={{marginLeft: 5}}>
|
<span style={{ marginLeft: 5 }}>
|
||||||
{method.name}
|
{method.name}
|
||||||
</span>
|
</span>
|
||||||
</div>)
|
</div>)
|
||||||
@ -96,15 +93,7 @@ export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({pieChartMode ,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.breadCrumbsContainer}>
|
{protocolsStats?.length > 0 && <div style={{ width: "100%", display: "flex", justifyContent: "center" }}>
|
||||||
{selectedProtocol && <div className={styles.breadCrumbs}>
|
|
||||||
<span className={styles.clickableTag} onClick={() => setSelectedProtocol(null)}>protocols</span>
|
|
||||||
<span>/</span>
|
|
||||||
<span className={styles.nonClickableTag}>{selectedProtocol}</span>
|
|
||||||
</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{protocolsStats?.length > 0 && <div style={{width: "100%", display: "flex", justifyContent: "center"}}>
|
|
||||||
<PieChart width={300} height={300}>
|
<PieChart width={300} height={300}>
|
||||||
<Pie
|
<Pie
|
||||||
data={commandStats || protocolsStats}
|
data={commandStats || protocolsStats}
|
||||||
@ -114,14 +103,13 @@ export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({pieChartMode ,
|
|||||||
labelLine={false}
|
labelLine={false}
|
||||||
label={renderCustomizedLabel}
|
label={renderCustomizedLabel}
|
||||||
outerRadius={125}
|
outerRadius={125}
|
||||||
fill="#8884d8"
|
fill="#8884d8">
|
||||||
onClick={(section) => !commandStats && setSelectedProtocol(section.name)}>
|
|
||||||
{(commandStats || protocolsStats).map((entry, index) => (
|
{(commandStats || protocolsStats).map((entry, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color || COLORS[index % COLORS.length]}/>)
|
<Cell key={`cell-${index}`} fill={entry.color || Utils.stringToColor(entry.name)} />)
|
||||||
)}
|
)}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Legend wrapperStyle={{position: "absolute", width: "auto", height: "auto", right: -150, top: 0}} content={pieLegend}/>
|
<Legend wrapperStyle={{ position: "absolute", width: "auto", height: "auto", right: -150, top: 0 }} content={pieLegend} />
|
||||||
<Tooltip formatter={(value) => pieChartMode === "VOLUME" ? Utils.humanFileSize(value) : value + " Requests"}/>
|
<Tooltip formatter={(value) => pieChartMode === "VOLUME" ? Utils.humanFileSize(value) : value + " Requests"} />
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
.headlineContainer
|
||||||
|
display: flex
|
||||||
|
|
||||||
.title
|
.title
|
||||||
color: #494677
|
color: #494677
|
||||||
font-family: Source Sans Pro,Lucida Grande,Tahoma,sans-serif
|
font-family: Source Sans Pro,Lucida Grande,Tahoma,sans-serif
|
||||||
@ -13,6 +16,11 @@
|
|||||||
padding: 30px
|
padding: 30px
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
|
.selectContainer
|
||||||
|
display: flex
|
||||||
|
justify-content: space-evenly
|
||||||
|
margin-bottom: 4%
|
||||||
|
|
||||||
.select
|
.select
|
||||||
border: none
|
border: none
|
||||||
border-bottom: 1px black solid
|
border-bottom: 1px black solid
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Backdrop, Box, Fade, Modal } from "@mui/material";
|
import { Backdrop, Box, Button, 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 spinnerImg from "assets/spinner.svg";
|
import spinnerImg from "assets/spinner.svg";
|
||||||
|
import refreshIcon from "assets/refresh.svg";
|
||||||
|
import { useCommonStyles } from "../../../helpers/commonStyle";
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -32,15 +34,20 @@ interface TrafficStatsModalProps {
|
|||||||
getTimelineStatsDataApi: () => Promise<any>
|
getTimelineStatsDataApi: () => Promise<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PROTOCOLS = ["ALL PROTOCOLS","gRPC", "REDIS", "HTTP", "GQL", "AMQP", "KFAKA"];
|
||||||
|
export const ALL_PROTOCOLS = PROTOCOLS[0];
|
||||||
|
|
||||||
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getPieStatsDataApi, getTimelineStatsDataApi }) => {
|
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getPieStatsDataApi, getTimelineStatsDataApi }) => {
|
||||||
|
|
||||||
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 [pieStatsData, setPieStatsData] = useState(null);
|
const [pieStatsData, setPieStatsData] = useState(null);
|
||||||
const [timelineStatsData, setTimelineStatsData] = useState(null);
|
const [timelineStatsData, setTimelineStatsData] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const commonClasses = useCommonStyles();
|
||||||
|
|
||||||
useEffect(() => {
|
const getTrafficStats = useCallback(async () => {
|
||||||
if (isOpen && getPieStatsDataApi) {
|
if (isOpen && getPieStatsDataApi) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@ -58,6 +65,14 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
|||||||
}
|
}
|
||||||
}, [isOpen, getPieStatsDataApi, getTimelineStatsDataApi, setPieStatsData, setTimelineStatsData])
|
}, [isOpen, getPieStatsDataApi, getTimelineStatsDataApi, setPieStatsData, setTimelineStatsData])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getTrafficStats();
|
||||||
|
}, [getTrafficStats])
|
||||||
|
|
||||||
|
const refreshStats = debounce(() => {
|
||||||
|
getTrafficStats();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
aria-labelledby="transition-modal-title"
|
aria-labelledby="transition-modal-title"
|
||||||
@ -72,21 +87,40 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
|||||||
<div className={styles.closeIcon}>
|
<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>
|
||||||
|
<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 className={styles.mainContainer}>
|
<div className={styles.mainContainer}>
|
||||||
|
<div className={styles.selectContainer}>
|
||||||
<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)}>
|
||||||
{modes.map(mode => <option key={mode} value={mode}>{mode}</option>)}
|
{modes.map(mode => <option key={mode} value={mode}>{mode}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span style={{ marginRight: 15 }}>Protocol</span>
|
||||||
|
<select className={styles.select} value={selectedProtocol} onChange={(e) => setSelectedProtocol(e.target.value)}>
|
||||||
|
{PROTOCOLS.map(protocol => <option key={protocol} value={protocol}>{protocol}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{isLoading ? <div style={{ textAlign: "center", marginTop: 20 }}>
|
{isLoading ? <div style={{ textAlign: "center", marginTop: 20 }}>
|
||||||
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
||||||
</div> :
|
</div> :
|
||||||
<div>
|
<div>
|
||||||
<TrafficPieChart pieChartMode={statsMode} data={pieStatsData} />
|
<TrafficPieChart pieChartMode={statsMode} data={pieStatsData} selectedProtocol={selectedProtocol}/>
|
||||||
<TimelineBarChart timeLineBarChartMode={statsMode} data={timelineStatsData} />
|
<TimelineBarChart timeLineBarChartMode={statsMode} data={timelineStatsData} selectedProtocol={selectedProtocol}/>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.8337 11.9167H7.69308L7.69416 11.907C7.83561 11.2143 8.11247 10.5564 8.50883 9.97105C9.09865 9.10202 9.92598 8.42097 10.8922 8.00913C11.2193 7.87046 11.5606 7.7643 11.9083 7.69388C12.6297 7.54762 13.3731 7.54762 14.0945 7.69388C15.1312 7.90631 16.0825 8.41908 16.8299 9.1683L18.3639 7.63863C17.6725 6.94707 16.8546 6.39501 15.9546 6.01255C15.4956 5.81823 15.0184 5.67016 14.53 5.57055C13.5223 5.36581 12.4838 5.36581 11.4761 5.57055C10.9873 5.67057 10.5098 5.819 10.0504 6.01363C8.69682 6.58791 7.53808 7.54123 6.71374 8.7588C6.15895 9.5798 5.77099 10.5019 5.57191 11.4725C5.54158 11.6188 5.52533 11.7683 5.50366 11.9167H2.16699L6.50033 16.25L10.8337 11.9167ZM15.167 14.0834H18.3076L18.3065 14.092C18.0234 15.4806 17.205 16.7019 16.0282 17.4915C15.443 17.8882 14.7851 18.1651 14.0923 18.3062C13.3713 18.4525 12.6283 18.4525 11.9072 18.3062C11.2146 18.1648 10.5567 17.8879 9.97133 17.4915C9.68383 17.2971 9.41541 17.0758 9.16966 16.8307L7.63783 18.3625C8.32954 19.0539 9.14791 19.6056 10.0482 19.9875C10.5076 20.1825 10.9875 20.331 11.4728 20.4295C12.4801 20.6344 13.5184 20.6344 14.5257 20.4295C16.4676 20.0265 18.1757 18.8819 19.2869 17.2391C19.8412 16.4187 20.2288 15.4974 20.4277 14.5275C20.4569 14.3813 20.4742 14.2318 20.4959 14.0834H23.8337L19.5003 9.75005L15.167 14.0834Z" fill="#205CF5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -42,4 +42,17 @@ export class Utils {
|
|||||||
return Array.from(map);
|
return Array.from(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static stringToColor = (str) => {
|
||||||
|
let colors = ["#e51c23", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#5677fc", "#03a9f4", "#00bcd4", "#009688", "#259b24", "#8bc34a", "#afb42b", "#ff9800", "#ff5722", "#795548", "#607d8b"]
|
||||||
|
|
||||||
|
let hash = 0;
|
||||||
|
if (str.length === 0) return hash;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
hash = ((hash % colors.length) + colors.length) % colors.length;
|
||||||
|
return colors[hash];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user