mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-15 18:39:58 +00:00
Compare commits
4 Commits
35.0-dev19
...
36.0-dev0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3a6b3a9d4 | ||
|
|
5f73c2d50a | ||
|
|
d6944d467c | ||
|
|
57078517a4 |
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/op/go-logging"
|
||||
basenine "github.com/up9inc/basenine/client/go"
|
||||
"github.com/up9inc/mizu/agent/pkg/api"
|
||||
"github.com/up9inc/mizu/agent/pkg/providers"
|
||||
"github.com/up9inc/mizu/agent/pkg/utils"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
@@ -81,6 +82,7 @@ func LoadExtensions() {
|
||||
})
|
||||
|
||||
api.InitMaps(ExtensionsMap, ProtocolsMap)
|
||||
providers.InitProtocolToColor(ProtocolsMap)
|
||||
}
|
||||
|
||||
func ConfigureBasenineServer(host string, port string, dbSize int64, logLevel logging.Level, insertionFilter string) {
|
||||
|
||||
@@ -79,13 +79,8 @@ func GetGeneralStats(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.GetGeneralStats())
|
||||
}
|
||||
|
||||
func GetAccumulativeStats(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.GetAccumulativeStats())
|
||||
}
|
||||
|
||||
func GetAccumulativeStatsTiming(c *gin.Context) {
|
||||
// for now hardcoded 10 bars of 5 minutes interval
|
||||
c.JSON(http.StatusOK, providers.GetAccumulativeStatsTiming(300, 10))
|
||||
func GetTrafficStats(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.GetTrafficStats())
|
||||
}
|
||||
|
||||
func GetCurrentResolvingInformation(c *gin.Context) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package providers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -26,7 +27,6 @@ type TimeFrameStatsValue struct {
|
||||
|
||||
type ProtocolStats struct {
|
||||
MethodsStats map[string]*SizeAndEntriesCount `json:"methods"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
type SizeAndEntriesCount struct {
|
||||
@@ -51,46 +51,103 @@ type AccumulativeStatsProtocolTime struct {
|
||||
Time int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type TrafficStatsResponse struct {
|
||||
PieStats []*AccumulativeStatsProtocol `json:"pie"`
|
||||
TimelineStats []*AccumulativeStatsProtocolTime `json:"timeline"`
|
||||
}
|
||||
|
||||
var (
|
||||
generalStats = GeneralStats{}
|
||||
bucketsStats = BucketStats{}
|
||||
bucketStatsLocker = sync.Mutex{}
|
||||
protocolToColor = map[string]string{}
|
||||
)
|
||||
|
||||
const (
|
||||
InternalBucketThreshold = time.Minute * 1
|
||||
MaxNumberOfBars = 30
|
||||
)
|
||||
|
||||
func ResetGeneralStats() {
|
||||
generalStats = GeneralStats{}
|
||||
}
|
||||
|
||||
func GetGeneralStats() GeneralStats {
|
||||
return generalStats
|
||||
func GetGeneralStats() *GeneralStats {
|
||||
return &generalStats
|
||||
}
|
||||
|
||||
func GetAccumulativeStats() []*AccumulativeStatsProtocol {
|
||||
bucketStatsCopy := getBucketStatsCopy()
|
||||
if len(bucketStatsCopy) == 0 {
|
||||
func InitProtocolToColor(protocolMap map[string]*api.Protocol) {
|
||||
for item, value := range protocolMap {
|
||||
protocolToColor[strings.Split(item, "/")[2]] = value.BackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
func GetTrafficStats() *TrafficStatsResponse {
|
||||
bucketsStatsCopy := getBucketStatsCopy()
|
||||
interval := calculateInterval(bucketsStatsCopy[0].BucketTime.Unix(), bucketsStatsCopy[len(bucketsStatsCopy)-1].BucketTime.Unix()) // in seconds
|
||||
|
||||
return &TrafficStatsResponse{
|
||||
PieStats: getAccumulativeStats(bucketsStatsCopy),
|
||||
TimelineStats: getAccumulativeStatsTiming(bucketsStatsCopy, interval),
|
||||
}
|
||||
}
|
||||
|
||||
func calculateInterval(firstTimestamp int64, lastTimestamp int64) time.Duration {
|
||||
validDurations := []time.Duration{
|
||||
time.Minute,
|
||||
time.Minute * 2,
|
||||
time.Minute * 3,
|
||||
time.Minute * 5,
|
||||
time.Minute * 10,
|
||||
time.Minute * 15,
|
||||
time.Minute * 20,
|
||||
time.Minute * 30,
|
||||
time.Minute * 45,
|
||||
time.Minute * 60,
|
||||
time.Minute * 75,
|
||||
time.Minute * 90, // 1.5 minutes
|
||||
time.Minute * 120, // 2 hours
|
||||
time.Minute * 150, // 2.5 hours
|
||||
time.Minute * 180, // 3 hours
|
||||
time.Minute * 240, // 4 hours
|
||||
time.Minute * 300, // 5 hours
|
||||
time.Minute * 360, // 6 hours
|
||||
time.Minute * 420, // 7 hours
|
||||
time.Minute * 480, // 8 hours
|
||||
time.Minute * 540, // 9 hours
|
||||
time.Minute * 600, // 10 hours
|
||||
time.Minute * 660, // 11 hours
|
||||
time.Minute * 720, // 12 hours
|
||||
time.Minute * 1440, // 24 hours
|
||||
}
|
||||
duration := time.Duration(lastTimestamp-firstTimestamp) * time.Second / time.Duration(MaxNumberOfBars)
|
||||
for _, validDuration := range validDurations {
|
||||
if validDuration-duration >= 0 {
|
||||
return validDuration
|
||||
}
|
||||
}
|
||||
return duration.Round(validDurations[len(validDurations)-1])
|
||||
|
||||
}
|
||||
|
||||
func getAccumulativeStats(stats BucketStats) []*AccumulativeStatsProtocol {
|
||||
if len(stats) == 0 {
|
||||
return make([]*AccumulativeStatsProtocol, 0)
|
||||
}
|
||||
|
||||
methodsPerProtocolAggregated, protocolToColor := getAggregatedStatsAllTime(bucketStatsCopy)
|
||||
methodsPerProtocolAggregated := getAggregatedStats(stats)
|
||||
|
||||
return convertAccumulativeStatsDictToArray(methodsPerProtocolAggregated, protocolToColor)
|
||||
return convertAccumulativeStatsDictToArray(methodsPerProtocolAggregated)
|
||||
}
|
||||
|
||||
func GetAccumulativeStatsTiming(intervalSeconds int, numberOfBars int) []*AccumulativeStatsProtocolTime {
|
||||
bucketStatsCopy := getBucketStatsCopy()
|
||||
if len(bucketStatsCopy) == 0 {
|
||||
func getAccumulativeStatsTiming(stats BucketStats, interval time.Duration) []*AccumulativeStatsProtocolTime {
|
||||
if len(stats) == 0 {
|
||||
return make([]*AccumulativeStatsProtocolTime, 0)
|
||||
}
|
||||
|
||||
firstBucketTime := getFirstBucketTime(time.Now().UTC(), intervalSeconds, numberOfBars)
|
||||
methodsPerProtocolPerTimeAggregated := getAggregatedResultTiming(interval, stats)
|
||||
|
||||
methodsPerProtocolPerTimeAggregated, protocolToColor := getAggregatedResultTimingFromSpecificTime(intervalSeconds, bucketStatsCopy, firstBucketTime)
|
||||
|
||||
return convertAccumulativeStatsTimelineDictToArray(methodsPerProtocolPerTimeAggregated, protocolToColor)
|
||||
return convertAccumulativeStatsTimelineDictToArray(methodsPerProtocolPerTimeAggregated)
|
||||
}
|
||||
|
||||
func EntryAdded(size int, summery *api.BaseEntry) {
|
||||
@@ -128,7 +185,6 @@ func addToBucketStats(size int, summery *api.BaseEntry) {
|
||||
if _, found := bucketOfEntry.ProtocolStats[summery.Protocol.Abbreviation]; !found {
|
||||
bucketOfEntry.ProtocolStats[summery.Protocol.Abbreviation] = ProtocolStats{
|
||||
MethodsStats: map[string]*SizeAndEntriesCount{},
|
||||
Color: summery.Protocol.BackgroundColor,
|
||||
}
|
||||
}
|
||||
if _, found := bucketOfEntry.ProtocolStats[summery.Protocol.Abbreviation].MethodsStats[summery.Method]; !found {
|
||||
@@ -147,13 +203,7 @@ func getBucketFromTimeStamp(timestamp int64) time.Time {
|
||||
return entryTimeStampAsTime.Add(-1 * InternalBucketThreshold / 2).Round(InternalBucketThreshold)
|
||||
}
|
||||
|
||||
func getFirstBucketTime(endTime time.Time, intervalSeconds int, numberOfBars int) time.Time {
|
||||
lastBucketTime := endTime.Add(-1 * time.Second * time.Duration(intervalSeconds) / 2).Round(time.Second * time.Duration(intervalSeconds))
|
||||
firstBucketTime := lastBucketTime.Add(-1 * time.Second * time.Duration(intervalSeconds*(numberOfBars-1)))
|
||||
return firstBucketTime
|
||||
}
|
||||
|
||||
func convertAccumulativeStatsTimelineDictToArray(methodsPerProtocolPerTimeAggregated map[time.Time]map[string]map[string]*AccumulativeStatsCounter, protocolToColor map[string]string) []*AccumulativeStatsProtocolTime {
|
||||
func convertAccumulativeStatsTimelineDictToArray(methodsPerProtocolPerTimeAggregated map[time.Time]map[string]map[string]*AccumulativeStatsCounter) []*AccumulativeStatsProtocolTime {
|
||||
finalResult := make([]*AccumulativeStatsProtocolTime, 0)
|
||||
for timeKey, item := range methodsPerProtocolPerTimeAggregated {
|
||||
protocolsData := make([]*AccumulativeStatsProtocol, 0)
|
||||
@@ -184,7 +234,7 @@ func convertAccumulativeStatsTimelineDictToArray(methodsPerProtocolPerTimeAggreg
|
||||
return finalResult
|
||||
}
|
||||
|
||||
func convertAccumulativeStatsDictToArray(methodsPerProtocolAggregated map[string]map[string]*AccumulativeStatsCounter, protocolToColor map[string]string) []*AccumulativeStatsProtocol {
|
||||
func convertAccumulativeStatsDictToArray(methodsPerProtocolAggregated map[string]map[string]*AccumulativeStatsCounter) []*AccumulativeStatsProtocol {
|
||||
protocolsData := make([]*AccumulativeStatsProtocol, 0)
|
||||
for protocolName, value := range methodsPerProtocolAggregated {
|
||||
entriesCount := 0
|
||||
@@ -219,55 +269,44 @@ func getBucketStatsCopy() BucketStats {
|
||||
return bucketStatsCopy
|
||||
}
|
||||
|
||||
func getAggregatedResultTimingFromSpecificTime(intervalSeconds int, bucketStats BucketStats, firstBucketTime time.Time) (map[time.Time]map[string]map[string]*AccumulativeStatsCounter, map[string]string) {
|
||||
protocolToColor := map[string]string{}
|
||||
func getAggregatedResultTiming(interval time.Duration, stats BucketStats) map[time.Time]map[string]map[string]*AccumulativeStatsCounter {
|
||||
methodsPerProtocolPerTimeAggregated := map[time.Time]map[string]map[string]*AccumulativeStatsCounter{}
|
||||
|
||||
bucketStatsIndex := len(bucketStats) - 1
|
||||
bucketStatsIndex := len(stats) - 1
|
||||
for bucketStatsIndex >= 0 {
|
||||
currentBucketTime := bucketStats[bucketStatsIndex].BucketTime
|
||||
if currentBucketTime.After(firstBucketTime) || currentBucketTime.Equal(firstBucketTime) {
|
||||
resultBucketRoundedKey := currentBucketTime.Add(-1 * time.Second * time.Duration(intervalSeconds) / 2).Round(time.Second * time.Duration(intervalSeconds))
|
||||
currentBucketTime := stats[bucketStatsIndex].BucketTime
|
||||
resultBucketRoundedKey := currentBucketTime.Add(-1 * interval / 2).Round(interval)
|
||||
|
||||
for protocolName, data := range bucketStats[bucketStatsIndex].ProtocolStats {
|
||||
if _, ok := protocolToColor[protocolName]; !ok {
|
||||
protocolToColor[protocolName] = data.Color
|
||||
for protocolName, data := range stats[bucketStatsIndex].ProtocolStats {
|
||||
for methodName, dataOfMethod := range data.MethodsStats {
|
||||
|
||||
if _, ok := methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey]; !ok {
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey] = map[string]map[string]*AccumulativeStatsCounter{}
|
||||
}
|
||||
|
||||
for methodName, dataOfMethod := range data.MethodsStats {
|
||||
|
||||
if _, ok := methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey]; !ok {
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey] = map[string]map[string]*AccumulativeStatsCounter{}
|
||||
}
|
||||
if _, ok := methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName]; !ok {
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName] = map[string]*AccumulativeStatsCounter{}
|
||||
}
|
||||
if _, ok := methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName]; !ok {
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName] = &AccumulativeStatsCounter{
|
||||
Name: methodName,
|
||||
EntriesCount: 0,
|
||||
VolumeSizeBytes: 0,
|
||||
}
|
||||
}
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName].EntriesCount += dataOfMethod.EntriesCount
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName].VolumeSizeBytes += dataOfMethod.VolumeInBytes
|
||||
if _, ok := methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName]; !ok {
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName] = map[string]*AccumulativeStatsCounter{}
|
||||
}
|
||||
if _, ok := methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName]; !ok {
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName] = &AccumulativeStatsCounter{
|
||||
Name: methodName,
|
||||
EntriesCount: 0,
|
||||
VolumeSizeBytes: 0,
|
||||
}
|
||||
}
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName].EntriesCount += dataOfMethod.EntriesCount
|
||||
methodsPerProtocolPerTimeAggregated[resultBucketRoundedKey][protocolName][methodName].VolumeSizeBytes += dataOfMethod.VolumeInBytes
|
||||
}
|
||||
}
|
||||
|
||||
bucketStatsIndex--
|
||||
}
|
||||
return methodsPerProtocolPerTimeAggregated, protocolToColor
|
||||
return methodsPerProtocolPerTimeAggregated
|
||||
}
|
||||
|
||||
func getAggregatedStatsAllTime(bucketStatsCopy BucketStats) (map[string]map[string]*AccumulativeStatsCounter, map[string]string) {
|
||||
protocolToColor := make(map[string]string, 0)
|
||||
func getAggregatedStats(bucketStatsCopy BucketStats) map[string]map[string]*AccumulativeStatsCounter {
|
||||
methodsPerProtocolAggregated := make(map[string]map[string]*AccumulativeStatsCounter, 0)
|
||||
for _, countersOfTimeFrame := range bucketStatsCopy {
|
||||
for protocolName, value := range countersOfTimeFrame.ProtocolStats {
|
||||
if _, ok := protocolToColor[protocolName]; !ok {
|
||||
protocolToColor[protocolName] = value.Color
|
||||
}
|
||||
|
||||
for method, countersValue := range value.MethodsStats {
|
||||
if _, found := methodsPerProtocolAggregated[protocolName]; !found {
|
||||
methodsPerProtocolAggregated[protocolName] = map[string]*AccumulativeStatsCounter{}
|
||||
@@ -284,5 +323,5 @@ func getAggregatedStatsAllTime(bucketStatsCopy BucketStats) (map[string]map[stri
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodsPerProtocolAggregated, protocolToColor
|
||||
return methodsPerProtocolAggregated
|
||||
}
|
||||
|
||||
@@ -26,38 +26,6 @@ func TestGetBucketOfTimeStamp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type DataForBucketBorderFunction struct {
|
||||
EndTime time.Time
|
||||
IntervalInSeconds int
|
||||
NumberOfBars int
|
||||
}
|
||||
|
||||
func TestGetBucketBorders(t *testing.T) {
|
||||
tests := map[DataForBucketBorderFunction]time.Time{
|
||||
DataForBucketBorderFunction{
|
||||
time.Date(2022, time.Month(1), 1, 10, 34, 45, 0, time.UTC),
|
||||
300,
|
||||
10,
|
||||
}: time.Date(2022, time.Month(1), 1, 9, 45, 0, 0, time.UTC),
|
||||
DataForBucketBorderFunction{
|
||||
time.Date(2022, time.Month(1), 1, 10, 35, 45, 0, time.UTC),
|
||||
60,
|
||||
5,
|
||||
}: time.Date(2022, time.Month(1), 1, 10, 31, 00, 0, time.UTC),
|
||||
}
|
||||
|
||||
for key, value := range tests {
|
||||
t.Run(fmt.Sprintf("%v", key), func(t *testing.T) {
|
||||
|
||||
actual := getFirstBucketTime(key.EndTime, key.IntervalInSeconds, key.NumberOfBars)
|
||||
|
||||
if actual != value {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", value, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAggregatedStatsAllTime(t *testing.T) {
|
||||
bucketStatsForTest := BucketStats{
|
||||
&TimeFrameStatsValue{
|
||||
@@ -140,7 +108,7 @@ func TestGetAggregatedStatsAllTime(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
actual, _ := getAggregatedStatsAllTime(bucketStatsForTest)
|
||||
actual := getAggregatedStats(bucketStatsForTest)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", 3, len(actual))
|
||||
@@ -227,7 +195,7 @@ func TestGetAggregatedStatsFromSpecificTime(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
actual, _ := getAggregatedResultTimingFromSpecificTime(300, bucketStatsForTest, time.Date(2022, time.Month(1), 1, 10, 00, 00, 0, time.UTC))
|
||||
actual := getAggregatedResultTiming(time.Minute*5, bucketStatsForTest)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", 3, len(actual))
|
||||
@@ -323,7 +291,7 @@ func TestGetAggregatedStatsFromSpecificTimeMultipleBuckets(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
actual, _ := getAggregatedResultTimingFromSpecificTime(60, bucketStatsForTest, time.Date(2022, time.Month(1), 1, 10, 00, 00, 0, time.UTC))
|
||||
actual := getAggregatedResultTiming(time.Minute, bucketStatsForTest)
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("unexpected result - expected: %v, actual: %v", 3, len(actual))
|
||||
|
||||
@@ -15,9 +15,8 @@ func StatusRoutes(ginApp *gin.Engine) {
|
||||
routeGroup.GET("/connectedTappersCount", controllers.GetConnectedTappersCount)
|
||||
routeGroup.GET("/tap", controllers.GetTappingStatus)
|
||||
|
||||
routeGroup.GET("/general", controllers.GetGeneralStats) // get general stats about entries in DB
|
||||
routeGroup.GET("/accumulative", controllers.GetAccumulativeStats)
|
||||
routeGroup.GET("/accumulativeTiming", controllers.GetAccumulativeStatsTiming)
|
||||
routeGroup.GET("/general", controllers.GetGeneralStats)
|
||||
routeGroup.GET("/trafficStats", controllers.GetTrafficStats)
|
||||
|
||||
routeGroup.GET("/resolving", controllers.GetCurrentResolvingInformation)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
|
||||
import queryAtom from "../../recoil/query/atom";
|
||||
import useWindowDimensions, { useRequestTextByWidth } from "../../hooks/WindowDimensionsHook";
|
||||
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||
import spinner from "assets/spinner.svg";
|
||||
import entryDataAtom from "../../recoil/entryData";
|
||||
import { LoadingWrapper } from "../UI/withLoading/withLoading";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
entryTitle: {
|
||||
@@ -135,19 +135,11 @@ export const EntryDetailed = () => {
|
||||
// eslint-disable-next-line
|
||||
}, [focusedEntryId]);
|
||||
|
||||
return <React.Fragment>
|
||||
{isLoading && <div style={{textAlign: "center", width: "100%", marginTop: 50}}><img alt="spinner" src={spinner} style={{height: 60}}/></div>}
|
||||
{!isLoading && entryData && <EntryTitle
|
||||
protocol={entryData.protocol}
|
||||
data={entryData.data}
|
||||
elapsedTime={entryData.data.elapsedTime}
|
||||
/>}
|
||||
{!isLoading && entryData && <EntrySummary entry={entryData.base} namespace={entryData.data.namespace} />}
|
||||
<React.Fragment>
|
||||
{!isLoading && entryData && <EntryViewer
|
||||
representation={entryData.representation}
|
||||
color={entryData.protocol.backgroundColor}
|
||||
/>}
|
||||
</React.Fragment>
|
||||
</React.Fragment>
|
||||
return <LoadingWrapper isLoading={isLoading} loaderMargin={50} loaderHeight={60}>
|
||||
{entryData && <React.Fragment>
|
||||
<EntryTitle protocol={entryData.protocol} data={entryData.data} elapsedTime={entryData.data.elapsedTime} />
|
||||
<EntrySummary entry={entryData.base} namespace={entryData.data.namespace} />
|
||||
<EntryViewer representation={entryData.representation} color={entryData.protocol.backgroundColor} />
|
||||
</React.Fragment>}
|
||||
</LoadingWrapper>
|
||||
};
|
||||
|
||||
@@ -117,7 +117,8 @@ interface EntryBodySectionProps {
|
||||
selector?: string,
|
||||
}
|
||||
|
||||
export const formatRequest = (body: any, contentType: string, decodeBase64: boolean = true, isBase64Encoding: boolean = false, isPretty: boolean = true): string => {
|
||||
export const formatRequest = (bodyRef: any, contentType: string, decodeBase64: boolean = true, isBase64Encoding: boolean = false, isPretty: boolean = true): string => {
|
||||
const { body } = bodyRef
|
||||
if (!decodeBase64 || !body) return body;
|
||||
|
||||
const chunk = body.slice(0, MAXIMUM_BYTES_TO_FORMAT);
|
||||
@@ -126,7 +127,7 @@ export const formatRequest = (body: any, contentType: string, decodeBase64: bool
|
||||
try {
|
||||
if (jsonLikeFormats.some(format => contentType?.indexOf(format) > -1)) {
|
||||
if (!isPretty) return bodyBuf;
|
||||
return jsonBeautify(JSON.parse(bodyBuf), null, 2, 80);
|
||||
return Utils.isJson(bodyBuf) ? jsonBeautify(JSON.parse(bodyBuf), null, 2, 80) : bodyBuf
|
||||
} else if (xmlLikeFormats.some(format => contentType?.indexOf(format) > -1)) {
|
||||
if (!isPretty) return bodyBuf;
|
||||
return xmlBeautify(bodyBuf, {
|
||||
@@ -144,12 +145,24 @@ export const formatRequest = (body: any, contentType: string, decodeBase64: bool
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
bodyRef.body = bodyBuf
|
||||
throw error
|
||||
}
|
||||
|
||||
return bodyBuf;
|
||||
}
|
||||
|
||||
export const formatRequestWithOutError = (body: any, contentType: string, decodeBase64: boolean = true, isBase64Encoding: boolean = false, isPretty: boolean = true): string => {
|
||||
const bodyRef = { body }
|
||||
try {
|
||||
return formatRequest(bodyRef, contentType, decodeBase64, isBase64Encoding, isPretty)
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
|
||||
return bodyRef.body
|
||||
}
|
||||
|
||||
export const EntryBodySection: React.FC<EntryBodySectionProps> = ({
|
||||
title,
|
||||
color,
|
||||
@@ -173,8 +186,9 @@ export const EntryBodySection: React.FC<EntryBodySectionProps> = ({
|
||||
}, [isLineNumbersGreaterThenOne, isPretty])
|
||||
|
||||
const formatTextBody = useCallback((body) => {
|
||||
const bodyRef = { body }
|
||||
try {
|
||||
return formatRequest(body, contentType, decodeBase64, isBase64Encoding, isPretty)
|
||||
return formatRequest(bodyRef, contentType, decodeBase64, isBase64Encoding, isPretty)
|
||||
} catch (error) {
|
||||
if (String(error).includes("More than one message in")) {
|
||||
if (isDecodeGrpc)
|
||||
@@ -183,6 +197,8 @@ export const EntryBodySection: React.FC<EntryBodySectionProps> = ({
|
||||
console.warn(error);
|
||||
}
|
||||
}
|
||||
|
||||
return bodyRef.body
|
||||
}, [isPretty, contentType, isDecodeGrpc, decodeBase64, isBase64Encoding])
|
||||
|
||||
const formattedText = useMemo(() => formatTextBody(content), [formatTextBody, content]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback } from "react"
|
||||
import React, { useState, useCallback, useEffect, useMemo } from "react"
|
||||
import { useRecoilValue, useSetRecoilState } from "recoil"
|
||||
import entryDataAtom from "../../../recoil/entryData"
|
||||
import SectionsRepresentation from "./SectionsRepresentation";
|
||||
@@ -9,49 +9,68 @@ import replayRequestModalOpenAtom from "../../../recoil/replayRequestModalOpen";
|
||||
|
||||
const enabledProtocolsForReplay = ["http"]
|
||||
|
||||
export const AutoRepresentation: React.FC<any> = ({ representation, color, isDisplayReplay = false }) => {
|
||||
export enum TabsEnum {
|
||||
Request = 0,
|
||||
Response = 1
|
||||
}
|
||||
|
||||
export const AutoRepresentation: React.FC<any> = ({ representation, color, openedTab = TabsEnum.Request, isDisplayReplay = false }) => {
|
||||
const entryData = useRecoilValue(entryDataAtom)
|
||||
const setIsOpenRequestModal = useSetRecoilState(replayRequestModalOpenAtom)
|
||||
const isReplayDisplayed = useCallback(() => {
|
||||
return enabledProtocolsForReplay.find(x => x === entryData.protocol.name) && isDisplayReplay
|
||||
}, [entryData.protocol.name, isDisplayReplay])
|
||||
|
||||
const TABS = [
|
||||
{
|
||||
tab: 'Request',
|
||||
badge: isReplayDisplayed() && <span title="Replay Request"><ReplayIcon fill={color} stroke={color} style={{ marginLeft: "10px", cursor: "pointer", height: "22px" }} onClick={() => setIsOpenRequestModal(true)} /></span>
|
||||
const { request, response } = JSON.parse(representation);
|
||||
|
||||
const TABS = useMemo(() => {
|
||||
const arr = [
|
||||
{
|
||||
tab: 'Request',
|
||||
badge: isReplayDisplayed() && <span title="Replay Request"><ReplayIcon fill={color} stroke={color} style={{ marginLeft: "10px", cursor: "pointer", height: "22px" }} onClick={() => setIsOpenRequestModal(true)} /></span>
|
||||
}]
|
||||
|
||||
if (response) {
|
||||
arr.push(
|
||||
{
|
||||
tab: 'Response',
|
||||
badge: null
|
||||
}
|
||||
);
|
||||
}
|
||||
];
|
||||
|
||||
return arr
|
||||
}, [color, isReplayDisplayed, response, setIsOpenRequestModal]);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(TABS[0].tab);
|
||||
|
||||
const getOpenedTabIndex = useCallback(() => {
|
||||
const currentIndex = TABS.findIndex(current => current.tab === currentTab)
|
||||
return currentIndex > -1 ? currentIndex : 0
|
||||
}, [TABS, currentTab])
|
||||
|
||||
useEffect(() => {
|
||||
if (openedTab) {
|
||||
setCurrentTab(TABS[openedTab].tab)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
// Don't fail even if `representation` is an empty string
|
||||
if (!representation) {
|
||||
return <React.Fragment></React.Fragment>;
|
||||
}
|
||||
|
||||
const { request, response } = JSON.parse(representation);
|
||||
|
||||
let responseTabIndex = 0;
|
||||
|
||||
if (response) {
|
||||
TABS.push(
|
||||
{
|
||||
tab: 'Response',
|
||||
badge: null
|
||||
}
|
||||
);
|
||||
responseTabIndex = TABS.length - 1;
|
||||
}
|
||||
|
||||
return <div className={styles.Entry}>
|
||||
{<div className={styles.body}>
|
||||
<div className={styles.bodyHeader}>
|
||||
<Tabs tabs={TABS} currentTab={currentTab} color={color} onChange={setCurrentTab} leftAligned />
|
||||
</div>
|
||||
{currentTab === TABS[0].tab && <React.Fragment>
|
||||
{getOpenedTabIndex() === TabsEnum.Request && <React.Fragment>
|
||||
<SectionsRepresentation data={request} color={color} requestRepresentation={request} />
|
||||
</React.Fragment>}
|
||||
{response && currentTab === TABS[responseTabIndex].tab && <React.Fragment>
|
||||
{response && getOpenedTabIndex() === TabsEnum.Response && <React.Fragment>
|
||||
<SectionsRepresentation data={response} color={color} />
|
||||
</React.Fragment>}
|
||||
</div>}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||
import leftOffTopAtom from "../../recoil/leftOffTop";
|
||||
import { DEFAULT_LEFTOFF, DEFAULT_FETCH, DEFAULT_FETCH_TIMEOUT_MS } from '../../hooks/useWS';
|
||||
import ReplayRequestModalContainer from "../modals/ReplayRequestModal/ReplayRequestModal";
|
||||
import replayRequestModalOpenAtom from "../../recoil/replayRequestModalOpen";
|
||||
|
||||
const useLayoutStyles = makeStyles(() => ({
|
||||
details: {
|
||||
@@ -70,6 +71,7 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({
|
||||
const [wsReadyState, setWsReadyState] = useState(0);
|
||||
const setLeftOffTop = useSetRecoilState(leftOffTopAtom);
|
||||
const scrollableRef = useRef(null);
|
||||
const isOpenReplayModal = useRecoilValue(replayRequestModalOpenAtom)
|
||||
|
||||
|
||||
const ws = useRef(null);
|
||||
@@ -88,6 +90,10 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({
|
||||
}
|
||||
}, [shouldCloseWebSocket, setShouldCloseWebSocket, closeWebSocket])
|
||||
|
||||
useEffect(() => {
|
||||
isOpenReplayModal && setShouldCloseWebSocket(true)
|
||||
}, [isOpenReplayModal, setShouldCloseWebSocket])
|
||||
|
||||
const sendQueryWhenWsOpen = useCallback((leftOff: string, query: string, fetch: number, fetchTimeoutMs: number) => {
|
||||
setTimeout(() => {
|
||||
if (ws?.current?.readyState === WebSocket.OPEN) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import CustomModal from "./CustomModal/CustomModal";
|
||||
import { InformationIcon, Link } from "./InformationIcon/InformationIcon";
|
||||
import SelectList from "./SelectList/SelectList";
|
||||
import NoDataMessage from "./NoDataMessage/NoDataMessage";
|
||||
import withLoading from "./withLoading/withLoading";
|
||||
|
||||
export { LoadingOverlay, Select, Tabs, Tooltip, Checkbox, CustomModal, InformationIcon, SelectList, NoDataMessage, Link };
|
||||
export { LoadingOverlay, Select, Tabs, Tooltip, Checkbox, CustomModal, InformationIcon, SelectList, NoDataMessage, withLoading, Link };
|
||||
export { StatusBar }
|
||||
|
||||
6
ui-common/src/components/UI/withLoading/spinner.svg
Normal file
6
ui-common/src/components/UI/withLoading/spinner.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<circle cx="50" cy="50" fill="none" stroke="#1d3f72" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138" transform="rotate(275.903 50 50)">
|
||||
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform>
|
||||
</circle>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
||||
|
After Width: | Height: | Size: 673 B |
33
ui-common/src/components/UI/withLoading/withLoading.tsx
Normal file
33
ui-common/src/components/UI/withLoading/withLoading.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import spinner from 'spinner.svg';
|
||||
|
||||
export interface WithLoadingProps {
|
||||
isLoading: boolean
|
||||
loaderMargin?: number,
|
||||
loaderHeight?: number
|
||||
}
|
||||
|
||||
const Loader = ({ loaderMargin = 20, loaderHeight = 35 }: Omit<WithLoadingProps, "isLoading">) => {
|
||||
return <div style={{ textAlign: "center", margin: loaderMargin }}>
|
||||
<img alt="spinner" src={spinner} style={{ height: loaderHeight }} />
|
||||
</div>
|
||||
}
|
||||
|
||||
const withLoading = <P extends object>(
|
||||
Component: React.ComponentType<P>
|
||||
): React.FC<P & WithLoadingProps> => ({
|
||||
isLoading,
|
||||
loaderMargin,
|
||||
loaderHeight,
|
||||
...props
|
||||
}: WithLoadingProps) => isLoading ?
|
||||
<Loader loaderMargin={loaderMargin} loaderHeight={loaderHeight} /> :
|
||||
<Component {...props as P} />;
|
||||
|
||||
export const LoadingWrapper: React.FC<WithLoadingProps> = ({ loaderMargin, loaderHeight, isLoading, children }) => {
|
||||
return isLoading ?
|
||||
<Loader loaderMargin={loaderMargin} loaderHeight={loaderHeight} /> :
|
||||
<React.Fragment>{children}</React.Fragment>
|
||||
}
|
||||
|
||||
export default withLoading
|
||||
@@ -12,14 +12,14 @@ import { toast } from "react-toastify";
|
||||
import { TOAST_CONTAINER_ID } from "../../../configs/Consts";
|
||||
import styles from './ReplayRequestModal.module.sass'
|
||||
import closeIcon from "assets/close.svg"
|
||||
import spinnerImg from "assets/spinner.svg"
|
||||
import refreshImg from "assets/refresh.svg"
|
||||
import { formatRequest } from "../../EntryDetailed/EntrySections/EntrySections";
|
||||
import { formatRequestWithOutError } from "../../EntryDetailed/EntrySections/EntrySections";
|
||||
import entryDataAtom from "../../../recoil/entryData";
|
||||
import { AutoRepresentation } from "../../EntryDetailed/EntryViewer/AutoRepresentation";
|
||||
import { AutoRepresentation, TabsEnum } from "../../EntryDetailed/EntryViewer/AutoRepresentation";
|
||||
import useDebounce from "../../../hooks/useDebounce"
|
||||
import replayRequestModalOpenAtom from "../../../recoil/replayRequestModalOpen";
|
||||
import { Utils } from "../../../helpers/Utils";
|
||||
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute',
|
||||
@@ -175,7 +175,7 @@ const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose
|
||||
</Fragment>
|
||||
break;
|
||||
case RequestTabs.Body:
|
||||
const formatedCode = formatRequest(postData || "", request?.postData?.mimeType)
|
||||
const formatedCode = formatRequestWithOutError(postData || "", request?.postData?.mimeType)
|
||||
innerComponent = <div className={styles.codeEditor}>
|
||||
<CodeEditor language={request?.postData?.mimeType.split("/")[1]}
|
||||
code={Utils.isJson(formatedCode) ? JSON.stringify(JSON.parse(formatedCode || "{}"), null, 2) : formatedCode}
|
||||
@@ -233,15 +233,16 @@ const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
{isLoading && <img alt="spinner" src={spinnerImg} style={{ height: 50 }} />}
|
||||
{response && !isLoading && (<Accordion TransitionProps={{ unmountOnExit: true }} expanded={responseExpanded} onChange={() => setResponseExpanded(!responseExpanded)}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="response-content">
|
||||
<span className={styles.sectionHeader}>RESPONSE</span>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AutoRepresentation representation={response} color={entryData.protocol.backgroundColor} />
|
||||
</AccordionDetails>
|
||||
</Accordion>)}
|
||||
<LoadingWrapper isLoading={isLoading} loaderMargin={10} loaderHeight={50}>
|
||||
{response && (<Accordion TransitionProps={{ unmountOnExit: true }} expanded={responseExpanded} onChange={() => setResponseExpanded(!responseExpanded)}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="response-content">
|
||||
<span className={styles.sectionHeader}>RESPONSE</span>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<AutoRepresentation representation={response} color={entryData.protocol.backgroundColor} openedTab={TabsEnum.Response} />
|
||||
</AccordionDetails>
|
||||
</Accordion>)}
|
||||
</LoadingWrapper>
|
||||
</div>
|
||||
</Box>
|
||||
</Fade>
|
||||
|
||||
@@ -96,8 +96,3 @@ $modalMargin-from-edge : 35px
|
||||
|
||||
.servicesFilterList
|
||||
height: calc(100% - 30px - 52px)
|
||||
|
||||
.spinnerContainer
|
||||
display: flex
|
||||
justify-content: center
|
||||
margin-bottom: 10px
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { Box, Fade, Modal, Backdrop, Button } from "@mui/material";
|
||||
import { toast } from "react-toastify";
|
||||
import spinnerImg from 'assets/spinner.svg';
|
||||
import Graph from "react-graph-vis";
|
||||
import debounce from 'lodash/debounce';
|
||||
import ServiceMapOptions from './ServiceMapOptions'
|
||||
@@ -16,6 +15,7 @@ import { GraphData, ServiceMapGraph } from "./ServiceMapModalTypes"
|
||||
import { Utils } from "../../../helpers/Utils";
|
||||
import { TOAST_CONTAINER_ID } from "../../../configs/Consts";
|
||||
import Resizeable from "../../UI/Resizeable/Resizeable"
|
||||
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute',
|
||||
@@ -197,14 +197,14 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
<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.headerContainer}>
|
||||
<div className={styles.headerSection}>
|
||||
<span className={styles.title}>Services</span>
|
||||
<Button size="medium"
|
||||
variant="contained"
|
||||
startIcon={<img src={isFilterClicked ? filterIconClicked : filterIcon} className="custom" alt="refresh" style={{ height: "26px", width: "26px" }}/>}
|
||||
startIcon={<img src={isFilterClicked ? filterIconClicked : filterIcon} className="custom" alt="refresh" style={{ height: "26px", width: "26px" }} />}
|
||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton + ` ${isFilterClicked ? commonClasses.clickedButton : ""}`}
|
||||
onClick={() => setIsFilterClicked(prevState => !prevState)}
|
||||
style={{ textTransform: 'unset' }}>
|
||||
@@ -243,16 +243,14 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
<div className={styles.graphSection}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
</div>
|
||||
{isLoading && <div className={styles.spinnerContainer}>
|
||||
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
||||
</div>}
|
||||
{!isLoading && <div style={{ height: "100%", width: "100%" }}>
|
||||
<Graph
|
||||
graph={graphData}
|
||||
options={graphOptions}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<LoadingWrapper isLoading={isLoading} loaderHeight={50} loaderMargin={20}>
|
||||
<div style={{ height: "100%", width: "100%" }}>
|
||||
<Graph
|
||||
graph={graphData}
|
||||
options={graphOptions}
|
||||
/>
|
||||
</div>
|
||||
</LoadingWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
||||
if (!data) return;
|
||||
const protocolsBarsData = [];
|
||||
const prtcNames = [];
|
||||
data.forEach(protocolObj => {
|
||||
data.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1).forEach(protocolObj => {
|
||||
let newProtocolbj: { [k: string]: any } = {};
|
||||
newProtocolbj.timestamp = Utils.getHoursAndMinutes(protocolObj.timestamp);
|
||||
protocolObj.protocols.forEach(protocol => {
|
||||
@@ -36,7 +36,6 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
||||
protocolsBarsData.push(newProtocolbj);
|
||||
})
|
||||
const uniqueObjArray = Utils.creatUniqueObjArrayByProp(prtcNames, "name")
|
||||
protocolsBarsData.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1);
|
||||
setProtocolStats(protocolsBarsData);
|
||||
setProtocolsNamesAndColors(uniqueObjArray);
|
||||
}, [data, timeLineBarChartMode])
|
||||
@@ -49,7 +48,7 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
||||
}
|
||||
const commandsNames = [];
|
||||
const protocolsCommands = [];
|
||||
data.forEach(protocolObj => {
|
||||
data.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1).forEach(protocolObj => {
|
||||
let newCommandlbj: { [k: string]: any } = {};
|
||||
newCommandlbj.timestamp = Utils.getHoursAndMinutes(protocolObj.timestamp);
|
||||
protocolObj.protocols.find(protocol => protocol.name === selectedProtocol)?.methods.forEach(command => {
|
||||
@@ -59,21 +58,33 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
||||
})
|
||||
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)} />
|
||||
return <Bar key={entry.name || entry} dataKey={entry.name || entry} stackId="a" fill={entry.color || Utils.stringToColor(entry)} barSize={30} />
|
||||
}), [protocolsNamesAndColors, commandNames])
|
||||
|
||||
const renderTick = (tickProps) => {
|
||||
const { x, y, payload } = tickProps;
|
||||
const { index, value } = payload;
|
||||
|
||||
if (index % 3 === 0) {
|
||||
return <text x={x} y={y + 10} textAnchor="end">{`${value}`}</text>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.barChartContainer}>
|
||||
{protocolStats.length > 0 && <BarChart
|
||||
width={730}
|
||||
width={750}
|
||||
height={250}
|
||||
data={commandStats || protocolStats}
|
||||
barCategoryGap={0}
|
||||
barSize={30}
|
||||
margin={{
|
||||
top: 20,
|
||||
right: 30,
|
||||
@@ -81,7 +92,7 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
||||
bottom: 5
|
||||
}}
|
||||
>
|
||||
<XAxis dataKey="timestamp" />
|
||||
<XAxis dataKey="timestamp" tick={renderTick} tickLine={false} />
|
||||
<YAxis tickFormatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value} />
|
||||
<Tooltip formatter={(value) => timeLineBarChartMode === "VOLUME" ? Utils.humanFileSize(value) : value + " Requests"} />
|
||||
{bars}
|
||||
|
||||
@@ -4,16 +4,16 @@ import styles from "./TrafficStatsModal.module.sass";
|
||||
import closeIcon from "assets/close.svg";
|
||||
import { TrafficPieChart } from "./TrafficPieChart/TrafficPieChart";
|
||||
import { TimelineBarChart } from "./TimelineBarChart/TimelineBarChart";
|
||||
import spinnerImg from "assets/spinner.svg";
|
||||
import refreshIcon from "assets/refresh.svg";
|
||||
import { useCommonStyles } from "../../../helpers/commonStyle";
|
||||
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute',
|
||||
top: '6%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, 0%)',
|
||||
width: '50vw',
|
||||
width: '60vw',
|
||||
height: '82vh',
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: '5px',
|
||||
@@ -30,14 +30,14 @@ export enum StatsMode {
|
||||
interface TrafficStatsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
getPieStatsDataApi: () => Promise<any>
|
||||
getTimelineStatsDataApi: () => Promise<any>
|
||||
getTrafficStatsDataApi: () => Promise<any>
|
||||
}
|
||||
|
||||
export const PROTOCOLS = ["ALL", "gRPC", "REDIS", "HTTP", "GQL", "AMQP", "KFAKA"];
|
||||
|
||||
export const PROTOCOLS = ["ALL", "gRPC", "REDIS", "HTTP", "GQL", "AMQP", "KAFKA"];
|
||||
export const ALL_PROTOCOLS = PROTOCOLS[0];
|
||||
|
||||
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getPieStatsDataApi, getTimelineStatsDataApi }) => {
|
||||
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getTrafficStatsDataApi }) => {
|
||||
|
||||
const modes = Object.keys(StatsMode).filter(x => !(parseInt(x) >= 0));
|
||||
const [statsMode, setStatsMode] = useState(modes[0]);
|
||||
@@ -48,14 +48,13 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
const commonClasses = useCommonStyles();
|
||||
|
||||
const getTrafficStats = useCallback(async () => {
|
||||
if (isOpen && getPieStatsDataApi) {
|
||||
if (isOpen && getTrafficStatsDataApi) {
|
||||
(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const pieData = await getPieStatsDataApi();
|
||||
setPieStatsData(pieData);
|
||||
const timelineData = await getTimelineStatsDataApi();
|
||||
setTimelineStatsData(timelineData);
|
||||
const statsData = await getTrafficStatsDataApi();
|
||||
setPieStatsData(statsData.pie);
|
||||
setTimelineStatsData(statsData.timeline);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
@@ -63,7 +62,7 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
}
|
||||
})()
|
||||
}
|
||||
}, [isOpen, getPieStatsDataApi, getTimelineStatsDataApi, setPieStatsData, setTimelineStatsData])
|
||||
}, [isOpen, getTrafficStatsDataApi, setPieStatsData, setTimelineStatsData])
|
||||
|
||||
useEffect(() => {
|
||||
getTrafficStats();
|
||||
@@ -115,13 +114,12 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{isLoading ? <div style={{ textAlign: "center", marginTop: 20 }}>
|
||||
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
||||
</div> :
|
||||
<LoadingWrapper isLoading={isLoading} loaderMargin={20} loaderHeight={50}>
|
||||
<div>
|
||||
<TrafficPieChart pieChartMode={statsMode} data={pieStatsData} selectedProtocol={selectedProtocol}/>
|
||||
<TimelineBarChart timeLineBarChartMode={statsMode} data={timelineStatsData} selectedProtocol={selectedProtocol}/>
|
||||
</div>}
|
||||
<TrafficPieChart pieChartMode={statsMode} data={pieStatsData} selectedProtocol={selectedProtocol} />
|
||||
<TimelineBarChart timeLineBarChartMode={statsMode} data={timelineStatsData} selectedProtocol={selectedProtocol} />
|
||||
</div>
|
||||
</LoadingWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
@@ -36,7 +36,7 @@ const App = () => {
|
||||
openModal={oasModalOpen}
|
||||
handleCloseModal={() => setOasModalOpen(false)}
|
||||
/>}
|
||||
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getPieStatsDataApi={api.getPieStats} getTimelineStatsDataApi={api.getTimelineStats}/>
|
||||
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getTrafficStatsDataApi={api.getTrafficStats}/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
|
||||
@@ -116,13 +116,8 @@ export default class Api {
|
||||
});
|
||||
}
|
||||
|
||||
getPieStats = async () => {
|
||||
const response = await client.get("/status/accumulative");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getTimelineStats = async () => {
|
||||
const response = await client.get("/status/accumulativeTiming");
|
||||
getTrafficStats = async () => {
|
||||
const response = await client.get("/status/trafficStats");
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user