🔥 Delete the ui directory and deploy kubeshark/front in a pod named front (#1246)

* Remove the `ui` directory

* Deploy the UI in a separate pod named `front`

* Fix the port number

* Fix the port forwarding

* Call `postFrontStarted` only after `kubeshark-api-server` and `front` are ready

* Fix linter

* Fix `yaml` comments
This commit is contained in:
M. Mert Yildiran 2022-11-23 13:13:04 -08:00 committed by GitHub
parent 2a35abd4e0
commit 8c97c4a120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
201 changed files with 309 additions and 69752 deletions

View File

@ -101,26 +101,3 @@ jobs:
with:
version: latest
working-directory: logger
eslint:
name: ESLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- uses: actions/setup-node@v2
with:
node-version: 16
- name: Install dependencies
run: |
sudo npm install -g eslint
cd ui
npm i
- name: Lint
run: |
cd ui
npm run eslint

View File

@ -1,16 +1,6 @@
ARG BUILDARCH=amd64
ARG TARGETARCH=amd64
### Front-end
FROM node:16 AS front-end
WORKDIR /app/ui-build
COPY ui/package.json ui/package-lock.json ./
RUN npm i
COPY ui .
RUN npm run build
### Base builder image for native builds architecture
FROM golang:1.17-alpine AS builder-native-base
ENV CGO_ENABLED=1 GOOS=linux
@ -124,7 +114,6 @@ WORKDIR /app
# Copy binary and config files from /build to root folder of scratch container.
COPY --from=builder ["/app/agent-build/kubesharkagent", "."]
COPY --from=builder ["/app/agent-build/basenine", "/usr/local/bin/basenine"]
COPY --from=front-end ["/app/ui-build/build", "site"]
# this script runs both apiserver and passivetapper and exits either if one of them exits, preventing a scenario where the container runs without one process
ENTRYPOINT ["/app/kubesharkagent"]

View File

@ -8,13 +8,10 @@ import (
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/gin-contrib/pprof"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/kubeshark/kubeshark/agent/pkg/dependency"
"github.com/kubeshark/kubeshark/agent/pkg/entries"
@ -100,20 +97,7 @@ func hostApi(socketHarOutputChannel chan<- *tapApi.OutputChannelItem) *gin.Engin
SocketOutChannel: socketHarOutputChannel,
}
ginApp.Use(disableRootStaticCache())
staticFolder := "./site"
indexStaticFile := staticFolder + "/index.html"
if err := setUIFlags(indexStaticFile); err != nil {
logger.Log.Errorf("Error setting ui flags, err: %v", err)
}
ginApp.Use(static.ServeRoot("/", staticFolder))
ginApp.NoRoute(func(c *gin.Context) {
c.File(indexStaticFile)
})
ginApp.Use(middlewares.CORSMiddleware()) // This has to be called after the static middleware, does not work if it's called before
ginApp.Use(middlewares.CORSMiddleware())
api.WebSocketRoutes(ginApp, &eventHandlers)
@ -210,34 +194,6 @@ func enableExpFeatureIfNeeded() {
}
}
func disableRootStaticCache() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.RequestURI == "/" {
// Disable cache only for the main static route
c.Writer.Header().Set("Cache-Control", "no-store")
}
c.Next()
}
}
func setUIFlags(uiIndexPath string) error {
read, err := os.ReadFile(uiIndexPath)
if err != nil {
return err
}
replacedContent := strings.Replace(string(read), "__IS_OAS_ENABLED__", strconv.FormatBool(config.Config.OAS.Enable), 1)
replacedContent = strings.Replace(replacedContent, "__IS_SERVICE_MAP_ENABLED__", strconv.FormatBool(config.Config.ServiceMap), 1)
err = os.WriteFile(uiIndexPath, []byte(replacedContent), 0)
if err != nil {
return err
}
return nil
}
func getTrafficFilteringOptions() *tapApi.TrafficFilteringOptions {
filteringOptionsJson := os.Getenv(shared.KubesharkFilteringOptionsEnvVar)
if filteringOptionsJson == "" {

View File

@ -34,10 +34,10 @@ func NewProvider(url string, retries int, timeout time.Duration) *Provider {
}
}
func (provider *Provider) TestConnection() error {
func (provider *Provider) TestConnection(path string) error {
retriesLeft := provider.retries
for retriesLeft > 0 {
if isReachable, err := provider.isReachable(); err != nil || !isReachable {
if isReachable, err := provider.isReachable(path); err != nil || !isReachable {
logger.Log.Debugf("api server not ready yet %v", err)
} else {
logger.Log.Debugf("connection test to api server passed successfully")
@ -53,9 +53,9 @@ func (provider *Provider) TestConnection() error {
return nil
}
func (provider *Provider) isReachable() (bool, error) {
echoUrl := fmt.Sprintf("%s/echo", provider.url)
if _, err := utils.Get(echoUrl, provider.client); err != nil {
func (provider *Provider) isReachable(path string) (bool, error) {
targetUrl := fmt.Sprintf("%s%s", provider.url, path)
if _, err := utils.Get(targetUrl, provider.client); err != nil {
return false, err
} else {
return true, nil

View File

@ -15,10 +15,10 @@ import (
func ServerConnection(kubernetesProvider *kubernetes.Provider) bool {
logger.Log.Infof("\nAPI-server-connectivity\n--------------------")
serverUrl := fmt.Sprintf("http://%s", kubernetes.GetKubesharkApiServerProxiedHostAndPath(config.Config.Tap.GuiPort))
serverUrl := kubernetes.GetLocalhostOnPort(config.Config.Hub.PortForward.SrcPort)
apiServerProvider := apiserver.NewProvider(serverUrl, 1, apiserver.DefaultTimeout)
if err := apiServerProvider.TestConnection(); err == nil {
if err := apiServerProvider.TestConnection(""); err == nil {
logger.Log.Infof("%v found Kubeshark server tunnel available and connected successfully to API server", fmt.Sprintf(uiUtils.Green, "√"))
return true
}
@ -46,13 +46,13 @@ func checkProxy(serverUrl string, kubernetesProvider *kubernetes.Provider) error
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpServer, err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.ProxyHost, config.Config.Tap.GuiPort, config.Config.KubesharkResourcesNamespace, kubernetes.ApiServerPodName, cancel)
httpServer, err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.ProxyHost, config.Config.Hub.PortForward.SrcPort, config.Config.Hub.PortForward.DstPort, config.Config.KubesharkResourcesNamespace, kubernetes.ApiServerPodName, cancel)
if err != nil {
return err
}
apiServerProvider := apiserver.NewProvider(serverUrl, apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err := apiServerProvider.TestConnection(); err != nil {
if err := apiServerProvider.TestConnection(""); err != nil {
return err
}
@ -68,13 +68,13 @@ func checkPortForward(serverUrl string, kubernetesProvider *kubernetes.Provider)
defer cancel()
podRegex, _ := regexp.Compile(kubernetes.ApiServerPodName)
forwarder, err := kubernetes.NewPortForward(kubernetesProvider, config.Config.KubesharkResourcesNamespace, podRegex, config.Config.Tap.GuiPort, ctx, cancel)
forwarder, err := kubernetes.NewPortForward(kubernetesProvider, config.Config.KubesharkResourcesNamespace, podRegex, config.Config.Tap.GuiPort, config.Config.Tap.GuiPort, ctx, cancel)
if err != nil {
return err
}
apiServerProvider := apiserver.NewProvider(serverUrl, apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err := apiServerProvider.TestConnection(); err != nil {
if err := apiServerProvider.TestConnection(""); err != nil {
return err
}

View File

@ -23,12 +23,8 @@ import (
"github.com/kubeshark/kubeshark/shared/kubernetes"
)
func GetApiServerUrl(port uint16) string {
return fmt.Sprintf("http://%s", kubernetes.GetKubesharkApiServerProxiedHostAndPath(port))
}
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, ctx context.Context, cancel context.CancelFunc, port uint16) {
httpServer, err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.ProxyHost, port, config.Config.KubesharkResourcesNamespace, kubernetes.ApiServerPodName, cancel)
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, ctx context.Context, cancel context.CancelFunc, serviceName string, srcPort uint16, dstPort uint16, healthCheck string) {
httpServer, err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.ProxyHost, srcPort, dstPort, config.Config.KubesharkResourcesNamespace, serviceName, cancel)
if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running k8s proxy %v\n"+
"Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName))
@ -36,25 +32,25 @@ func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, ctx con
return
}
provider := apiserver.NewProvider(GetApiServerUrl(port), apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err := provider.TestConnection(); err != nil {
provider := apiserver.NewProvider(kubernetes.GetLocalhostOnPort(srcPort), apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err := provider.TestConnection(healthCheck); err != nil {
logger.Log.Debugf("Couldn't connect using proxy, stopping proxy and trying to create port-forward")
if err := httpServer.Shutdown(ctx); err != nil {
logger.Log.Debugf("Error occurred while stopping proxy %v", errormessage.FormatError(err))
}
podRegex, _ := regexp.Compile(kubernetes.ApiServerPodName)
if _, err := kubernetes.NewPortForward(kubernetesProvider, config.Config.KubesharkResourcesNamespace, podRegex, port, ctx, cancel); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running port forward %v\n"+
"Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName))
if _, err := kubernetes.NewPortForward(kubernetesProvider, config.Config.KubesharkResourcesNamespace, podRegex, srcPort, dstPort, ctx, cancel); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running port forward [%s] %v\n"+
"Try setting different port by using --%s", podRegex, errormessage.FormatError(err), configStructs.GuiPortTapName))
cancel()
return
}
provider = apiserver.NewProvider(GetApiServerUrl(port), apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err := provider.TestConnection(); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", fsUtils.GetLogFilePath()))
cancel()
provider = apiserver.NewProvider(kubernetes.GetLocalhostOnPort(srcPort), apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err := provider.TestConnection(healthCheck); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to [%s], for more info check logs at %s", serviceName, fsUtils.GetLogFilePath()))
// cancel()
return
}
}

View File

@ -46,7 +46,7 @@ var configCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(configCmd)
defaultConfig := config.ConfigStruct{}
defaultConfig := config.CreateDefaultConfig()
if err := defaults.Set(&defaultConfig); err != nil {
logger.Log.Debug(err)
}

View File

@ -29,7 +29,7 @@ Further info is available at https://github.com/kubeshark/kubeshark`,
}
func init() {
defaultConfig := config.ConfigStruct{}
defaultConfig := config.CreateDefaultConfig()
if err := defaults.Set(&defaultConfig); err != nil {
logger.Log.Debug(err)
}

View File

@ -37,11 +37,14 @@ type tapState struct {
var state tapState
var apiProvider *apiserver.Provider
var apiServerPodReady bool
var frontPodReady bool
var proxyDone bool
func RunKubesharkTap() {
state.startTime = time.Now()
apiProvider = apiserver.NewProvider(GetApiServerUrl(config.Config.Tap.GuiPort), apiserver.DefaultRetries, apiserver.DefaultTimeout)
apiProvider = apiserver.NewProvider(kubernetes.GetLocalhostOnPort(config.Config.Hub.PortForward.SrcPort), apiserver.DefaultRetries, apiserver.DefaultTimeout)
kubernetesProvider, err := getKubernetesProviderForCli()
if err != nil {
@ -102,6 +105,7 @@ func RunKubesharkTap() {
go goUtils.HandleExcWrapper(watchApiServerEvents, ctx, kubernetesProvider, cancel)
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel)
go goUtils.HandleExcWrapper(watchFrontPod, ctx, kubernetesProvider, cancel)
// block until exit signal or error
utils.WaitForFinish(ctx, cancel)
@ -261,8 +265,82 @@ func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
isPodReady = true
apiServerPodReady = true
postApiServerStarted(ctx, kubernetesProvider, cancel)
}
if !proxyDone && apiServerPodReady && frontPodReady {
proxyDone = true
postFrontStarted(ctx, kubernetesProvider, cancel)
}
case kubernetes.EventBookmark:
break
case kubernetes.EventError:
break
}
case err, ok := <-errorChan:
if !ok {
errorChan = nil
continue
}
logger.Log.Errorf("[ERROR] Agent creation, watching %v namespace, error: %v", config.Config.KubesharkResourcesNamespace, err)
cancel()
case <-timeAfter:
if !isPodReady {
logger.Log.Errorf(uiUtils.Error, "Kubeshark API server was not ready in time")
cancel()
}
case <-ctx.Done():
logger.Log.Debugf("Watching API Server pod loop, ctx done")
return
}
}
}
func watchFrontPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", "front"))
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.KubesharkResourcesNamespace}, podWatchHelper)
isPodReady := false
apiServerTimeoutSec := config.GetIntEnvConfig(config.ApiServerTimeoutSec, 120)
timeAfter := time.After(time.Duration(apiServerTimeoutSec) * time.Second)
for {
select {
case wEvent, ok := <-eventChan:
if !ok {
eventChan = nil
continue
}
switch wEvent.Type {
case kubernetes.EventAdded:
logger.Log.Debugf("Watching API Server pod loop, added")
case kubernetes.EventDeleted:
logger.Log.Infof("%s removed", "front")
cancel()
return
case kubernetes.EventModified:
modifiedPod, err := wEvent.ToPod()
if err != nil {
logger.Log.Errorf(uiUtils.Error, err)
cancel()
continue
}
logger.Log.Debugf("Watching API Server pod loop, modified: %v, containers statuses: %v", modifiedPod.Status.Phase, modifiedPod.Status.ContainerStatuses)
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
isPodReady = true
frontPodReady = true
}
if !proxyDone && apiServerPodReady && frontPodReady {
proxyDone = true
postFrontStarted(ctx, kubernetesProvider, cancel)
}
case kubernetes.EventBookmark:
break
case kubernetes.EventError:
@ -341,14 +419,21 @@ func watchApiServerEvents(ctx context.Context, kubernetesProvider *kubernetes.Pr
}
func postApiServerStarted(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
startProxyReportErrorIfAny(kubernetesProvider, ctx, cancel, config.Config.Tap.GuiPort)
startProxyReportErrorIfAny(kubernetesProvider, ctx, cancel, "kubeshark-api-server", config.Config.Hub.PortForward.SrcPort, config.Config.Hub.PortForward.DstPort, "/echo")
if err := startTapperSyncer(ctx, cancel, kubernetesProvider, state.targetNamespaces, state.startTime); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error starting kubeshark tapper syncer: %v", errormessage.FormatError(err)))
cancel()
}
url := GetApiServerUrl(config.Config.Tap.GuiPort)
url := kubernetes.GetLocalhostOnPort(config.Config.Hub.PortForward.SrcPort)
logger.Log.Infof("API Server is available at %s", url)
}
func postFrontStarted(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
startProxyReportErrorIfAny(kubernetesProvider, ctx, cancel, "front", config.Config.Front.PortForward.SrcPort, config.Config.Front.PortForward.DstPort, "")
url := kubernetes.GetLocalhostOnPort(config.Config.Front.PortForward.SrcPort)
logger.Log.Infof("Kubeshark is available at %s", url)
if !config.Config.HeadlessMode {
uiUtils.OpenBrowser(url)

View File

@ -39,19 +39,19 @@ func runKubesharkView() {
return
}
url = GetApiServerUrl(config.Config.View.GuiPort)
url = kubernetes.GetLocalhostOnPort(config.Config.Front.PortForward.SrcPort)
response, err := http.Get(fmt.Sprintf("%s/", url))
if err == nil && response.StatusCode == 200 {
logger.Log.Infof("Found a running service %s and open port %d", kubernetes.ApiServerPodName, config.Config.View.GuiPort)
logger.Log.Infof("Found a running service %s and open port %d", kubernetes.ApiServerPodName, config.Config.Front.PortForward.SrcPort)
return
}
logger.Log.Infof("Establishing connection to k8s cluster...")
startProxyReportErrorIfAny(kubernetesProvider, ctx, cancel, config.Config.View.GuiPort)
startProxyReportErrorIfAny(kubernetesProvider, ctx, cancel, "front", config.Config.Front.PortForward.SrcPort, config.Config.Front.PortForward.DstPort, "")
}
apiServerProvider := apiserver.NewProvider(url, apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err := apiServerProvider.TestConnection(); err != nil {
if err := apiServerProvider.TestConnection(""); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", fsUtils.GetLogFilePath()))
return
}

View File

@ -32,6 +32,19 @@ var (
)
func InitConfig(cmd *cobra.Command) error {
Config.Hub = HubConfig{
PortForward{
8898,
80,
},
}
Config.Front = FrontConfig{
PortForward{
8899,
80,
},
}
cmdName = cmd.Name()
if err := defaults.Set(&Config); err != nil {

View File

@ -20,7 +20,42 @@ const (
KubeConfigPathConfigName = "kube-config-path"
)
type PortForward struct {
SrcPort uint16 `yaml:"src-port"`
DstPort uint16 `yaml:"dst-port"`
}
type HubConfig struct {
PortForward PortForward `yaml:"port-forward"`
}
type FrontConfig struct {
PortForward PortForward `yaml:"port-forward"`
}
func CreateDefaultConfig() ConfigStruct {
config := ConfigStruct{}
config.Hub = HubConfig{
PortForward{
8898,
80,
},
}
config.Front = FrontConfig{
PortForward{
8899,
80,
},
}
return config
}
type ConfigStruct struct {
Hub HubConfig `yaml:"hub"`
Front FrontConfig `yaml:"front"`
Tap configStructs.TapConfig `yaml:"tap"`
Check configStructs.CheckConfig `yaml:"check"`
Install configStructs.InstallConfig `yaml:"install"`

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/kubeshark/kubeshark/cli/config"
"github.com/kubeshark/kubeshark/cli/errormessage"
"github.com/kubeshark/kubeshark/cli/kubeshark"
"github.com/kubeshark/kubeshark/cli/uiUtils"
@ -56,7 +57,16 @@ func CreateTapKubesharkResources(ctx context.Context, kubernetesProvider *kubern
return kubesharkServiceAccountExists, err
}
_, err = kubernetesProvider.CreateService(ctx, kubesharkResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName)
if err := createFrontPod(ctx, kubernetesProvider, opts); err != nil {
return kubesharkServiceAccountExists, err
}
_, err = kubernetesProvider.CreateService(ctx, kubesharkResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName, 80, int32(config.Config.Hub.PortForward.DstPort), int32(config.Config.Hub.PortForward.SrcPort))
if err != nil {
return kubesharkServiceAccountExists, err
}
_, err = kubernetesProvider.CreateService(ctx, kubesharkResourcesNamespace, "front", "front", 80, int32(config.Config.Front.PortForward.DstPort), int32(config.Config.Front.PortForward.SrcPort))
if err != nil {
return kubesharkServiceAccountExists, err
}
@ -91,13 +101,25 @@ func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.P
}
func createKubesharkApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, opts *kubernetes.ApiServerOptions) error {
pod, err := kubernetesProvider.GetKubesharkApiServerPodObject(opts, false, "", false)
pod, err := kubernetesProvider.BuildApiServerPod(opts, false, "", false)
if err != nil {
return err
}
if _, err = kubernetesProvider.CreatePod(ctx, opts.Namespace, pod); err != nil {
return err
}
logger.Log.Debugf("Successfully created API server pod: %s", kubernetes.ApiServerPodName)
logger.Log.Infof("Successfully created pod: [%s]", pod.Name)
return nil
}
func createFrontPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, opts *kubernetes.ApiServerOptions) error {
pod, err := kubernetesProvider.BuildFrontPod(opts, false, "", false)
if err != nil {
return err
}
if _, err = kubernetesProvider.CreatePod(ctx, opts.Namespace, pod); err != nil {
return err
}
logger.Log.Infof("Successfully created pod: [%s]", pod.Name)
return nil
}

View File

@ -7,7 +7,7 @@ const (
ConfigDirPath = "/app/config/"
DataDirPath = "/app/data/"
ConfigFileName = "kubeshark-config.json"
DefaultApiServerPort = 8899
DefaultApiServerPort = 80
LogLevelEnvVar = "LOG_LEVEL"
KubesharkAgentImageRepo = "docker.io/kubeshark/kubeshark"
BasenineHost = "127.0.0.1"

View File

@ -184,7 +184,7 @@ type ApiServerOptions struct {
Profiler bool
}
func (provider *Provider) GetKubesharkApiServerPodObject(opts *ApiServerOptions, mountVolumeClaim bool, volumeClaimName string, createAuthContainer bool) (*core.Pod, error) {
func (provider *Provider) BuildApiServerPod(opts *ApiServerOptions, mountVolumeClaim bool, volumeClaimName string, createAuthContainer bool) (*core.Pod, error) {
configMapVolume := &core.ConfigMapVolumeSource{}
configMapVolume.Name = ConfigMapName
@ -400,11 +400,102 @@ func (provider *Provider) GetKubesharkApiServerPodObject(opts *ApiServerOptions,
return pod, nil
}
func (provider *Provider) BuildFrontPod(opts *ApiServerOptions, mountVolumeClaim bool, volumeClaimName string, createAuthContainer bool) (*core.Pod, error) {
configMapVolume := &core.ConfigMapVolumeSource{}
configMapVolume.Name = ConfigMapName
cpuLimit, err := resource.ParseQuantity(opts.Resources.CpuLimit)
if err != nil {
return nil, fmt.Errorf("invalid cpu limit for %s container", "front")
}
memLimit, err := resource.ParseQuantity(opts.Resources.MemoryLimit)
if err != nil {
return nil, fmt.Errorf("invalid memory limit for %s container", "front")
}
cpuRequests, err := resource.ParseQuantity(opts.Resources.CpuRequests)
if err != nil {
return nil, fmt.Errorf("invalid cpu request for %s container", "front")
}
memRequests, err := resource.ParseQuantity(opts.Resources.MemoryRequests)
if err != nil {
return nil, fmt.Errorf("invalid memory request for %s container", "front")
}
volumeMounts := []core.VolumeMount{}
volumes := []core.Volume{}
containers := []core.Container{
{
Name: "front",
Image: "kubeshark/front:test-amd64",
ImagePullPolicy: opts.ImagePullPolicy,
VolumeMounts: volumeMounts,
ReadinessProbe: &core.Probe{
FailureThreshold: 3,
ProbeHandler: core.ProbeHandler{
TCPSocket: &core.TCPSocketAction{
Port: intstr.Parse("80"),
},
},
PeriodSeconds: 1,
SuccessThreshold: 1,
TimeoutSeconds: 1,
},
Resources: core.ResourceRequirements{
Limits: core.ResourceList{
"cpu": cpuLimit,
"memory": memLimit,
},
Requests: core.ResourceList{
"cpu": cpuRequests,
"memory": memRequests,
},
},
Command: []string{"nginx"},
Args: []string{"-g", "daemon off;"},
WorkingDir: shared.DataDirPath,
},
}
pod := &core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "front",
Labels: map[string]string{
"app": "front",
LabelManagedBy: provider.managedBy,
LabelCreatedBy: provider.createdBy,
},
},
Spec: core.PodSpec{
Containers: containers,
Volumes: volumes,
DNSPolicy: core.DNSClusterFirstWithHostNet,
TerminationGracePeriodSeconds: new(int64),
Tolerations: []core.Toleration{
{
Operator: core.TolerationOpExists,
Effect: core.TaintEffectNoExecute,
},
{
Operator: core.TolerationOpExists,
Effect: core.TaintEffectNoSchedule,
},
},
},
}
//define the service account only when it exists to prevent pod crash
if opts.ServiceAccountName != "" {
pod.Spec.ServiceAccountName = opts.ServiceAccountName
}
return pod, nil
}
func (provider *Provider) CreatePod(ctx context.Context, namespace string, podSpec *core.Pod) (*core.Pod, error) {
return provider.clientSet.CoreV1().Pods(namespace).Create(ctx, podSpec, metav1.CreateOptions{})
}
func (provider *Provider) CreateService(ctx context.Context, namespace string, serviceName string, appLabelValue string) (*core.Service, error) {
func (provider *Provider) CreateService(ctx context.Context, namespace string, serviceName string, appLabelValue string, targetPort int, port int32, nodePort int32) (*core.Service, error) {
service := core.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
@ -414,7 +505,13 @@ func (provider *Provider) CreateService(ctx context.Context, namespace string, s
},
},
Spec: core.ServiceSpec{
Ports: []core.ServicePort{{TargetPort: intstr.FromInt(shared.DefaultApiServerPort), Port: 80, Name: "api"}},
Ports: []core.ServicePort{
{
Name: serviceName,
TargetPort: intstr.FromInt(targetPort),
Port: port,
},
},
Type: core.ServiceTypeClusterIP,
Selector: map[string]string{"app": appLabelValue},
},

View File

@ -11,7 +11,6 @@ import (
"strings"
"time"
"github.com/kubeshark/kubeshark/shared"
"k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
@ -23,8 +22,8 @@ import (
const k8sProxyApiPrefix = "/"
const kubesharkServicePort = 80
func StartProxy(kubernetesProvider *Provider, proxyHost string, kubesharkPort uint16, kubesharkNamespace string, kubesharkServiceName string, cancel context.CancelFunc) (*http.Server, error) {
logger.Log.Debugf("Starting proxy using proxy method. namespace: [%v], service name: [%s], port: [%v]", kubesharkNamespace, kubesharkServiceName, kubesharkPort)
func StartProxy(kubernetesProvider *Provider, proxyHost string, srcPort uint16, dstPort uint16, kubesharkNamespace string, kubesharkServiceName string, cancel context.CancelFunc) (*http.Server, error) {
logger.Log.Infof("Starting proxy - namespace: [%v], service name: [%s], port: [%d:%d]\n", kubesharkNamespace, kubesharkServiceName, srcPort, dstPort)
filter := &proxy.FilterServer{
AcceptPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathAcceptRE),
RejectPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathRejectRE),
@ -40,7 +39,7 @@ func StartProxy(kubernetesProvider *Provider, proxyHost string, kubesharkPort ui
mux.Handle(k8sProxyApiPrefix, getRerouteHttpHandlerKubesharkAPI(proxyHandler, kubesharkNamespace, kubesharkServiceName))
mux.Handle("/static/", getRerouteHttpHandlerKubesharkStatic(proxyHandler, kubesharkNamespace, kubesharkServiceName))
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", proxyHost, int(kubesharkPort)))
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", proxyHost, int(srcPort)))
if err != nil {
return nil, err
}
@ -63,12 +62,22 @@ func getKubesharkApiServerProxiedHostAndPath(kubesharkNamespace string, kubeshar
return fmt.Sprintf("/api/v1/namespaces/%s/services/%s:%d/proxy", kubesharkNamespace, kubesharkServiceName, kubesharkServicePort)
}
func GetKubesharkApiServerProxiedHostAndPath(kubesharkPort uint16) string {
return fmt.Sprintf("localhost:%d", kubesharkPort)
func GetLocalhostOnPort(port uint16) string {
return fmt.Sprintf("http://localhost:%d", port)
}
func getRerouteHttpHandlerKubesharkAPI(proxyHandler http.Handler, kubesharkNamespace string, kubesharkServiceName string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, x-session-token")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
proxiedPath := getKubesharkApiServerProxiedHostAndPath(kubesharkNamespace, kubesharkServiceName)
//avoid redirecting several times
@ -86,7 +95,7 @@ func getRerouteHttpHandlerKubesharkStatic(proxyHandler http.Handler, kubesharkNa
})
}
func NewPortForward(kubernetesProvider *Provider, namespace string, podRegex *regexp.Regexp, localPort uint16, ctx context.Context, cancel context.CancelFunc) (*portforward.PortForwarder, error) {
func NewPortForward(kubernetesProvider *Provider, namespace string, podRegex *regexp.Regexp, srcPort uint16, dstPort uint16, ctx context.Context, cancel context.CancelFunc) (*portforward.PortForwarder, error) {
pods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, podRegex, []string{namespace})
if err != nil {
return nil, err
@ -96,7 +105,7 @@ func NewPortForward(kubernetesProvider *Provider, namespace string, podRegex *re
podName := pods[0].Name
logger.Log.Debugf("Starting proxy using port-forward method. namespace: [%v], pod name: [%s], port: [%v]", namespace, podName, localPort)
logger.Log.Debugf("Starting proxy using port-forward method. namespace: [%v], pod name: [%s], %d:%d", namespace, podName, srcPort, dstPort)
dialer, err := getHttpDialer(kubernetesProvider, namespace, podName)
if err != nil {
@ -106,7 +115,7 @@ func NewPortForward(kubernetesProvider *Provider, namespace string, podRegex *re
stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1)
out, errOut := new(bytes.Buffer), new(bytes.Buffer)
forwarder, err := portforward.New(dialer, []string{fmt.Sprintf("%d:%d", localPort, shared.DefaultApiServerPort)}, stopChan, readyChan, out, errOut)
forwarder, err := portforward.New(dialer, []string{fmt.Sprintf("%d:%d", srcPort, dstPort)}, stopChan, readyChan, out, errOut)
if err != nil {
return nil, err
}

View File

@ -1,2 +0,0 @@
REACT_APP_OVERRIDE_WS_URL="ws://localhost:8899/ws"
REACT_APP_OVERRIDE_API_URL="http://localhost:8899/"

View File

@ -1,5 +0,0 @@
build/
dist/
node_modules/
.snapshots/
*.min.js

View File

@ -1,10 +0,0 @@
{
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"tabWidth": 2,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"trailingComma": "none"
}

138
ui/.snyk
View File

@ -1,138 +0,0 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.14.0
ignore:
SNYK-JS-AXIOS-1579269:
- '*':
reason: None Given
SNYK-JS-TRIMNEWLINES-1298042:
- '*':
reason: None Given
SNYK-JS-ANSIHTML-1296849:
- '*':
reason: None Given
SNYK-JS-ANSIREGEX-1583908:
- '*':
reason: None Given
SNYK-JS-BROWSERSLIST-1090194:
- '*':
reason: None Given
SNYK-JS-CSSWHAT-1298035:
- '*':
reason: None Given
SNYK-JS-DNSPACKET-1293563:
- '*':
reason: None Given
SNYK-JS-EJS-1049328:
- '*':
reason: None Given
SNYK-JS-GLOBPARENT-1016905:
- '*':
reason: None Given
SNYK-JS-IMMER-1540542:
- '*':
reason: None Given
SNYK-JS-LODASHTEMPLATE-1088054:
- '*':
reason: None Given
SNYK-JS-NODESASS-1059081:
- '*':
reason: None Given
SNYK-JS-NODESASS-535498:
- '*':
reason: None Given
SNYK-JS-NODESASS-535500:
- '*':
reason: None Given
SNYK-JS-NODESASS-535502:
- '*':
reason: None Given
SNYK-JS-NODESASS-540956:
- '*':
reason: Non given
SNYK-JS-NODESASS-540958:
- '*':
reason: Non given
SNYK-JS-NODESASS-540964:
- '*':
reason: Non given
SNYK-JS-NODESASS-540978:
- '*':
reason: Non given
SNYK-JS-NODESASS-540980:
- '*':
reason: Non given
SNYK-JS-NODESASS-540990:
- '*':
reason: Non given
SNYK-JS-NODESASS-540992:
- '*':
reason: Non given
SNYK-JS-NODESASS-540994:
- '*':
reason: Non given
SNYK-JS-NODESASS-540996:
- '*':
reason: Non given
SNYK-JS-NODESASS-540998:
- '*':
reason: Non given
SNYK-JS-NODESASS-541000:
- '*':
reason: Non given
SNYK-JS-NODESASS-541002:
- '*':
reason: Non given
SNYK-JS-NTHCHECK-1586032:
- '*':
reason: Non given
SNYK-JS-PATHPARSE-1077067:
- '*':
reason: Non given
SNYK-JS-POSTCSS-1090595:
- '*':
reason: Non given
SNYK-JS-POSTCSS-1255640:
- '*':
reason: Non given
SNYK-JS-PRISMJS-1314893:
- '*':
reason: Non given
SNYK-JS-PRISMJS-1585202:
- '*':
reason: Non given
SNYK-JS-PROMPTS-1729737:
- '*':
reason: Non given
SNYK-JS-SHELLQUOTE-1766506:
- '*':
reason: Non given
SNYK-JS-TAR-1536528:
- '*':
reason: Non given
SNYK-JS-TAR-1536531:
- '*':
reason: Non given
SNYK-JS-TAR-1536758:
- '*':
reason: Non given
SNYK-JS-TAR-1579147:
- '*':
reason: Non given
SNYK-JS-TAR-1579152:
- '*':
reason: Non given
SNYK-JS-TAR-1579155:
- '*':
reason: Non given
SNYK-JS-TMPL-1583443:
- '*':
reason: Non given
SNYK-JS-URLPARSE-1533425:
- '*':
reason: Non given
SNYK-JS-WS-1296835:
- '*':
reason: Non given
SNYK-JS-JSONSCHEMA-1920922:
- '*':
reason: Non given

View File

@ -1,4 +0,0 @@
language: node_js
node_js:
- 12
- 10

View File

@ -1,22 +0,0 @@
// this workaround fix a warning of mini-css-extract-plugin throws "Conflicting order" during build
// https://github.com/facebook/create-react-app/issues/5372
const path = require("path")
module.exports = {
webpack: {
configure: (webpackConfig) => {
const instanceOfMiniCssExtractPlugin = webpackConfig.plugins.find(
(plugin) => plugin.options && plugin.options.ignoreOrder != null,
);
if(instanceOfMiniCssExtractPlugin)
instanceOfMiniCssExtractPlugin.options.ignoreOrder = true;
webpackConfig.resolve.alias['react']= path.resolve(__dirname, 'node_modules/react'); // solve 2 react instances
webpackConfig.resolve.alias['@emotion/react']= path.resolve("node_modules", "@emotion/react");
webpackConfig.resolve.alias['@mui/styles']= path.resolve("node_modules", "@mui/styles");
return webpackConfig;
}
}
}

62001
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,109 +0,0 @@
{
"name": "kubeshark",
"version": "0.0.0",
"description": "Made with create-react-library",
"author": "",
"license": "MIT",
"repository": "/kubeshark-common",
"main": "dist/index.js",
"module": "dist/index.modern.js",
"source": "src/index.tsx",
"engines": {
"node": ">=10"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject",
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings=0"
},
"peerDependencies": {
"@craco/craco": "^6.4.3",
"@types/jest": "^26.0.24",
"@types/node": "^12.20.54",
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^17.0.2",
"recoil": "^0.7.2",
"sass": "^1.52.3"
},
"dependencies": {
"@craco/craco": "^6.4.3",
"@elastic/eui": "^60.2.0",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.8.2",
"@mui/lab": "^5.0.0-alpha.84",
"@mui/material": "^5.8.2",
"@mui/styles": "^5.8.0",
"@types/lodash": "^4.14.182",
"@uiw/react-textarea-code-editor": "^1.6.0",
"ace-builds": "^1.6.0",
"axios": "^0.27.2",
"core-js": "^3.22.7",
"highlight.js": "^11.5.1",
"json-beautify": "^1.1.1",
"jsonpath": "^1.1.1",
"marked": "^4.0.16",
"material-ui-popup-state": "^2.0.1",
"mobx": "^6.6.0",
"moment": "^2.29.3",
"node-fetch": "^3.2.4",
"numeral": "^2.0.6",
"protobuf-decoder": "^0.1.2",
"react-ace": "^9.0.0",
"react-graph-vis": "^1.0.7",
"react-lowlight": "^3.0.0",
"react-router-dom": "^6.3.0",
"react-scrollable-feed-virtualized": "^1.4.9",
"react-syntax-highlighter": "^15.5.0",
"react-toastify": "^8.2.0",
"recharts": "^2.1.10",
"redoc": "^2.0.0-rc.71",
"styled-components": "^5.3.5",
"use-file-picker": "^1.4.2",
"web-vitals": "^2.1.4",
"xml-formatter": "^2.6.1"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^13.3.0",
"@svgr/rollup": "^6.2.1",
"@types/ace": "^0.0.48",
"cross-env": "^7.0.3",
"env-cmd": "^10.1.0",
"gh-pages": "^4.0.0",
"microbundle-crl": "^0.13.11",
"npm-run-all": "^4.1.5",
"prettier": "^2.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rollup-plugin-import-css": "^3.0.3",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-sass": "^1.2.12",
"rollup-plugin-scss": "^3.0.0",
"typescript": "^4.5.3"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"files": [
"src/*.scss",
"dist"
],
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@ -1,62 +0,0 @@
<svg width="32" height="32" viewBox="0 0 87.547439 84.530304" fill="none" xmlns="http://www.w3.org/2000/svg">
<g
id="Layer_2"
transform="translate(0.09743584)">
<g
id="Layer_1-2">
<path
style="fill:#326de6"
d="M 43.697266 0 C 42.897266 0 41.997266 -0.00078125 41.197266 0.19921875 L 10.697266 14.900391 C 9.1972608 15.600391 8.0972656 16.899609 7.6972656 18.599609 L 0.09765625 51.5 C -0.20234375 53.2 0.19726587 54.900781 1.1972656 56.300781 L 22.296875 82.400391 C 23.496875 83.600391 25.196484 84.4 26.896484 84.5 L 60.597656 84.5 C 62.397656 84.7 64.097266 83.900391 65.197266 82.400391 L 86.296875 56.300781 C 87.296875 54.900781 87.698047 53.2 87.498047 51.5 L 79.896484 18.800781 C 79.396484 17.200781 78.197266 15.899609 76.697266 15.099609 L 46.197266 0.5 C 45.397266 0.1 44.497266 0 43.697266 0 z M 66.515625 15.484375 A 1.4619351 1.4619351 0 0 1 68.019531 16.947266 A 2.9127558 2.9127558 0 0 1 67.322266 18.832031 A 16.523949 16.523949 0 0 0 64.300781 24.134766 A 6.6495148 6.6495148 0 0 1 61.210938 27.787109 C 55.292678 31.042497 50.023438 33.441406 50.023438 33.441406 C 51.547517 33.407901 52.888258 33.539943 54.078125 33.78125 L 54.794922 32.212891 A 0.26693874 0.26693874 0 0 1 55.302734 32.298828 L 55.462891 34.126953 C 55.704079 34.200806 55.934194 34.280377 56.160156 34.363281 L 57.441406 32.927734 A 0.26693874 0.26693874 0 0 1 57.902344 33.162109 L 57.511719 34.970703 C 57.70188 35.067399 57.867852 35.173517 58.044922 35.275391 L 59.410156 33.9375 A 0.26693874 0.26693874 0 0 1 59.853516 34.201172 L 59.308594 36.103516 C 59.440078 36.203833 59.557825 36.304741 59.679688 36.40625 L 61.484375 35.480469 A 0.26693874 0.26693874 0 0 1 61.828125 35.865234 L 60.775391 37.470703 C 60.969304 37.684973 61.146122 37.893588 61.304688 38.095703 L 63.212891 37.695312 A 0.26693874 0.26693874 0 0 1 63.443359 38.15625 L 62.115234 39.328125 C 62.15577 39.402437 62.230824 39.511093 62.261719 39.574219 A 0.4464948 0.4464948 0 0 1 62.029297 40.183594 A 48.930273 48.930273 0 0 0 51.714844 46.09375 C 49.086166 47.966168 46.207502 49.350532 43.730469 50.332031 C 43.076373 54.285127 40.473796 56.885623 39.478516 57.75 A 0.87976722 0.87976722 0 0 1 38.634766 57.923828 A 0.87995885 0.87995885 0 0 1 38.033203 56.925781 C 38.437075 54.75056 38.408972 53.11241 38.332031 52.101562 C 38.290797 52.112159 38.202408 52.138735 38.164062 52.148438 A 13.074058 13.074058 0 0 0 35.507812 53.115234 A 11.895733 11.895733 0 0 0 34.857422 53.480469 A 11.838628 11.838628 0 0 0 34.330078 53.794922 A 11.895733 11.895733 0 0 0 33.837891 54.140625 A 11.838628 11.838628 0 0 0 33.228516 54.59375 C 32.991842 54.790181 32.768649 54.997504 32.558594 55.212891 C 32.369385 55.406902 32.194752 55.609111 32.027344 55.816406 C 31.508815 57.117507 31.306818 58.67221 32.125 60.228516 A 5.1377563 5.1377563 0 0 0 35.498047 62.789062 A 26.202921 26.202921 0 0 0 41.576172 63.449219 A 4.1583421 4.1583421 0 0 0 44.960938 61.724609 C 46.624465 59.405902 50.130251 55.252983 54.955078 53.447266 A 34.752434 34.752434 0 0 0 60.173828 51.050781 C 61.569843 50.242875 63.193231 51.655259 62.607422 53.158203 A 7.7023228 7.7023228 0 0 1 60.019531 56.652344 C 56.332021 59.349134 52.257813 62.816608 52.257812 66.779297 C 52.257812 66.779297 51.966213 71.210845 55.501953 73.949219 A 1.1662521 1.1662521 0 0 1 54.792969 76.033203 C 51.436784 76.075553 46.506377 74.963299 42.957031 69.091797 A 22.909207 22.909207 0 0 1 38.742188 70.107422 L 38.738281 70.107422 C 35.66417 70.582412 32.957403 70.666419 30.652344 70.337891 L 30.65625 70.347656 C 30.65625 70.347656 14.041675 69.733055 14.634766 53.652344 C 14.811605 48.857623 16.281456 44.724826 18.251953 41.277344 C 18.16014 39.986781 17.79688 38.65623 17.28125 37.388672 A 1.4696003 1.4696003 0 0 1 17.421875 36.001953 L 19.126953 33.505859 A 0.15886017 0.15886017 0 0 0 18.912109 33.28125 L 16.152344 35.037109 C 15.659283 34.137796 15.146296 33.32412 14.689453 32.648438 A 2.1703863 2.1703863 0 0 1 15.625 29.427734 C 18.291513 28.306515 21.524198 27.723456 25.607422 28.181641 A 36.480542 36.480542 0 0 0 31.316406 28.324219 C 48.430863 18.814384 61.805489 16.163693 66.345703 15.5 A 1.4619351 1.4619351 0 0 1 66.515625 15.484375 z "
transform="translate(-0.09743584)"
id="path6" />
<g
id="g857"
transform="matrix(0.19162867,0,0,0.19162867,8.490619,13.182287)"><g
id="g849"
style="fill:#485a75"><path
id="path839"
data-original="#485a75"
d="m 192.963,97.984 c -4.249,-8.947 -6.27,-19.006 -6.563,-29.928 a 1.414,1.414 0 0 0 -2.736,-0.449 55.314,55.314 0 0 0 -2.656,9.6 21.038,21.038 0 0 0 3.958,16.393 35.833,35.833 0 0 0 5.808,6.066 1.419,1.419 0 0 0 2.189,-1.682 z"
style="fill:#485a75" /><path
id="path841"
data-original="#485a75"
d="m 177.85,105.342 c -4.249,-8.947 -6.27,-19.006 -6.563,-29.928 a 1.414,1.414 0 0 0 -2.736,-0.449 55.314,55.314 0 0 0 -2.656,9.6 21.038,21.038 0 0 0 3.958,16.394 35.72,35.72 0 0 0 5.808,6.066 1.419,1.419 0 0 0 2.189,-1.683 z"
style="fill:#485a75" /><path
id="path843"
data-original="#485a75"
d="m 163.212,112.168 c -4.249,-8.947 -6.27,-19.006 -6.563,-29.928 a 1.414,1.414 0 0 0 -2.736,-0.449 55.314,55.314 0 0 0 -2.656,9.6 21.038,21.038 0 0 0 3.958,16.394 35.718,35.718 0 0 0 5.808,6.066 1.419,1.419 0 0 0 2.189,-1.683 z"
style="fill:#485a75" /><path
id="path845"
data-original="#485a75"
d="m 282.539,41.775 a 3.464,3.464 0 0 1 -2.442,-5.922 l 2.594,-2.577 a 3.4644893,3.4644893 0 1 1 4.883,4.916 l -2.594,2.577 a 3.455,3.455 0 0 1 -2.441,1.006 z"
style="fill:#485a75" /><circle
id="circle847"
data-original="#485a75"
r="5.0100002"
cy="49.93"
cx="242.799"
style="fill:#485a75" /></g></g><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 39.03763,34.543061 c -1.527793,-1.496038 -1.827598,-2.980669 -1.058708,-5.242707 0.127475,-0.375025 0.218266,-0.50615 0.350457,-0.50615 0.152165,0 0.188106,0.09647 0.244306,0.655726 0.216618,2.155597 0.449111,3.24366 0.975953,4.567454 0.461821,1.160417 0.302867,1.323615 -0.512008,0.525677 z"
id="path900"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.006337,33.406311 c -1.337815,-1.184989 -1.866757,-2.629191 -1.538123,-4.199632 0.240017,-1.146966 0.464694,-1.723928 0.67132,-1.723928 0.159683,0 0.190274,0.122336 0.292808,1.17094 0.16981,1.736629 0.586255,3.459239 1.068721,4.420732 0.174454,0.347664 0.153619,0.638924 -0.04552,0.636332 -0.05889,-7.68e-4 -0.261033,-0.137766 -0.449207,-0.304444 z"
id="path902"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 44.593492,31.684332 c -1.06996,-1.096616 -1.495754,-2.317349 -1.27225,-3.647492 0.06476,-0.385406 0.209609,-0.983895 0.321888,-1.329977 0.160604,-0.495034 0.243576,-0.629239 0.389027,-0.629239 0.167866,0 0.195552,0.106755 0.300777,1.15978 0.178804,1.789343 0.514135,3.175579 1.092636,4.516885 0.109635,0.254197 0.118901,0.363706 0.03765,0.444957 -0.173562,0.173562 -0.231396,0.139321 -0.869728,-0.514914 z"
id="path904"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 54.796786,23.600645 c -0.300846,-0.126947 -0.598408,-0.559802 -0.598408,-0.870489 0,-0.192938 0.100882,-0.378642 0.330349,-0.608109 0.289083,-0.289083 0.372038,-0.323583 0.664085,-0.276191 0.411876,0.06684 0.590137,0.209069 0.754324,0.601856 0.107229,0.256525 0.107229,0.352363 0,0.608889 -0.06999,0.167444 -0.190498,0.356886 -0.267791,0.420982 -0.193757,0.160673 -0.644482,0.223522 -0.882559,0.123062 z"
id="path906"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 62.366634,20.995747 c -0.30085,-0.260136 -0.326767,-0.517366 -0.08785,-0.871899 0.220552,-0.327277 0.758517,-0.743997 0.960464,-0.743997 0.06271,0 0.225096,0.08737 0.360847,0.194149 0.379108,0.298207 0.323778,0.598735 -0.208173,1.130685 -0.485833,0.485834 -0.722376,0.552984 -1.025291,0.291062 z"
id="path908"
transform="translate(-0.09743584)" /></g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -1,53 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Work+Sans:400,300,500,700,900,800,600">
<title>Kubeshark</title>
<script>
try {
// Injected from server
window.isOasEnabled = __IS_OAS_ENABLED__
window.isServiceMapEnabled = __IS_SERVICE_MAP_ENABLED__
}
catch (e) {
}
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -1,9 +0,0 @@
@import './variables.module'
body
background-color: $main-background-color
.kubesharkApp
color: $font-color
width: 100%
height: 100%

View File

@ -1,46 +0,0 @@
import './App.sass';
import { Header } from "./components/Header/Header";
import { TrafficPage } from "./components/Pages/TrafficPage/TrafficPage";
import { ServiceMapModal } from './components/modals/ServiceMapModal/ServiceMapModal';
import { useRecoilState } from "recoil";
import serviceMapModalOpenAtom from "./recoil/serviceMapModalOpen";
import oasModalOpenAtom from './recoil/oasModalOpen/atom';
import trafficStatsModalOpenAtom from "./recoil/trafficStatsModalOpen";
import { OasModal } from './components/modals/OasModal/OasModal';
import Api from './helpers/api';
import { ThemeProvider, StyledEngineProvider, createTheme } from '@mui/material';
import { TrafficStatsModal } from './components/modals/TrafficStatsModal/TrafficStatsModal';
const api = Api.getInstance()
const App = () => {
const [serviceMapModalOpen, setServiceMapModalOpen] = useRecoilState(serviceMapModalOpenAtom);
const [oasModalOpen, setOasModalOpen] = useRecoilState(oasModalOpenAtom)
const [trafficStatsModalOpen, setTrafficStatsModalOpen] = useRecoilState(trafficStatsModalOpenAtom);
return (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={createTheme(({}))}>
<div className="kubesharkApp">
<Header />
<TrafficPage />
{window["isServiceMapEnabled"] && <ServiceMapModal
isOpen={serviceMapModalOpen}
onOpen={() => setServiceMapModalOpen(true)}
onClose={() => setServiceMapModalOpen(false)}
getServiceMapDataApi={api.serviceMapData} />}
{window["isOasEnabled"] && <OasModal
getOasServices={api.getOasServices}
getOasByService={api.getOasByService}
openModal={oasModalOpen}
handleCloseModal={() => setOasModalOpen(false)}
/>}
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getTrafficStatsDataApi={api.getTrafficStats} />
</div>
</ThemeProvider>
</StyledEngineProvider>
);
}
export default App;

View File

@ -1,22 +0,0 @@
.subSectionHeader{
position: relative;
font-style: normal;
font-weight: bold;
font-size: 12px;
line-height: 15px;
color: $font-color;
&::after{
content: "";
border: 1px solid #E9EBF8;
transform: rotate(180deg);
position: absolute;
left: 0px;
right: -100%;
top: 100%;
bottom: 0%;
width: 100%;
width: -moz-available;
width: -webkit-fill-available;
width: stretch;
}
}

View File

@ -1,13 +0,0 @@
.authPresentationContainer
display: flex
border-left: 2px #87878759 solid
padding-left: 10px
margin-left: 10px
color: rgba(0,0,0,0.75)
.authEmail
font-weight: 600
font-size: 13px
.authModel
font-size: 11px

View File

@ -1,30 +0,0 @@
import React, {useEffect, useState} from "react";
import Api from "../../helpers/api";
import './AuthPresentation.sass';
const api = Api.getInstance();
export const AuthPresentation = () => {
const [statusAuth, setStatusAuth] = useState(null);
useEffect(() => {
(async () => {
try {
const auth = await api.getAuthStatus();
setStatusAuth(auth);
} catch (e) {
console.error(e);
}
})();
}, []);
return <>
{statusAuth?.email && <div className="authPresentationContainer">
<div>
<div className="authEmail">{statusAuth.email}</div>
<div className="authModel">{statusAuth.model}</div>
</div>
</div>}
</>;
}

View File

@ -1,81 +0,0 @@
@import "../../variables.module"
.list
overflow: scroll
display: flex
flex-grow: 1
flex-direction: column
justify-content: space-between
position: relative
.container
position: relative
display: flex
flex-direction: column
overflow: hidden
flex-grow: 1
.footer
display: flex
justify-content: space-between
border-top: 1px solid #BCC6DD
align-items: center
padding-top: 10px
margin-right: 15px
.styledButton
cursor: pointer
line-height: 1
border-radius: 20px
letter-spacing: .02857em
color: #627ef7
border: 1px solid rgba(98, 126, 247, 0.5)
padding: 5px 18px
transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms
font-weight: 600
.styledButton:hover
border: 1px solid #627ef7
background-color: rgba(255, 255, 255, 0.06)
.spinnerContainer
display: flex
justify-content: center
margin-bottom: 10px
.noMoreDataAvailable
text-align: center
font-weight: 600
color: $secondary-font-color
.btnOld
position: absolute
top: 20px
right: 10px
background: #205CF5
border-radius: 50%
height: 35px
width: 35px
border: none
cursor: pointer
z-index: 1
img
height: 10px
transform: scaleY(-1)
.btnLive
position: absolute
bottom: 10px
right: 10px
background: #205CF5
border-radius: 50%
height: 35px
width: 35px
border: none
cursor: pointer
img
height: 10px
.hideButton
display: none
.showButton
display: block

View File

@ -1,220 +0,0 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import styles from './EntriesList.module.sass';
import ScrollableFeedVirtualized from "react-scrollable-feed-virtualized";
import { EntryItem } from "../EntryListItem/EntryListItem";
import down from "./assets/downImg.svg";
import spinner from "./assets/spinner.svg";
import { RecoilState, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import entriesAtom from "../../recoil/entries";
import queryAtom from "../../recoil/query";
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
import TrafficViewerApi from "../TrafficViewer/TrafficViewerApi";
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
import { toast } from "react-toastify";
import { MAX_ENTRIES, TOAST_CONTAINER_ID } from "../../configs/Consts";
import tappingStatusAtom from "../../recoil/tappingStatus";
import leftOffTopAtom from "../../recoil/leftOffTop";
import Moment from "moment";
interface EntriesListProps {
listEntryREF: any;
onSnapBrokenEvent: () => void;
isSnappedToBottom: boolean;
setIsSnappedToBottom: any;
noMoreDataTop: boolean;
setNoMoreDataTop: (flag: boolean) => void;
openWebSocket: (leftOff: string, query: string, resetEntries: boolean, fetch: number, fetchTimeoutMs: number) => void;
scrollableRef: any;
ws: any;
}
export const EntriesList: React.FC<EntriesListProps> = ({
listEntryREF,
onSnapBrokenEvent,
isSnappedToBottom,
setIsSnappedToBottom,
noMoreDataTop,
setNoMoreDataTop,
openWebSocket,
scrollableRef,
ws
}) => {
const [entries, setEntries] = useRecoilState(entriesAtom);
const query = useRecoilValue(queryAtom);
const isWsConnectionClosed = ws?.current?.readyState !== WebSocket.OPEN;
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
const [leftOffTop, setLeftOffTop] = useRecoilState(leftOffTopAtom);
const setTappingStatus = useSetRecoilState(tappingStatusAtom);
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
const [loadMoreTop, setLoadMoreTop] = useState(false);
const [isLoadingTop, setIsLoadingTop] = useState(false);
const [queriedTotal, setQueriedTotal] = useState(0);
const [startTime, setStartTime] = useState(0);
const [truncatedTimestamp, setTruncatedTimestamp] = useState(0);
const leftOffBottom = entries.length > 0 ? entries[entries.length - 1].id : "latest";
useEffect(() => {
const list = document.getElementById('list').firstElementChild;
list.addEventListener('scroll', (e) => {
const el: any = e.target;
if (el.scrollTop === 0) {
setLoadMoreTop(true);
} else {
setNoMoreDataTop(false);
setLoadMoreTop(false);
}
});
}, [setLoadMoreTop, setNoMoreDataTop]);
const memoizedEntries = useMemo(() => {
return entries;
}, [entries]);
const getOldEntries = useCallback(async () => {
setLoadMoreTop(false);
if (leftOffTop === "") {
return;
}
setIsLoadingTop(true);
const data = await trafficViewerApi.fetchEntries(leftOffTop, -1, query, 100, 3000);
if (!data || data.data === null || data.meta === null) {
setNoMoreDataTop(true);
setIsLoadingTop(false);
return;
}
setLeftOffTop(data.meta.leftOff);
let scrollTo: boolean;
if (data.meta.noMoreData) {
setNoMoreDataTop(true);
scrollTo = false;
} else {
scrollTo = true;
}
setIsLoadingTop(false);
const newEntries = [...data.data.reverse(), ...entries];
if(newEntries.length > MAX_ENTRIES) {
newEntries.splice(MAX_ENTRIES, newEntries.length - MAX_ENTRIES)
}
setEntries(newEntries);
setQueriedTotal(data.meta.total);
setTruncatedTimestamp(data.meta.truncatedTimestamp);
if (scrollTo) {
scrollableRef.current.scrollToIndex(data.data.length - 1);
}
}, [setLoadMoreTop, setIsLoadingTop, entries, setEntries, query, setNoMoreDataTop, leftOffTop, setLeftOffTop, setQueriedTotal, setTruncatedTimestamp, scrollableRef, trafficViewerApi]);
useEffect(() => {
if (!isWsConnectionClosed || !loadMoreTop || noMoreDataTop) return;
getOldEntries();
}, [loadMoreTop, noMoreDataTop, getOldEntries, isWsConnectionClosed]);
const scrollbarVisible = scrollableRef.current?.childWrapperRef.current.clientHeight > scrollableRef.current?.wrapperRef.current.clientHeight;
useEffect(() => {
if (!focusedEntryId && entries.length > 0)
setFocusedEntryId(entries[0].id);
}, [focusedEntryId, entries, setFocusedEntryId])
useEffect(() => {
const newEntries = [...entries];
if (newEntries.length > MAX_ENTRIES) {
setLeftOffTop(newEntries[0].id);
newEntries.splice(0, newEntries.length - MAX_ENTRIES)
setNoMoreDataTop(false);
setEntries(newEntries);
}
}, [entries, setLeftOffTop, setNoMoreDataTop, setEntries])
if(ws.current && !ws.current.onmessage) {
ws.current.onmessage = (e) => {
if (!e?.data) return;
const message = JSON.parse(e.data);
switch (message.messageType) {
case "entry":
setEntries(entriesState => [...entriesState, message.data]);
break;
case "status":
setTappingStatus(message.tappingStatus);
break;
case "toast":
toast[message.data.type](message.data.text, {
theme: "colored",
autoClose: message.data.autoClose,
pauseOnHover: true,
progress: undefined,
containerId: TOAST_CONTAINER_ID
});
break;
case "queryMetadata":
setTruncatedTimestamp(message.data.truncatedTimestamp);
setQueriedTotal(message.data.total);
setLeftOffTop(leftOffState => leftOffState === "" ? message.data.leftOff : leftOffState);
break;
case "startTime":
setStartTime(message.data);
break;
}
}
}
return <React.Fragment>
<div className={styles.list}>
<div id="list" ref={listEntryREF} className={styles.list}>
{isLoadingTop && <div className={styles.spinnerContainer}>
<img alt="spinner" src={spinner} style={{height: 25}}/>
</div>}
{noMoreDataTop && <div id="noMoreDataTop" className={styles.noMoreDataAvailable}>No more data available</div>}
<ScrollableFeedVirtualized ref={scrollableRef} itemHeight={48} marginTop={10} onSnapBroken={onSnapBrokenEvent}>
{false /* It's because the first child is ignored by ScrollableFeedVirtualized */}
{memoizedEntries.map(entry => <EntryItem
key={`entry-${entry.id}`}
entry={entry}
style={{}}
headingMode={false}
/>)}
</ScrollableFeedVirtualized>
<button type="button"
title="Fetch old records"
className={`${styles.btnOld} ${!scrollbarVisible && leftOffTop !== "" ? styles.showButton : styles.hideButton}`}
onClick={(_) => {
trafficViewerApi.webSocket.close()
getOldEntries();
}}>
<img alt="down" src={down}/>
</button>
<button type="button"
title="Snap to bottom"
className={`${styles.btnLive} ${isSnappedToBottom && !isWsConnectionClosed ? styles.hideButton : styles.showButton}`}
onClick={(_) => {
if (isWsConnectionClosed) {
openWebSocket(leftOffBottom, query, false, 0, 0);
}
scrollableRef.current.jumpToBottom();
setIsSnappedToBottom(true);
}}>
<img alt="down" src={down}/>
</button>
</div>
<div className={styles.footer}>
<div>Displaying <b id="entries-length">{entries?.length > MAX_ENTRIES ? MAX_ENTRIES : entries?.length}</b> results out of <b
id="total-entries">{queriedTotal}</b> total
</div>
{startTime !== 0 && <div>First traffic entry time <span style={{
marginRight: 5,
fontWeight: 600,
fontSize: 13
}}>{Moment(truncatedTimestamp ? truncatedTimestamp : startTime).utc().format('MM/DD/YYYY, h:mm:ss.SSS A')}</span>
</div>}
</div>
</div>
</React.Fragment>;
};

View File

@ -1,3 +0,0 @@
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 2.82846L7.82843 3.6478e-05L9.24264 1.41425L5 5.65689L4.99997 5.65686L3.58579 4.24268L0.75733 1.41422L2.17154 5.00679e-06L5 2.82846Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 301 B

View File

@ -1,6 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 673 B

View File

@ -1,145 +0,0 @@
import React, { useEffect, useState } from "react";
import EntryViewer from "./EntryViewer/EntryViewer";
import { EntryItem } from "../EntryListItem/EntryListItem";
import makeStyles from '@mui/styles/makeStyles';
import Protocol from "../UI/Protocol/Protocol"
import Queryable from "../UI/Queryable/Queryable";
import { toast } from "react-toastify";
import { RecoilState, useRecoilState, useRecoilValue } from "recoil";
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
import TrafficViewerApi from "../TrafficViewer/TrafficViewerApi";
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 entryDataAtom from "../../recoil/entryData";
import { LoadingWrapper } from "../UI/withLoading/withLoading";
const useStyles = makeStyles(() => ({
entryTitle: {
display: 'flex',
minHeight: 20,
maxHeight: 46,
alignItems: 'center',
marginBottom: 4,
marginLeft: 6,
padding: 2,
paddingBottom: 0
},
entrySummary: {
display: 'flex',
minHeight: 36,
maxHeight: 46,
alignItems: 'center',
marginBottom: 4,
padding: 5,
paddingBottom: 0
}
}));
export const formatSize = (n: number) => n > 1000 ? `${Math.round(n / 1000)}KB` : `${n} B`;
const minSizeDisplayRequestSize = 880;
const EntryTitle: React.FC<any> = ({ protocol, data, elapsedTime }) => {
const classes = useStyles();
const request = data.request;
const response = data.response;
const { width } = useWindowDimensions();
const { requestText, responseText, elapsedTimeText } = useRequestTextByWidth(width)
return <div className={classes.entryTitle}>
<Protocol protocol={protocol} horizontal={true} />
{(width > minSizeDisplayRequestSize) && <div style={{ right: "30px", position: "absolute", display: "flex" }}>
{request && <Queryable
query={`requestSize == ${data.requestSize}`}
style={{ margin: "0 18px" }}
displayIconOnMouseOver={true}
>
<div
style={{ opacity: 0.5 }}
id="entryDetailedTitleRequestSize"
>
{`${requestText}${formatSize(data.requestSize)}`}
</div>
</Queryable>}
{response && <Queryable
query={`responseSize == ${data.responseSize}`}
style={{ margin: "0 18px" }}
displayIconOnMouseOver={true}
>
<div
style={{ opacity: 0.5 }}
id="entryDetailedTitleResponseSize"
>
{`${responseText}${formatSize(data.responseSize)}`}
</div>
</Queryable>}
{response && <Queryable
query={`elapsedTime >= ${elapsedTime}`}
style={{ margin: "0 0 0 18px" }}
displayIconOnMouseOver={true}
>
<div
style={{ opacity: 0.5 }}
id="entryDetailedTitleElapsedTime"
>
{`${elapsedTimeText}${Math.round(elapsedTime)}ms`}
</div>
</Queryable>}
</div>}
</div>;
};
const EntrySummary: React.FC<any> = ({ entry, namespace }) => {
return <EntryItem
key={`entry-${entry.id}`}
entry={entry}
style={{}}
headingMode={true}
namespace={namespace}
/>;
};
export const EntryDetailed = () => {
const focusedEntryId = useRecoilValue(focusedEntryIdAtom);
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
const query = useRecoilValue(queryAtom);
const [isLoading, setIsLoading] = useState(false);
const [entryData, setEntryData] = useRecoilState(entryDataAtom)
useEffect(() => {
setEntryData(null);
if (!focusedEntryId) return;
setIsLoading(true);
(async () => {
try {
const entryData = await trafficViewerApi.getEntry(focusedEntryId, query);
setEntryData(entryData);
} catch (error) {
if (error.response?.data?.type) {
toast[error.response.data.type](`Entry[${focusedEntryId}]: ${error.response.data.msg}`, {
theme: "colored",
autoClose: error.response.data.autoClose,
progress: undefined,
containerId: TOAST_CONTAINER_ID
});
}
console.error(error);
} finally {
setIsLoading(false);
}
})();
// eslint-disable-next-line
}, [focusedEntryId]);
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>
};

View File

@ -1,95 +0,0 @@
@import '../../../variables.module'
.title
display: flex
align-items: center
font-weight: 800
.button
display: flex
align-content: center
justify-content: space-around
width: .75rem
height: .75rem
border-radius: 4px
font-size: .75rem
line-height: 0.92
margin-right: .5rem
font-weight: 800
color: $main-background-color
background-color: $light-blue-color
&.expanded
@extend .button
line-height: .75rem
background-color: $blue-color
.dataLine
font-weight: 600
font-size: .75rem
line-height: 1.2
margin-bottom: -2px
.dataKey
color: $blue-gray
margin: 0 0.5rem 0 0
text-align: right
overflow: hidden
text-overflow: ellipsis
width: 1%
max-width: 15rem
.rulesTitleSuccess
color: #0C0B1A
.rulesMatchedSuccess
background: #E8FFF1
padding: 5px
border-radius: 4px
color: #219653
font-style: normal
font-size: 0.7rem
font-weight: 600
.rulesMatchedFailure
background: #FFE9EF
padding: 5px
border-radius: 4px
color: #DB2156
font-style: normal
font-size: 0.7rem
font-weight: 600
.dataValue
color: $blue-gray
margin: 0
font-weight: normal
> span:first-child
word-break: break-all
max-width: calc(100% - 1.5rem)
> span:nth-child(2)
border-radius: .2rem
background-color: #344073
display: block
margin-left: .5rem
margin-right: 0
transition: all .3s
width: 1rem
height: 1rem
&:hover
background-color: #42518f
img
position: relative
width: 100%
.collapsibleContainer
border-top: 1px solid $light-blue-color
padding: 1rem
background: none
table
width: 100%
tr td:first-child
white-space: nowrap
padding-right: .5rem
.noRules
padding: 0 1rem 1rem

View File

@ -1,283 +0,0 @@
import styles from "./EntrySections.module.sass";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { SyntaxHighlighter } from "../../UI/SyntaxHighlighter";
import CollapsibleContainer from "../../UI/CollapsibleContainer/CollapsibleContainer";
import FancyTextDisplay from "../../UI/FancyTextDisplay/FancyTextDisplay";
import Queryable from "../../UI/Queryable/Queryable";
import Checkbox from "../../UI/Checkbox/Checkbox";
import ProtobufDecoder from "protobuf-decoder";
import { default as jsonBeautify } from "json-beautify";
import { default as xmlBeautify } from "xml-formatter";
import { Utils } from "../../../helpers/Utils"
interface EntryViewLineProps {
label: string;
value: number | string;
selector?: string;
overrideQueryValue?: string;
displayIconOnMouseOver?: boolean;
useTooltip?: boolean;
}
const EntryViewLine: React.FC<EntryViewLineProps> = ({ label, value, selector = "", overrideQueryValue = "", displayIconOnMouseOver = true, useTooltip = true }) => {
let query: string;
if (!selector) {
query = "";
} else if (overrideQueryValue) {
query = `${selector} == ${overrideQueryValue}`;
} else if (typeof (value) == "string") {
query = `${selector} == "${JSON.stringify(value).slice(1, -1)}"`;
} else {
query = `${selector} == ${value}`;
}
return (label && <tr className={styles.dataLine}>
<td className={`${styles.dataKey}`}>
<Queryable
query={query}
style={{ float: "right", height: "18px" }}
iconStyle={{ marginRight: "20px" }}
flipped={true}
useTooltip={useTooltip}
displayIconOnMouseOver={displayIconOnMouseOver}
>
{label}
</Queryable>
</td>
<td>
<FancyTextDisplay
className={styles.dataValue}
text={value}
applyTextEllipsis={false}
flipped={true}
displayIconOnMouseOver={true}
/>
</td>
</tr>) || null;
}
interface EntrySectionCollapsibleTitleProps {
title: string,
color: string,
expanded: boolean,
setExpanded: any,
query?: string,
}
const EntrySectionCollapsibleTitle: React.FC<EntrySectionCollapsibleTitleProps> = ({ title, color, expanded, setExpanded, query = "" }) => {
return <div className={styles.title}>
<div
className={`${styles.button} ${expanded ? styles.expanded : ''}`}
style={{ backgroundColor: color }}
onClick={() => {
setExpanded(!expanded)
}}
>
{expanded ? '-' : '+'}
</div>
<Queryable
query={query}
useTooltip={!!query}
displayIconOnMouseOver={!!query}
>
<span>{title}</span>
</Queryable>
</div>
}
interface EntrySectionContainerProps {
title: string,
color: string,
query?: string,
}
export const EntrySectionContainer: React.FC<EntrySectionContainerProps> = ({ title, color, children, query = "" }) => {
const [expanded, setExpanded] = useState(true);
return <CollapsibleContainer
className={styles.collapsibleContainer}
expanded={expanded}
title={<EntrySectionCollapsibleTitle title={title} color={color} expanded={expanded} setExpanded={setExpanded} query={query} />}
>
{children}
</CollapsibleContainer>
}
const MAXIMUM_BYTES_TO_FORMAT = 1000000; // The maximum of chars to highlight in body, in case the response can be megabytes
const jsonLikeFormats = ['json', 'yaml', 'yml'];
const xmlLikeFormats = ['xml', 'html'];
const protobufFormats = ['application/grpc'];
const supportedFormats = jsonLikeFormats.concat(xmlLikeFormats, protobufFormats);
interface EntryBodySectionProps {
title: string,
content: any,
color: string,
encoding?: string,
contentType?: string,
selector?: 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);
const bodyBuf = isBase64Encoding ? atob(chunk) : chunk;
try {
if (jsonLikeFormats.some(format => contentType?.indexOf(format) > -1)) {
if (!isPretty) return bodyBuf;
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, {
indentation: ' ',
filter: (node) => node.type !== 'Comment',
collapseContent: true,
lineSeparator: '\n'
});
} else if (protobufFormats.some(format => contentType?.indexOf(format) > -1)) {
// Replace all non printable characters (ASCII)
const protobufDecoder = new ProtobufDecoder(bodyBuf, true);
const protobufDecoded = protobufDecoder.decode().toSimple();
if (!isPretty) return JSON.stringify(protobufDecoded);
return jsonBeautify(protobufDecoded, null, 2, 80);
}
} 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,
content,
encoding,
contentType,
selector,
}) => {
const [isPretty, setIsPretty] = useState(true);
const [showLineNumbers, setShowLineNumbers] = useState(false);
const [decodeBase64, setDecodeBase64] = useState(true);
const isBase64Encoding = encoding === 'base64';
const supportsPrettying = supportedFormats.some(format => contentType?.indexOf(format) > -1);
const [isDecodeGrpc, setIsDecodeGrpc] = useState(true);
const [isLineNumbersGreaterThenOne, setIsLineNumbersGreaterThenOne] = useState(true);
useEffect(() => {
(isLineNumbersGreaterThenOne && isPretty) && setShowLineNumbers(true);
!isLineNumbersGreaterThenOne && setShowLineNumbers(false);
}, [isLineNumbersGreaterThenOne, isPretty])
const formatTextBody = useCallback((body) => {
const bodyRef = { body }
try {
return formatRequest(bodyRef, contentType, decodeBase64, isBase64Encoding, isPretty)
} catch (error) {
if (String(error).includes("More than one message in")) {
if (isDecodeGrpc)
setIsDecodeGrpc(false);
} else if (String(error).includes("Failed to parse")) {
console.warn(error);
}
}
return bodyRef.body
}, [isPretty, contentType, isDecodeGrpc, decodeBase64, isBase64Encoding])
const formattedText = useMemo(() => formatTextBody(content), [formatTextBody, content]);
useEffect(() => {
const lineNumbers = Utils.lineNumbersInString(formattedText);
setIsLineNumbersGreaterThenOne(lineNumbers > 1);
}, [isPretty, content, showLineNumbers, formattedText]);
return <React.Fragment>
{content && content?.length > 0 && <EntrySectionContainer
title={title}
color={color}
query={`${selector} == r".*"`}
>
<div style={{ display: 'flex', alignItems: 'center', alignContent: 'center', margin: "5px 0" }}>
{supportsPrettying && <div style={{ paddingTop: 3 }}>
<Checkbox checked={isPretty} onToggle={() => { setIsPretty(!isPretty) }} data-cy="prettyCheckBoxInput"/>
</div>}
{supportsPrettying && <span style={{ marginLeft: '.2rem' }}>Pretty</span>}
<div style={{ paddingTop: 3, paddingLeft: supportsPrettying ? 20 : 0 }}>
<Checkbox checked={showLineNumbers} onToggle={() => { setShowLineNumbers(!showLineNumbers) }} disabled={!isLineNumbersGreaterThenOne || !decodeBase64} data-cy="lineNumbersCheckBoxInput"/>
</div>
<span style={{ marginLeft: '.2rem' }}>Line numbers</span>
{isBase64Encoding && <div style={{ paddingTop: 3, paddingLeft: (isLineNumbersGreaterThenOne || supportsPrettying) ? 20 : 0 }}>
<Checkbox checked={decodeBase64} onToggle={() => { setDecodeBase64(!decodeBase64) }} data-cy="decodeBase64CheckboxInput"/>
</div>}
{isBase64Encoding && <span style={{ marginLeft: '.2rem' }}>Decode Base64</span>}
{!isDecodeGrpc && <span style={{ fontSize: '12px', color: '#DB2156', marginLeft: '.8rem' }}>More than one message in protobuf payload is not supported</span>}
</div>
<SyntaxHighlighter
code={formattedText}
showLineNumbers={showLineNumbers}
/>
</EntrySectionContainer>}
</React.Fragment>
}
interface EntrySectionProps {
title: string,
color: string,
arrayToIterate: any[],
}
export const EntryTableSection: React.FC<EntrySectionProps> = ({ title, color, arrayToIterate }) => {
let arrayToIterateSorted: any[];
if (arrayToIterate) {
arrayToIterateSorted = arrayToIterate.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
if (a.name < b.name) {
return -1;
}
return 0;
});
}
return <React.Fragment>
{
arrayToIterate && arrayToIterate.length > 0 ?
<EntrySectionContainer title={title} color={color}>
<table>
<tbody id={`tbody-${title}`}>
{arrayToIterateSorted.map(({ name, value, selector }, index) => <EntryViewLine
key={index}
label={name}
value={value}
selector={selector}
/>)}
</tbody>
</table>
</EntrySectionContainer> : <span />
}
</React.Fragment>
}

View File

@ -1,79 +0,0 @@
import React, { useState, useCallback, useEffect, useMemo } from "react"
import { useRecoilValue, useSetRecoilState } from "recoil"
import entryDataAtom from "../../../recoil/entryData"
import SectionsRepresentation from "./SectionsRepresentation";
import { ReactComponent as ReplayIcon } from './replay.svg';
import styles from './EntryViewer.module.sass';
import { Tabs } from "../../UI";
import replayRequestModalOpenAtom from "../../../recoil/replayRequestModalOpen";
import entryDetailedConfigAtom, { EntryDetailedConfig } from "../../../recoil/entryDetailedConfig";
const enabledProtocolsForReplay = ["http"]
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 { isReplayEnabled } = useRecoilValue<EntryDetailedConfig>(entryDetailedConfigAtom)
const setIsOpenRequestModal = useSetRecoilState(replayRequestModalOpenAtom)
const isReplayDisplayed = useCallback(() => {
return enabledProtocolsForReplay.find(x => x === entryData.protocol.name) && isDisplayReplay && isReplayEnabled
}, [entryData.protocol.name, isDisplayReplay, isReplayEnabled])
const { request, response } = JSON.parse(representation);
const TABS = useMemo(() => {
const arr = [
{
tab: 'Request',
badge: null
}]
if (response && response.length > 0) {
arr.push({
tab: 'Response',
badge: null
});
}
return arr
}, [response]);
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>;
}
return <div className={styles.Entry}>
{<div className={styles.body}>
<div className={styles.bodyHeader}>
<Tabs tabs={TABS} currentTab={currentTab} color={color} onChange={setCurrentTab} leftAligned />
{isReplayDisplayed() && <span title="Replay Request"><ReplayIcon fill={color} stroke={color} style={{ marginLeft: "10px", cursor: "pointer", height: "22px" }} onClick={() => setIsOpenRequestModal(true)} /></span>}
</div>
{getOpenedTabIndex() === TabsEnum.Request && <React.Fragment>
<SectionsRepresentation data={request} color={color} requestRepresentation={request} />
</React.Fragment>}
{response && response.length > 0 && getOpenedTabIndex() === TabsEnum.Response && <React.Fragment>
<SectionsRepresentation data={response} color={color} />
</React.Fragment>}
</div>}
</div>;
}

View File

@ -1,69 +0,0 @@
@import "../../../variables.module"
.Entry
font-family: "Source Sans Pro", Lucida Grande, Tahoma, sans-serif
height: calc(100% - 70px)
width: 100%
margin-top: 10px
h3,
h4
font-family: "Source Sans Pro", Lucida Grande, Tahoma, sans-serif
.header
background-color: rgb(55, 65, 111)
padding: 0.5rem .75rem .65rem .75rem
border-top-left-radius: 0.25rem
border-top-right-radius: 0.25rem
display: flex
font-size: .75rem
align-items: center
.description
min-width: 25rem
display: flex
align-items: center
justify-content: space-between
.method
padding: 0 .25rem
font-size: 0.75rem
font-weight: bold
border-radius: 0.25rem
border: 0.0625rem solid rgba(255, 255, 255, 0.16)
margin-right: .5rem
> span
margin-left: .5rem
.timing
border-left: 1px solid #627ef7
margin-left: .3rem
padding-left: .3rem
.headerClickable
cursor: pointer
&:hover
background: lighten(rgb(55, 65, 111), 10%)
border-top-left-radius: 0
border-top-right-radius: 0
.body
height: 100%
overflow-y: auto
background: $main-background-color
color: $blue-gray
border-radius: 4px
padding: 10px
position: relative
.bodyHeader
padding: 0 1rem
display: flex
align-items: center
justify-content: space-between
.endpointURL
font-size: .75rem
display: block
color: $blue-color
text-decoration: none
margin-bottom: .5rem
overflow-wrap: anywhere
padding: 5px 0

View File

@ -1,17 +0,0 @@
import React from 'react';
import { AutoRepresentation } from './AutoRepresentation';
interface Props {
representation: any;
color: string;
}
const EntryViewer: React.FC<Props> = ({representation, color}) => {
return <AutoRepresentation
representation={representation}
color={color}
isDisplayReplay={true}
/>
};
export default EntryViewer;

View File

@ -1,34 +0,0 @@
import React from "react";
import { EntryTableSection, EntryBodySection } from "../EntrySections/EntrySections";
enum SectionTypes {
SectionTable = "table",
SectionBody = "body",
}
const SectionsRepresentation: React.FC<any> = ({ data, color }) => {
const sections = []
if (data) {
for (const [i, row] of data.entries()) {
switch (row.type) {
case SectionTypes.SectionTable:
sections.push(
<EntryTableSection key={i} title={row.title} color={color} arrayToIterate={JSON.parse(row.data)} />
)
break;
case SectionTypes.SectionBody:
sections.push(
<EntryBodySection key={i} title={row.title} color={color} content={row.data} encoding={row.encoding} contentType={row.mimeType} selector={row.selector} />
)
break;
default:
break;
}
}
}
return <React.Fragment>{sections}</React.Fragment>;
}
export default SectionsRepresentation

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title/><path d="M16,12a1,1,0,0,1-.49.86l-5,3A1,1,0,0,1,10,16a1,1,0,0,1-.49-.13A1,1,0,0,1,9,15V9a1,1,0,0,1,1.51-.86l5,3A1,1,0,0,1,16,12Z" fill="#464646"/><path d="M21.92,5.09a1,1,0,0,0-1.07.15L19.94,6A9.84,9.84,0,0,0,12,2a10,10,0,1,0,9.42,13.33,1,1,0,0,0-1.89-.66A8,8,0,1,1,12,4a7.87,7.87,0,0,1,6.42,3.32l-1.07.92A1,1,0,0,0,18,10h3.5a1,1,0,0,0,1-1V6A1,1,0,0,0,21.92,5.09Z" fill="#464646"/></svg>

Before

Width:  |  Height:  |  Size: 477 B

View File

@ -1,4 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15" cy="15" r="13.5" stroke="#205CF5" stroke-width="3"/>
<path d="M20 15C20 15.3167 19.8392 15.6335 19.5175 15.8189L12.5051 19.8624C11.8427 20.2444 11 19.7858 11 19.0435V10.9565C11 10.2142 11.8427 9.75564 12.5051 10.1376L19.5175 14.1811C19.8392 14.3665 20 14.6833 20 15Z" fill="#205CF5"/>
</svg>

Before

Width:  |  Height:  |  Size: 404 B

View File

@ -1,6 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 673 B

View File

@ -1,87 +0,0 @@
@import '../../variables.module'
.row
display: flex
background: $main-background-color
min-height: 46px
max-height: 46px
align-items: center
padding: 0 8px
border-radius: 4px
cursor: pointer
border: solid 1px transparent
margin-right: 10px
&:not(:first-child)
margin-top: 10px
&:hover
border: solid 1px lighten(#4253a5, 20%)
.rowSelected
border: 1px $blue-color solid
.resolvedName
text-overflow: ellipsis
white-space: nowrap
color: $secondary-font-color
padding-left: 4px
padding-right: 10px
display: flex
font-size: 12px
.timestamp
font-size: 12px
color: $secondary-font-color
padding-left: 12px
flex-shrink: 0
width: 185px
text-align: left
.capture
margin-top: -60px
.capture img
height: 14px
margin-top: 12px
margin-left: -2px
.endpointServiceContainer
display: flex
flex-direction: column
overflow: hidden
padding-right: 10px
padding-top: 4px
flex-grow: 1
padding-left: 10px
.separatorRight
display: flex
border-right: 1px solid $data-background-color
padding-right: 12px
.separatorLeft
display: flex
padding: 4px
padding-left: 12px
.tcpInfo
font-size: 12px
color: $secondary-font-color
margin-top: 5px
margin-bottom: 5px
.port
margin-right: 5px
.ip
margin-left: 5px
@media (max-width: 1760px)
.timestamp
display: none
.separatorRight
border-right: 0px
@media (max-width: 1340px)
.separatorRight
display: none

View File

@ -1,265 +0,0 @@
import React from "react";
import Moment from 'moment';
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
import styles from './EntryListItem.module.sass';
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode/StatusCode";
import Protocol, {ProtocolInterface} from "../UI/Protocol/Protocol"
import eBPFLogo from "./assets/lock.svg";
import {Summary} from "../UI/Summary/Summary";
import Queryable from "../UI/Queryable/Queryable";
import ingoingIconSuccess from "./assets/ingoing-traffic-success.svg"
import ingoingIconFailure from "./assets/ingoing-traffic-failure.svg"
import ingoingIconNeutral from "./assets/ingoing-traffic-neutral.svg"
import outgoingIconSuccess from "./assets/outgoing-traffic-success.svg"
import outgoingIconFailure from "./assets/outgoing-traffic-failure.svg"
import outgoingIconNeutral from "./assets/outgoing-traffic-neutral.svg"
import {useRecoilState} from "recoil";
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
interface TCPInterface {
ip: string
port: string
name: string
}
interface Entry {
proto: ProtocolInterface,
capture: string,
method?: string,
methodQuery?: string,
summary: string,
summaryQuery: string,
id: number,
status?: number;
statusQuery?: string;
timestamp: Date;
src: TCPInterface,
dst: TCPInterface,
isOutgoing?: boolean;
latency: number;
}
interface EntryProps {
entry: Entry;
style: object;
headingMode: boolean;
namespace?: string;
}
enum CaptureTypes {
UndefinedCapture = "",
Pcap = "pcap",
Envoy = "envoy",
Linkerd = "linkerd",
Ebpf = "ebpf",
}
export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode, namespace}) => {
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
const isSelected = focusedEntryId === entry.id;
const classification = getClassification(entry.status)
let ingoingIcon;
let outgoingIcon;
switch(classification) {
case StatusCodeClassification.SUCCESS: {
ingoingIcon = ingoingIconSuccess;
outgoingIcon = outgoingIconSuccess;
break;
}
case StatusCodeClassification.FAILURE: {
ingoingIcon = ingoingIconFailure;
outgoingIcon = outgoingIconFailure;
break;
}
case StatusCodeClassification.NEUTRAL: {
ingoingIcon = ingoingIconNeutral;
outgoingIcon = outgoingIconNeutral;
break;
}
}
const isStatusCodeEnabled = ((entry.proto.name === "http" && "status" in entry) || entry.status !== 0);
return <React.Fragment>
<div
id={`entry-${entry.id}`}
className={`${styles.row}
${isSelected ? styles.rowSelected : ""}`}
onClick={() => {
if (!setFocusedEntryId) return;
setFocusedEntryId(entry.id);
}}
style={{
border: isSelected && !headingMode ? `1px ${entry.proto.backgroundColor} solid` : "1px transparent solid",
position: !headingMode ? "absolute" : "unset",
top: style['top'],
marginTop: !headingMode ? style['marginTop'] : "10px",
width: !headingMode ? "calc(100% - 25px)" : "calc(100% - 18px)",
}}
>
{!headingMode ? <Protocol
protocol={entry.proto}
horizontal={false}
/> : null}
{/* TODO: Update the code below once we have api.Pcap, api.Envoy and api.Linkerd distinction in the backend */}
{entry.capture === CaptureTypes.Ebpf ? <div className={styles.capture}>
<Queryable
query={`capture == "${entry.capture}"`}
displayIconOnMouseOver={true}
flipped={false}
style={{position: "absolute"}}
>
<img src={eBPFLogo} alt="eBPF"/>
</Queryable>
</div> : null}
{isStatusCodeEnabled && <div>
<StatusCode statusCode={entry.status} statusQuery={entry.statusQuery}/>
</div>}
<div className={styles.endpointServiceContainer}>
<Summary method={entry.method} methodQuery={entry.methodQuery} summary={entry.summary} summaryQuery={entry.summaryQuery}/>
<div className={styles.resolvedName}>
<Queryable
query={`src.name == "${entry.src.name}"`}
displayIconOnMouseOver={true}
flipped={true}
style={{marginTop: "-4px", overflow: "visible"}}
iconStyle={!headingMode ? {marginTop: "4px", right: "16px", position: "relative"} :
entry.proto.name === "http" ? {marginTop: "4px", left: "calc(50vw + 41px)", position: "absolute"} :
{marginTop: "4px", left: "calc(50vw - 9px)", position: "absolute"}}
>
<span
title="Source Name"
>
{entry.src.name ? entry.src.name : "[Unresolved]"}
</span>
</Queryable>
<SwapHorizIcon style={{color: entry.proto.backgroundColor, marginTop: "-2px",marginLeft:"5px",marginRight:"5px"}}></SwapHorizIcon>
<Queryable
query={`dst.name == "${entry.dst.name}"`}
displayIconOnMouseOver={true}
flipped={true}
style={{marginTop: "-4px"}}
iconStyle={{marginTop: "4px", marginLeft: "-2px",right: "11px", position: "relative"}}
>
<span
title="Destination Name">
{entry.dst.name ? entry.dst.name : "[Unresolved]"}
</span>
</Queryable>
</div>
</div>
<div className={styles.separatorRight}>
{headingMode ? <Queryable
query={`namespace == "${namespace}"`}
displayIconOnMouseOver={true}
flipped={true}
iconStyle={{marginRight: "16px"}}
>
<span
className={`${styles.tcpInfo} ${styles.ip}`}
title="Namespace"
>
{namespace}
</span>
</Queryable> : null}
<Queryable
query={`src.ip == "${entry.src.ip}"`}
displayIconOnMouseOver={true}
flipped={true}
iconStyle={{marginRight: "16px"}}
>
<span
className={`${styles.tcpInfo} ${styles.ip}`}
title="Source IP"
>
{entry.src.ip}
</span>
</Queryable>
<span className={`${styles.tcpInfo}`} style={{marginTop: "18px"}}>{entry.src.port ? ":" : ""}</span>
<Queryable
query={`src.port == "${entry.src.port}"`}
displayIconOnMouseOver={true}
flipped={true}
iconStyle={{marginTop: "28px"}}
>
<span
className={`${styles.tcpInfo} ${styles.port}`}
title="Source Port"
>
{entry.src.port}
</span>
</Queryable>
{entry.isOutgoing ?
<Queryable
query={`outgoing == true`}
displayIconOnMouseOver={true}
flipped={true}
iconStyle={{marginTop: "28px"}}
>
<img
src={outgoingIcon}
alt="Outgoing traffic"
title="Outgoing"
/>
</Queryable>
:
<Queryable
query={`outgoing == false`}
displayIconOnMouseOver={true}
flipped={true}
iconStyle={{marginTop: "28px"}}
>
<img
src={ingoingIcon}
alt="Ingoing traffic"
title="Ingoing"
/>
</Queryable>
}
<Queryable
query={`dst.ip == "${entry.dst.ip}"`}
displayIconOnMouseOver={true}
flipped={false}
iconStyle={{marginTop: "30px", marginLeft: "-2px",right: "35px", position: "relative"}}
>
<span
className={`${styles.tcpInfo} ${styles.ip}`}
title="Destination IP"
>
{entry.dst.ip}
</span>
</Queryable>
<span className={`${styles.tcpInfo}`} style={{marginTop: "18px"}}>:</span>
<Queryable
query={`dst.port == "${entry.dst.port}"`}
displayIconOnMouseOver={true}
flipped={false}
>
<span
className={`${styles.tcpInfo} ${styles.port}`}
title="Destination Port"
>
{entry.dst.port}
</span>
</Queryable>
</div>
<div className={styles.timestamp}>
<Queryable
query={`timestamp >= datetime("${Moment(+entry.timestamp)?.utc().format('MM/DD/YYYY, h:mm:ss.SSS A')}")`}
displayIconOnMouseOver={true}
flipped={false}
>
<span
title="Timestamp (UTC)"
>
{Moment(+entry.timestamp)?.utc().format('MM/DD/YYYY, h:mm:ss.SSS A')}
</span>
</Queryable>
</div>
</div>
</React.Fragment>
}

View File

@ -1,5 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5175 11.1465C16.8392 10.8869 17 10.4434 17 10C17 9.55657 16.8392 9.11314 16.5175 8.85348L12.5425 5.64459C13.2682 5.23422 14.1067 5 15 5C17.7614 5 20 7.23858 20 10C20 12.7614 17.7614 15 15 15C14.1067 15 13.2682 14.7658 12.5425 14.3554L16.5175 11.1465Z" fill="#BCCEFD"/>
<path d="M16 10C16 10.3167 15.8749 10.6335 15.6247 10.8189L10.1706 14.8624C9.65543 15.2444 9 14.7858 9 14.0435V5.95652C9 5.21417 9.65543 4.75564 10.1706 5.13758L15.6247 9.18106C15.8749 9.36653 16 9.68326 16 10Z" fill="#EB5757"/>
<path d="M0 10C0 8.89543 0.895431 8 2 8H10C11.1046 8 12 8.89543 12 10C12 11.1046 11.1046 12 10 12H2C0.895431 12 0 11.1046 0 10Z" fill="#EB5757"/>
</svg>

Before

Width:  |  Height:  |  Size: 800 B

View File

@ -1,5 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5175 11.1465C16.8392 10.8869 17 10.4434 17 10C17 9.55657 16.8392 9.11314 16.5175 8.85348L12.5425 5.64459C13.2682 5.23422 14.1067 5 15 5C17.7614 5 20 7.23858 20 10C20 12.7614 17.7614 15 15 15C14.1067 15 13.2682 14.7658 12.5425 14.3554L16.5175 11.1465Z" fill="#BCCEFD"/>
<path d="M16 10C16 10.3167 15.8749 10.6335 15.6247 10.8189L10.1706 14.8624C9.65543 15.2444 9 14.7858 9 14.0435V5.95652C9 5.21417 9.65543 4.75564 10.1706 5.13758L15.6247 9.18106C15.8749 9.36653 16 9.68326 16 10Z" fill="gray"/>
<path d="M0 10C0 8.89543 0.895431 8 2 8H10C11.1046 8 12 8.89543 12 10C12 11.1046 11.1046 12 10 12H2C0.895431 12 0 11.1046 0 10Z" fill="gray"/>
</svg>

Before

Width:  |  Height:  |  Size: 794 B

View File

@ -1,5 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5175 11.1465C16.8392 10.8869 17 10.4434 17 10C17 9.55657 16.8392 9.11314 16.5175 8.85348L12.5425 5.64459C13.2682 5.23422 14.1067 5 15 5C17.7614 5 20 7.23858 20 10C20 12.7614 17.7614 15 15 15C14.1067 15 13.2682 14.7658 12.5425 14.3554L16.5175 11.1465Z" fill="#BCCEFD"/>
<path d="M16 10C16 10.3167 15.8749 10.6335 15.6247 10.8189L10.1706 14.8624C9.65543 15.2444 9 14.7858 9 14.0435V5.95652C9 5.21417 9.65543 4.75564 10.1706 5.13758L15.6247 9.18106C15.8749 9.36653 16 9.68326 16 10Z" fill="#27AE60"/>
<path d="M0 10C0 8.89543 0.895431 8 2 8H10C11.1046 8 12 8.89543 12 10C12 11.1046 11.1046 12 10 12H2C0.895431 12 0 11.1046 0 10Z" fill="#27AE60"/>
</svg>

Before

Width:  |  Height:  |  Size: 800 B

View File

@ -1,3 +0,0 @@
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.33333 6.36364H8.66667V6.36011H10.6667V6.36364H11C11.2778 6.36364 11.5139 6.45644 11.7083 6.64205C11.9028 6.82765 12 7.05303 12 7.31818V13.0455C12 13.3106 11.9028 13.536 11.7083 13.7216C11.5139 13.9072 11.2778 14 11 14H1C0.722222 14 0.486111 13.9072 0.291666 13.7216C0.0972223 13.536 0 13.3106 0 13.0455V7.31818C0 7.05303 0.0972223 6.82765 0.291666 6.64205C0.486111 6.45644 0.722222 6.36364 1 6.36364H1.33333V4.45455C1.33333 3.23485 1.79167 2.1875 2.70833 1.3125C3.625 0.4375 4.72222 0 6 0C7.27778 0 8.375 0.4375 9.29167 1.3125C9.92325 1.91538 10.3373 2.60007 10.5337 3.36658L8.59659 3.85085C8.48731 3.40176 8.25026 3.00309 7.88542 2.65483C7.36458 2.15767 6.73611 1.90909 6 1.90909C5.26389 1.90909 4.63542 2.15767 4.11458 2.65483C3.59375 3.15199 3.33333 3.75189 3.33333 4.45455V6.36364Z" fill="#BCCEFD"/>
</svg>

Before

Width:  |  Height:  |  Size: 959 B

View File

@ -1,5 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 15C17.7614 15 20 12.7615 20 10C20 7.23861 17.7614 5.00003 15 5.00003C13.3642 5.00003 11.9118 5.78558 10.9996 7.00003H14C15.6569 7.00003 17 8.34318 17 10C17 11.6569 15.6569 13 14 13H10.9996C11.9118 14.2145 13.3642 15 15 15Z" fill="#BCCEFD"/>
<rect x="4" y="8.00003" width="12" height="4" rx="2" fill="#EB5757"/>
<path d="M5.96244e-08 10C6.34015e-08 9.68329 0.125088 9.36656 0.375266 9.18109L5.82939 5.13761C6.34457 4.75567 7 5.2142 7 5.95655L7 14.0435C7 14.7859 6.34457 15.2444 5.82939 14.8625L0.375266 10.819C0.125088 10.6335 5.58474e-08 10.3168 5.96244e-08 10Z" fill="#EB5757"/>
</svg>

Before

Width:  |  Height:  |  Size: 736 B

View File

@ -1,5 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 15C17.7614 15 20 12.7615 20 10C20 7.23861 17.7614 5.00003 15 5.00003C13.3642 5.00003 11.9118 5.78558 10.9996 7.00003H14C15.6569 7.00003 17 8.34318 17 10C17 11.6569 15.6569 13 14 13H10.9996C11.9118 14.2145 13.3642 15 15 15Z" fill="#BCCEFD"/>
<rect x="4" y="8.00003" width="12" height="4" rx="2" fill="gray"/>
<path d="M5.96244e-08 10C6.34015e-08 9.68329 0.125088 9.36656 0.375266 9.18109L5.82939 5.13761C6.34457 4.75567 7 5.2142 7 5.95655L7 14.0435C7 14.7859 6.34457 15.2444 5.82939 14.8625L0.375266 10.819C0.125088 10.6335 5.58474e-08 10.3168 5.96244e-08 10Z" fill="gray"/>
</svg>

Before

Width:  |  Height:  |  Size: 730 B

View File

@ -1,5 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 15C17.7614 15 20 12.7615 20 10C20 7.23861 17.7614 5.00003 15 5.00003C13.3642 5.00003 11.9118 5.78558 10.9996 7.00003H14C15.6569 7.00003 17 8.34318 17 10C17 11.6569 15.6569 13 14 13H10.9996C11.9118 14.2145 13.3642 15 15 15Z" fill="#BCCEFD"/>
<rect x="4" y="8.00003" width="12" height="4" rx="2" fill="#27AE60"/>
<path d="M5.96244e-08 10C6.34015e-08 9.68329 0.125088 9.36656 0.375266 9.18109L5.82939 5.13761C6.34457 4.75567 7 5.2142 7 5.95655L7 14.0435C7 14.7859 6.34457 15.2444 5.82939 14.8625L0.375266 10.819C0.125088 10.6335 5.58474e-08 10.3168 5.96244e-08 10Z" fill="#27AE60"/>
</svg>

Before

Width:  |  Height:  |  Size: 736 B

View File

@ -1,44 +0,0 @@
@import "../../variables.module"
.container
display: flex
flex-direction: row
align-items: center
padding: .5rem 0
border-bottom: 1px solid #BCC6DD
margin-right: 20px
.filterLabel
color: #8f9bb2
margin-right: 6px
font-size: 11px
margin-bottom: 4px
.icon
fill: #627ef7
.filterContainer
padding-right: 14px
display: flex
flex-direction: column
.filterText
input
padding: 4px 12px
background: $main-background-color
border-radius: 4px
font-size: 14px
border: 1px solid #BCC6DD
fieldset
border: none
$divider-breakpoint-1: 1055px
$divider-breakpoint-2: 1453px
@media (max-width: $divider-breakpoint-1)
.divider1
display: none
@media (max-width: $divider-breakpoint-2)
.divider2
display: none

View File

@ -1,345 +0,0 @@
import React, { FC, useEffect, useMemo, useRef, useState } from "react";
import styles from './Filters.module.sass';
import {Button, Grid, Modal, Box, Typography, Backdrop, Fade, Divider, debounce} from "@mui/material";
import CodeEditor from '@uiw/react-textarea-code-editor';
import MenuBookIcon from '@mui/icons-material/MenuBook';
import { SyntaxHighlighter } from "../UI/SyntaxHighlighter";
import filterUIExample1 from "./assets/filter-ui-example-1.png"
import filterUIExample2 from "./assets/filter-ui-example-2.png"
import variables from '../../variables.module.scss';
import { useRecoilState, useRecoilValue } from "recoil";
import queryAtom from "../../recoil/query";
import useKeyPress from "../../hooks/useKeyPress"
import shortcutsKeyboard from "../../configs/shortcutsKeyboard"
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
interface FiltersProps {
reopenConnection: any;
}
export const Filters: React.FC<FiltersProps> = ({ reopenConnection }) => {
const [query, setQuery] = useRecoilState(queryAtom);
const api: any = useRecoilValue(TrafficViewerApiAtom)
return <div className={styles.container}>
<QueryForm
query={query}
reopenConnection={reopenConnection}
onQueryChange={(query) => { setQuery(query?.trim()); }} validateQuery={api?.validateQuery} />
</div>;
};
type OnQueryChange = { valid: boolean, message: string, query: string }
interface QueryFormProps {
reopenConnection?: any;
query: string
onQueryChange?: (query: string) => void
validateQuery: (query: string) => Promise<{ valid: boolean, message: string }>;
onValidationChanged?: (event: OnQueryChange) => void
}
export const modalStyle = {
position: 'absolute',
top: '10%',
left: '50%',
transform: 'translate(-50%, 0%)',
width: '80vw',
bgcolor: 'background.paper',
borderRadius: '5px',
boxShadow: 24,
outline: "none",
p: 4,
color: '#000',
};
export const CodeEditorWrap: FC<QueryFormProps> = ({ query, onQueryChange, validateQuery, onValidationChanged }) => {
const [queryBackgroundColor, setQueryBackgroundColor] = useState("#f5f5f5");
const handleQueryChange = useMemo(
() =>
debounce(async (query: string) => {
if (!query) {
setQueryBackgroundColor("#f5f5f5");
onValidationChanged && onValidationChanged({ query: query, message: "", valid: true })
} else {
const data = await validateQuery(query);
if (!data) {
return;
}
if (data.valid) {
setQueryBackgroundColor("#d2fad2");
} else {
setQueryBackgroundColor("#fad6dc");
}
onValidationChanged && onValidationChanged({ query: query, message: data.message, valid: data.valid })
}
}, 500),
[onValidationChanged, validateQuery]
) as (query: string) => void;
useEffect(() => {
handleQueryChange(query);
}, [query, handleQueryChange]);
return <CodeEditor
value={query}
language="py"
placeholder="Kubeshark Filter Syntax"
onChange={(event) => onQueryChange(event.target.value)}
padding={8}
style={{
fontSize: 14,
backgroundColor: `${queryBackgroundColor}`,
fontFamily: 'ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace',
}}
/>
}
export const QueryForm: React.FC<QueryFormProps> = ({ validateQuery, reopenConnection, query, onQueryChange, onValidationChanged }) => {
const formRef = useRef<HTMLFormElement>(null);
const [openModal, setOpenModal] = useState(false);
const handleOpenModal = () => setOpenModal(true);
const handleCloseModal = () => setOpenModal(false);
const handleSubmit = (e) => {
reopenConnection();
e.preventDefault();
}
useKeyPress(shortcutsKeyboard.ctrlEnter, handleSubmit, formRef.current);
return <React.Fragment>
<form
ref={formRef}
onSubmit={handleSubmit}
style={{
width: '100%',
}}
>
<Grid container spacing={2}>
<Grid
item
xs={8}
style={{
maxHeight: '25vh',
overflowY: 'auto',
}}
>
<label>
<CodeEditorWrap validateQuery={validateQuery} query={query} onQueryChange={onQueryChange} onValidationChanged={onValidationChanged} />
</label>
</Grid>
<Grid item xs={4}>
<Button
type="submit"
variant="contained"
style={{
margin: "2px 0px 0px 0px",
backgroundColor: variables.blueColor,
fontWeight: 600,
borderRadius: "4px",
color: "#fff",
textTransform: "none",
}}
>
Apply
</Button>
<Button
title="Open Filtering Guide (Cheatsheet)"
variant="contained"
color="primary"
style={{
margin: "2px 0px 0px 10px",
minWidth: "26px",
backgroundColor: variables.blueColor,
fontWeight: 600,
borderRadius: "4px",
color: "#fff",
textTransform: "none",
}}
onClick={handleOpenModal}
>
<MenuBookIcon fontSize="inherit"></MenuBookIcon>
</Button>
</Grid>
</Grid>
</form>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
open={openModal}
onClose={handleCloseModal}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
style={{overflow: 'auto'}}
>
<Fade in={openModal}>
<Box sx={modalStyle}>
<Typography id="modal-modal-title" variant="h5" component="h2" style={{textAlign: 'center'}}>
Filtering Guide (Cheatsheet)
</Typography>
<Typography component={'span'} id="modal-modal-description">
<p>Kubeshark has a rich filtering syntax that let's you query the results both flexibly and efficiently.</p>
<p>Here are some examples that you can try;</p>
</Typography>
<Grid container>
<Grid item xs style={{margin: "10px"}}>
<Typography id="modal-modal-description">
This is a simple query that matches to HTTP packets with request path "/catalogue":
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`http and request.path == "/catalogue"`}
language="python"
/>
<Typography id="modal-modal-description">
The same query can be negated for HTTP path and written like this:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`http and request.path != "/catalogue"`}
language="python"
/>
<Typography id="modal-modal-description">
The syntax supports regular expressions. Here is a query that matches the HTTP requests that send JSON to a server:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`http and request.headers["Accept"] == r"application/json.*"`}
language="python"
/>
<Typography id="modal-modal-description">
Here is another query that matches HTTP responses with status code 4xx:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`http and response.status == r"4.*"`}
language="python"
/>
<Typography id="modal-modal-description">
The same exact query can be as integer comparison:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`http and response.status >= 400`}
language="python"
/>
<Typography id="modal-modal-description">
The results can be queried based on their timestamps:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`timestamp < datetime("10/28/2021, 9:13:02.905 PM")`}
language="python"
/>
</Grid>
<Divider className={styles.divider1} orientation="vertical" flexItem />
<Grid item xs style={{margin: "10px"}}>
<Typography id="modal-modal-description">
Since Kubeshark supports various protocols like gRPC, AMQP, Kafka and Redis. It's possible to write complex queries that match multiple protocols like this:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`(http and request.method == "PUT") or (amqp and request.queue.startsWith("test"))\n or (kafka and response.payload.errorCode == 2) or (redis and request.key == "example")\n or (grpc and request.headers[":path"] == r".*foo.*")`}
language="python"
/>
<Typography id="modal-modal-description">
By clicking the plus icon that appears beside the queryable UI elements on hovering in both left-pane and right-pane, you can automatically select a field and update the query:
</Typography>
<img
src={filterUIExample1}
width={600}
alt="Clicking to UI elements (left-pane)"
title="Clicking to UI elements (left-pane)"
/>
<Typography id="modal-modal-description">
Such that; clicking this icon in left-pane, would append the query below:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`and dst.name == "carts.sock-shop"`}
language="python"
/>
<Typography id="modal-modal-description">
Another queriable UI element example, this time from the right-pane:
</Typography>
<img
src={filterUIExample2}
width={300}
alt="Clicking to UI elements (right-pane)"
title="Clicking to UI elements (right-pane)"
/>
<Typography id="modal-modal-description">
A query that compares one selector to another is also a valid query:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`http and (request.query["x"] == response.headers["y"]\n or response.content.text.contains(request.query["x"]))`}
language="python"
/>
</Grid>
<Divider className={styles.divider2} orientation="vertical" flexItem />
<Grid item xs style={{margin: "10px"}}>
<Typography id="modal-modal-description">
There are a few helper methods included the in the filter language* to help building queries more easily.
</Typography>
<br></br>
<Typography id="modal-modal-description">
true if the given selector's value starts with (similarly <code style={{fontSize: "14px"}}>endsWith</code>, <code style={{fontSize: "14px"}}>contains</code>) the string:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`request.path.startsWith("something")`}
language="python"
/>
<Typography id="modal-modal-description">
a field that contains a JSON encoded string can be filtered based a JSONPath:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`response.content.text.json().some.path == "somevalue"`}
language="python"
/>
<Typography id="modal-modal-description">
fields that contain sensitive information can be redacted:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`and redact("request.path", "src.name")`}
language="python"
/>
<Typography id="modal-modal-description">
returns the UNIX timestamp which is the equivalent of the time that's provided by the string. Invalid input evaluates to false:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`timestamp >= datetime("10/19/2021, 6:29:02.593 PM")`}
language="python"
/>
<Typography id="modal-modal-description">
limits the number of records that are streamed back as a result of a query. Always evaluates to true:
</Typography>
<SyntaxHighlighter
showLineNumbers={false}
code={`and limit(100)`}
language="python"
/>
</Grid>
</Grid>
<br></br>
<Typography id="modal-modal-description" style={{fontSize: 12, fontStyle: 'italic'}}>
*Please refer to <a href="https://docs.kubeshark.co/en/querying#kfl-syntax-reference"><b>KFL Syntax Reference</b></a> for more information.
</Typography>
</Box>
</Fade>
</Modal>
</React.Fragment>
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,32 +0,0 @@
@import '../../variables.module'
.header
height: 60px
display: flex
align-items: center
padding: 5px 24px
justify-content: space-between
.title
margin-left: 10px
font-family: 'Work Sans', sans-serif
font-size: 20px
font-weight: bold
color: $blue-color
.logo
height: 45px
width: 45px
.description
margin-top: 2px
margin-left: 10px
font-size: 11px
font-weight: bold
color: $light-blue-color
.headerIcon
cursor: pointer
.entLogo
cursor: pointer

View File

@ -1,20 +0,0 @@
import React from "react";
import {AuthPresentation} from "../AuthPresentation/AuthPresentation";
import logo from './assets/Kubeshark-logo.svg';
import './Header.sass';
import * as UI from "../UI"
export const Header: React.FC = () => {
return <div className="header">
<div style={{display: "flex", alignItems: "center"}}>
<img className="logo" src={logo} alt="logo"/>
<div className="title">Kubeshark</div>
<div className="description">Traffic viewer for Kubernetes</div>
</div>
<div style={{display: "flex", alignItems: "center"}}>
<UI.InformationIcon/>
<AuthPresentation/>
</div>
</div>;
}

View File

@ -1,62 +0,0 @@
<svg width="323" height="122" viewBox="0 0 87.547439 84.530304" fill="none" xmlns="http://www.w3.org/2000/svg">
<g
id="Layer_2"
transform="translate(0.09743584)">
<g
id="Layer_1-2">
<path
style="fill:#326de6"
d="M 43.697266 0 C 42.897266 0 41.997266 -0.00078125 41.197266 0.19921875 L 10.697266 14.900391 C 9.1972608 15.600391 8.0972656 16.899609 7.6972656 18.599609 L 0.09765625 51.5 C -0.20234375 53.2 0.19726587 54.900781 1.1972656 56.300781 L 22.296875 82.400391 C 23.496875 83.600391 25.196484 84.4 26.896484 84.5 L 60.597656 84.5 C 62.397656 84.7 64.097266 83.900391 65.197266 82.400391 L 86.296875 56.300781 C 87.296875 54.900781 87.698047 53.2 87.498047 51.5 L 79.896484 18.800781 C 79.396484 17.200781 78.197266 15.899609 76.697266 15.099609 L 46.197266 0.5 C 45.397266 0.1 44.497266 0 43.697266 0 z M 66.515625 15.484375 A 1.4619351 1.4619351 0 0 1 68.019531 16.947266 A 2.9127558 2.9127558 0 0 1 67.322266 18.832031 A 16.523949 16.523949 0 0 0 64.300781 24.134766 A 6.6495148 6.6495148 0 0 1 61.210938 27.787109 C 55.292678 31.042497 50.023438 33.441406 50.023438 33.441406 C 51.547517 33.407901 52.888258 33.539943 54.078125 33.78125 L 54.794922 32.212891 A 0.26693874 0.26693874 0 0 1 55.302734 32.298828 L 55.462891 34.126953 C 55.704079 34.200806 55.934194 34.280377 56.160156 34.363281 L 57.441406 32.927734 A 0.26693874 0.26693874 0 0 1 57.902344 33.162109 L 57.511719 34.970703 C 57.70188 35.067399 57.867852 35.173517 58.044922 35.275391 L 59.410156 33.9375 A 0.26693874 0.26693874 0 0 1 59.853516 34.201172 L 59.308594 36.103516 C 59.440078 36.203833 59.557825 36.304741 59.679688 36.40625 L 61.484375 35.480469 A 0.26693874 0.26693874 0 0 1 61.828125 35.865234 L 60.775391 37.470703 C 60.969304 37.684973 61.146122 37.893588 61.304688 38.095703 L 63.212891 37.695312 A 0.26693874 0.26693874 0 0 1 63.443359 38.15625 L 62.115234 39.328125 C 62.15577 39.402437 62.230824 39.511093 62.261719 39.574219 A 0.4464948 0.4464948 0 0 1 62.029297 40.183594 A 48.930273 48.930273 0 0 0 51.714844 46.09375 C 49.086166 47.966168 46.207502 49.350532 43.730469 50.332031 C 43.076373 54.285127 40.473796 56.885623 39.478516 57.75 A 0.87976722 0.87976722 0 0 1 38.634766 57.923828 A 0.87995885 0.87995885 0 0 1 38.033203 56.925781 C 38.437075 54.75056 38.408972 53.11241 38.332031 52.101562 C 38.290797 52.112159 38.202408 52.138735 38.164062 52.148438 A 13.074058 13.074058 0 0 0 35.507812 53.115234 A 11.895733 11.895733 0 0 0 34.857422 53.480469 A 11.838628 11.838628 0 0 0 34.330078 53.794922 A 11.895733 11.895733 0 0 0 33.837891 54.140625 A 11.838628 11.838628 0 0 0 33.228516 54.59375 C 32.991842 54.790181 32.768649 54.997504 32.558594 55.212891 C 32.369385 55.406902 32.194752 55.609111 32.027344 55.816406 C 31.508815 57.117507 31.306818 58.67221 32.125 60.228516 A 5.1377563 5.1377563 0 0 0 35.498047 62.789062 A 26.202921 26.202921 0 0 0 41.576172 63.449219 A 4.1583421 4.1583421 0 0 0 44.960938 61.724609 C 46.624465 59.405902 50.130251 55.252983 54.955078 53.447266 A 34.752434 34.752434 0 0 0 60.173828 51.050781 C 61.569843 50.242875 63.193231 51.655259 62.607422 53.158203 A 7.7023228 7.7023228 0 0 1 60.019531 56.652344 C 56.332021 59.349134 52.257813 62.816608 52.257812 66.779297 C 52.257812 66.779297 51.966213 71.210845 55.501953 73.949219 A 1.1662521 1.1662521 0 0 1 54.792969 76.033203 C 51.436784 76.075553 46.506377 74.963299 42.957031 69.091797 A 22.909207 22.909207 0 0 1 38.742188 70.107422 L 38.738281 70.107422 C 35.66417 70.582412 32.957403 70.666419 30.652344 70.337891 L 30.65625 70.347656 C 30.65625 70.347656 14.041675 69.733055 14.634766 53.652344 C 14.811605 48.857623 16.281456 44.724826 18.251953 41.277344 C 18.16014 39.986781 17.79688 38.65623 17.28125 37.388672 A 1.4696003 1.4696003 0 0 1 17.421875 36.001953 L 19.126953 33.505859 A 0.15886017 0.15886017 0 0 0 18.912109 33.28125 L 16.152344 35.037109 C 15.659283 34.137796 15.146296 33.32412 14.689453 32.648438 A 2.1703863 2.1703863 0 0 1 15.625 29.427734 C 18.291513 28.306515 21.524198 27.723456 25.607422 28.181641 A 36.480542 36.480542 0 0 0 31.316406 28.324219 C 48.430863 18.814384 61.805489 16.163693 66.345703 15.5 A 1.4619351 1.4619351 0 0 1 66.515625 15.484375 z "
transform="translate(-0.09743584)"
id="path6" />
<g
id="g857"
transform="matrix(0.19162867,0,0,0.19162867,8.490619,13.182287)"><g
id="g849"
style="fill:#485a75"><path
id="path839"
data-original="#485a75"
d="m 192.963,97.984 c -4.249,-8.947 -6.27,-19.006 -6.563,-29.928 a 1.414,1.414 0 0 0 -2.736,-0.449 55.314,55.314 0 0 0 -2.656,9.6 21.038,21.038 0 0 0 3.958,16.393 35.833,35.833 0 0 0 5.808,6.066 1.419,1.419 0 0 0 2.189,-1.682 z"
style="fill:#485a75" /><path
id="path841"
data-original="#485a75"
d="m 177.85,105.342 c -4.249,-8.947 -6.27,-19.006 -6.563,-29.928 a 1.414,1.414 0 0 0 -2.736,-0.449 55.314,55.314 0 0 0 -2.656,9.6 21.038,21.038 0 0 0 3.958,16.394 35.72,35.72 0 0 0 5.808,6.066 1.419,1.419 0 0 0 2.189,-1.683 z"
style="fill:#485a75" /><path
id="path843"
data-original="#485a75"
d="m 163.212,112.168 c -4.249,-8.947 -6.27,-19.006 -6.563,-29.928 a 1.414,1.414 0 0 0 -2.736,-0.449 55.314,55.314 0 0 0 -2.656,9.6 21.038,21.038 0 0 0 3.958,16.394 35.718,35.718 0 0 0 5.808,6.066 1.419,1.419 0 0 0 2.189,-1.683 z"
style="fill:#485a75" /><path
id="path845"
data-original="#485a75"
d="m 282.539,41.775 a 3.464,3.464 0 0 1 -2.442,-5.922 l 2.594,-2.577 a 3.4644893,3.4644893 0 1 1 4.883,4.916 l -2.594,2.577 a 3.455,3.455 0 0 1 -2.441,1.006 z"
style="fill:#485a75" /><circle
id="circle847"
data-original="#485a75"
r="5.0100002"
cy="49.93"
cx="242.799"
style="fill:#485a75" /></g></g><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 39.03763,34.543061 c -1.527793,-1.496038 -1.827598,-2.980669 -1.058708,-5.242707 0.127475,-0.375025 0.218266,-0.50615 0.350457,-0.50615 0.152165,0 0.188106,0.09647 0.244306,0.655726 0.216618,2.155597 0.449111,3.24366 0.975953,4.567454 0.461821,1.160417 0.302867,1.323615 -0.512008,0.525677 z"
id="path900"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.006337,33.406311 c -1.337815,-1.184989 -1.866757,-2.629191 -1.538123,-4.199632 0.240017,-1.146966 0.464694,-1.723928 0.67132,-1.723928 0.159683,0 0.190274,0.122336 0.292808,1.17094 0.16981,1.736629 0.586255,3.459239 1.068721,4.420732 0.174454,0.347664 0.153619,0.638924 -0.04552,0.636332 -0.05889,-7.68e-4 -0.261033,-0.137766 -0.449207,-0.304444 z"
id="path902"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 44.593492,31.684332 c -1.06996,-1.096616 -1.495754,-2.317349 -1.27225,-3.647492 0.06476,-0.385406 0.209609,-0.983895 0.321888,-1.329977 0.160604,-0.495034 0.243576,-0.629239 0.389027,-0.629239 0.167866,0 0.195552,0.106755 0.300777,1.15978 0.178804,1.789343 0.514135,3.175579 1.092636,4.516885 0.109635,0.254197 0.118901,0.363706 0.03765,0.444957 -0.173562,0.173562 -0.231396,0.139321 -0.869728,-0.514914 z"
id="path904"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 54.796786,23.600645 c -0.300846,-0.126947 -0.598408,-0.559802 -0.598408,-0.870489 0,-0.192938 0.100882,-0.378642 0.330349,-0.608109 0.289083,-0.289083 0.372038,-0.323583 0.664085,-0.276191 0.411876,0.06684 0.590137,0.209069 0.754324,0.601856 0.107229,0.256525 0.107229,0.352363 0,0.608889 -0.06999,0.167444 -0.190498,0.356886 -0.267791,0.420982 -0.193757,0.160673 -0.644482,0.223522 -0.882559,0.123062 z"
id="path906"
transform="translate(-0.09743584)" /><path
style="opacity:1;fill:#326de6;fill-opacity:1;stroke:none;stroke-width:0.70809567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 62.366634,20.995747 c -0.30085,-0.260136 -0.326767,-0.517366 -0.08785,-0.871899 0.220552,-0.327277 0.758517,-0.743997 0.960464,-0.743997 0.06271,0 0.225096,0.08737 0.360847,0.194149 0.379108,0.298207 0.323778,0.598735 -0.208173,1.130685 -0.485833,0.485834 -0.722376,0.552984 -1.025291,0.291062 z"
id="path908"
transform="translate(-0.09743584)" /></g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -1,81 +0,0 @@
import React, { useState } from "react";
import { Button } from "@mui/material";
import Api, { KubesharkWebsocketURL } from "../../../helpers/api";
import debounce from 'lodash/debounce';
import { useRecoilState } from "recoil";
import { useCommonStyles } from "../../../helpers/commonStyle"
import serviceMapModalOpenAtom from "../../../recoil/serviceMapModalOpen";
import { TrafficViewer } from "../../TrafficViewer/TrafficViewer"
import "../../../index.sass"
import oasModalOpenAtom from "../../../recoil/oasModalOpen/atom";
import serviceMap from "./assets/serviceMap.svg";
import services from "./assets/services.svg";
import trafficStatsIcon from "./assets/trafficStats.svg";
import trafficStatsModalOpenAtom from "../../../recoil/trafficStatsModalOpen";
import { REPLAY_ENABLED } from "../../../consts";
const api = Api.getInstance();
export const TrafficPage: React.FC = () => {
const commonClasses = useCommonStyles();
const [serviceMapModalOpen, setServiceMapModalOpen] = useRecoilState(serviceMapModalOpenAtom);
const [openOasModal, setOpenOasModal] = useRecoilState(oasModalOpenAtom);
const [trafficStatsModalOpen, setTrafficStatsModalOpen] = useRecoilState(trafficStatsModalOpenAtom);
const [shouldCloseWebSocket, setShouldCloseWebSocket] = useState(false);
const trafficViewerApi = { ...api }
const handleOpenOasModal = () => {
setShouldCloseWebSocket(true)
setOpenOasModal(true);
}
const handleOpenStatsModal = () => {
setShouldCloseWebSocket(true)
setTrafficStatsModalOpen(true);
}
const openServiceMapModalDebounce = debounce(() => {
setShouldCloseWebSocket(true)
setServiceMapModalOpen(true)
}, 500);
const actionButtons = <div style={{ display: 'flex', height: "100%" }}>
{window["isOasEnabled"] && <Button
startIcon={<img className="custom" src={services} alt="services" />}
size="large"
variant="contained"
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
style={{ marginRight: 25, textTransform: 'unset' }}
onClick={handleOpenOasModal}>
Service Catalog
</Button>}
{window["isServiceMapEnabled"] && <Button
startIcon={<img src={serviceMap} className="custom" alt="service-map" style={{ marginRight: "8%" }} />}
size="large"
variant="contained"
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
onClick={openServiceMapModalDebounce}
style={{ marginRight: 25, textTransform: 'unset' }}>
Service Map
</Button>}
<Button
startIcon={<img className="custom" src={trafficStatsIcon} alt="services" />}
size="large"
variant="contained"
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
style={{ textTransform: 'unset' }}
onClick={handleOpenStatsModal}>
Traffic Stats
</Button>
</div>
return (
<>
<TrafficViewer webSocketUrl={KubesharkWebsocketURL} shouldCloseWebSocket={shouldCloseWebSocket} setShouldCloseWebSocket={setShouldCloseWebSocket}
trafficViewerApiProp={trafficViewerApi} actionButtons={actionButtons} isShowStatusBar={!(openOasModal || serviceMapModalOpen || trafficStatsModalOpen)} isDemoBannerView={false} entryDetailedConfig={{
isReplayEnabled: REPLAY_ENABLED
}} />
</>
);
};

View File

@ -1,15 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.2998 5.40002L8.9998 9.00002" stroke="#A0B2FF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.2998 12.6L8.99976 8.99988" stroke="#A0B2FF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.7002 5.40002L2.70024 12.5001" stroke="#A0B2FF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 8.99988V16.2" stroke="#A0B2FF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.7 5.40005L9 1.80005" stroke="#A0B2FF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.2998 5.40005L8.9998 1.80005" stroke="#A0B2FF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.4999 5.39995C4.4999 6.39406 3.69402 7.19995 2.6999 7.19995C1.70579 7.19995 0.899902 6.39406 0.899902 5.39995C0.899902 4.40584 1.70579 3.59995 2.6999 3.59995C3.69402 3.59995 4.4999 4.40584 4.4999 5.39995Z" fill="#205CF5"/>
<path d="M4.4999 12.6C4.4999 13.5941 3.69402 14.4 2.6999 14.4C1.70579 14.4 0.899902 13.5941 0.899902 12.6C0.899902 11.6059 1.70579 10.8 2.6999 10.8C3.69402 10.8 4.4999 11.6059 4.4999 12.6Z" fill="#205CF5"/>
<path d="M17.1 5.39995C17.1 6.39406 16.2941 7.19995 15.3 7.19995C14.3059 7.19995 13.5 6.39406 13.5 5.39995C13.5 4.40584 14.3059 3.59995 15.3 3.59995C16.2941 3.59995 17.1 4.40584 17.1 5.39995Z" fill="#205CF5"/>
<path d="M17.1 12.6C17.1 13.5941 16.2941 14.4 15.3 14.4C14.3059 14.4 13.5 13.5941 13.5 12.6C13.5 11.6059 14.3059 10.8 15.3 10.8C16.2941 10.8 17.1 11.6059 17.1 12.6Z" fill="#205CF5"/>
<path d="M10.8002 1.79998C10.8002 2.79409 9.99431 3.59998 9.0002 3.59998C8.00608 3.59998 7.2002 2.79409 7.2002 1.79998C7.2002 0.805863 8.00608 -2.45571e-05 9.0002 -2.45571e-05C9.99431 -2.45571e-05 10.8002 0.805863 10.8002 1.79998Z" fill="#205CF5"/>
<path d="M10.8002 9.00005C10.8002 9.99416 9.99431 10.8 9.0002 10.8C8.00608 10.8 7.2002 9.99416 7.2002 9.00005C7.2002 8.00594 8.00608 7.20005 9.0002 7.20005C9.99431 7.20005 10.8002 8.00594 10.8002 9.00005Z" fill="#205CF5"/>
<path d="M10.8002 16.2C10.8002 17.1941 9.99431 18 9.0002 18C8.00608 18 7.2002 17.1941 7.2002 16.2C7.2002 15.2059 8.00608 14.4 9.0002 14.4C9.99431 14.4 10.8002 15.2059 10.8002 16.2Z" fill="#205CF5"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,10 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="5.5" y="6.5" width="20" height="17" rx="1.5" stroke="#A0B2FF"/>
<path d="M18.5 6.5H24C24.8284 6.5 25.5 7.17157 25.5 8V22C25.5 22.8284 24.8284 23.5 24 23.5H18.5V6.5Z" fill="#BCCEFD" stroke="#A0B2FF"/>
<rect x="19" y="9" width="5" height="2" rx="1" fill="white"/>
<rect x="8" y="9" width="4" height="2" rx="1" fill="#205CF5"/>
<rect x="8" y="14" width="7" height="2" rx="1" fill="#205CF5"/>
<rect width="3" height="2" rx="1" transform="matrix(-1 0 0 1 22 14)" fill="white"/>
<path d="M24 20C24 19.4477 23.5523 19 23 19H19V21H23C23.5523 21 24 20.5523 24 20Z" fill="white"/>
<rect x="8" y="19" width="7" height="2" rx="1" fill="#205CF5"/>
</svg>

Before

Width:  |  Height:  |  Size: 746 B

View File

@ -1,10 +0,0 @@
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1.5" y="2.5" width="9" height="12" rx="1.5" stroke="#A0B2FF"/>
<rect x="5" y="4" width="3.42857" height="2" rx="1" fill="#205CF5"/>
<rect x="3" y="7" width="6" height="2" rx="1" fill="#205CF5"/>
<rect x="3" y="11" width="6" height="2" rx="1" fill="#205CF5"/>
<rect x="-0.5" y="0.5" width="9" height="12" rx="1.5" transform="matrix(-1 0 0 1 19 2)" stroke="#A0B2FF"/>
<rect width="3" height="2" rx="1" transform="matrix(-1 0 0 1 15 4)" fill="#205CF5"/>
<rect width="6" height="2" rx="1" transform="matrix(-1 0 0 1 18 7)" fill="#205CF5"/>
<rect width="6" height="2" rx="1" transform="matrix(-1 0 0 1 18 11)" fill="#205CF5"/>
</svg>

Before

Width:  |  Height:  |  Size: 734 B

View File

@ -1,124 +0,0 @@
@import '../../variables.module'
.TrafficPage
width: 100%
display: flex
flex-direction: column
overflow: hidden
flex-grow: 1
height: calc(100% - 70px)
.TrafficPageHeader
padding: 20px 24px
display: flex
align-items: center
background-color: $header-background-color
justify-content: space-between
.TrafficPageStreamStatus
display: flex
align-items: center
.TrafficPageHeaderImage
width: 22px
height: 22px
.TrafficPageHeaderText
margin-left: 10px
font-family: 'Source Sans Pro', serif
font-size: 14px
font-weight: bold
color: #f7f9fc
.TrafficPageHeaderActions
margin-left: auto
.TrafficPageHeaderActionsImage
width: 22px
height: 22px
cursor: pointer
padding-right: 1.2vw
margin-left: auto
transform: translate(0 ,25%)
.TrafficPageViewer
height: 96.5%
overflow: auto
> iframe
width: 100%
height: 96.5%
display: block
overflow-y: auto
.TrafficContent
box-sizing: border-box
height: calc(100% - 60px)
overflow: scroll
.TrafficPageContainer
display: flex
flex-grow: 1
overflow: hidden
background-color: $data-background-color
.TrafficPageListContainer
display: flex
flex-grow: 1
overflow: hidden
padding-left: 24px
flex-direction: column
.TrafficPageDetailContainer
width: 45vw
background-color: #171c30
flex: 0 0 50%
padding: 12px 24px
.indicatorContainer
border-radius: 50%
padding: 2px
margin-left: 10px
.indicator
height: 8px
width: 8px
border-radius: 50%
.greenIndicatorContainer
border: 2px #6fcf9770 solid
@keyframes biggerIndication
0%
transform: scale(2.0)
100%
transform: scale(0.7)
.greenIndicator
background-color: #27AE60
animation: biggerIndication 1.5s ease-out 0s alternate infinite none running
.orangeIndicatorContainer
border: 2px #fabd5970 solid
.orangeIndicator
background-color: #ffb530
.redIndicatorContainer
border: 2px #ff3a3045 solid
.redIndicator
background-color: #ff3a30
.connectionText
display: flex
align-items: center
height: 17px
font-size: 16px
.playPauseIcon
cursor: pointer
margin-right: 15px
height: 30px

View File

@ -1,295 +0,0 @@
import React, {useCallback, useEffect, useRef, useState} from "react";
import { Filters } from "../Filters/Filters";
import { EntriesList } from "../EntriesList/EntriesList";
import makeStyles from '@mui/styles/makeStyles';
import TrafficViewerStyles from "./TrafficViewer.module.sass";
import styles from '../EntriesList/EntriesList.module.sass';
import { EntryDetailed } from "../EntryDetailed/EntryDetailed";
import playIcon from "./assets/run.svg";
import pauseIcon from "./assets/pause.svg";
import variables from '../../variables.module.scss';
import { ToastContainer } from 'react-toastify';
import { RecoilRoot, RecoilState, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import entriesAtom from "../../recoil/entries";
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
import queryAtom from "../../recoil/query";
import trafficViewerApiAtom from "../../recoil/TrafficViewerApi"
import TrafficViewerApi from "./TrafficViewerApi";
import { StatusBar } from "../UI/StatusBar/StatusBar";
import tappingStatusAtom from "../../recoil/tappingStatus/atom";
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";
import entryDetailedConfigAtom, { EntryDetailedConfig } from "../../recoil/entryDetailedConfig";
const useLayoutStyles = makeStyles(() => ({
details: {
flex: "0 0 50%",
width: "45vw",
padding: "12px 24px",
borderRadius: 4,
marginTop: 15,
background: variables.headerBackgroundColor,
},
viewer: {
display: "flex",
overflowY: "auto",
height: "calc(100% - 70px)",
padding: 5,
paddingBottom: 0,
overflow: "auto",
},
}));
interface TrafficViewerProps {
api?: any
trafficViewerApiProp: TrafficViewerApi,
actionButtons?: JSX.Element,
isShowStatusBar?: boolean,
webSocketUrl: string,
shouldCloseWebSocket: boolean,
setShouldCloseWebSocket: (flag: boolean) => void,
isDemoBannerView: boolean,
entryDetailedConfig: EntryDetailedConfig
}
export const TrafficViewer: React.FC<TrafficViewerProps> = ({
trafficViewerApiProp,
webSocketUrl,
actionButtons,
isShowStatusBar, isDemoBannerView,
shouldCloseWebSocket, setShouldCloseWebSocket,
entryDetailedConfig }) => {
const classes = useLayoutStyles();
const setEntries = useSetRecoilState(entriesAtom);
const setFocusedEntryId = useSetRecoilState(focusedEntryIdAtom);
const setEntryDetailedConfigAtom = useSetRecoilState(entryDetailedConfigAtom)
const query = useRecoilValue(queryAtom);
const setTrafficViewerApiState = useSetRecoilState(trafficViewerApiAtom as RecoilState<TrafficViewerApi>)
const [tappingStatus, setTappingStatus] = useRecoilState(tappingStatusAtom);
const [noMoreDataTop, setNoMoreDataTop] = useState(false);
const [isSnappedToBottom, setIsSnappedToBottom] = useState(true);
const [wsReadyState, setWsReadyState] = useState(0);
const setLeftOffTop = useSetRecoilState(leftOffTopAtom);
const scrollableRef = useRef(null);
const isOpenReplayModal = useRecoilValue(replayRequestModalOpenAtom)
const ws = useRef(null);
const closeWebSocket = useCallback(() => {
if (ws?.current?.readyState === WebSocket.OPEN) {
ws.current.close();
return true;
}
}, [])
useEffect(() => {
if(shouldCloseWebSocket){
closeWebSocket()
setShouldCloseWebSocket(false);
}
}, [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) {
ws.current.send(JSON.stringify({
"leftOff": leftOff,
"query": query,
"enableFullEntries": false,
"fetch": fetch,
"timeoutMs": fetchTimeoutMs
}));
} else {
sendQueryWhenWsOpen(leftOff, query, fetch, fetchTimeoutMs);
}
}, 500)
}, [])
const listEntry = useRef(null);
const openWebSocket = useCallback((leftOff: string, query: string, resetEntries: boolean, fetch: number, fetchTimeoutMs: number) => {
if (resetEntries) {
setFocusedEntryId(null);
setEntries([]);
setLeftOffTop("");
setNoMoreDataTop(false);
}
try {
ws.current = new WebSocket(webSocketUrl);
sendQueryWhenWsOpen(leftOff, query, fetch, fetchTimeoutMs);
ws.current.onopen = () => {
setWsReadyState(ws?.current?.readyState);
}
ws.current.onclose = () => {
setWsReadyState(ws?.current?.readyState);
}
ws.current.onerror = (event) => {
console.error("WebSocket error:", event);
if (ws?.current?.readyState === WebSocket.OPEN) {
ws.current.close();
}
}
} catch (e) {
}
}, [setFocusedEntryId, setEntries, setLeftOffTop, setNoMoreDataTop, ws, sendQueryWhenWsOpen, webSocketUrl])
const openEmptyWebSocket = useCallback(() => {
openWebSocket(DEFAULT_LEFTOFF, query, true, DEFAULT_FETCH, DEFAULT_FETCH_TIMEOUT_MS);
}, [openWebSocket, query])
useEffect(() => {
setTrafficViewerApiState({...trafficViewerApiProp, webSocket: {close: closeWebSocket}});
(async () => {
try {
const tapStatusResponse = await trafficViewerApiProp.tapStatus();
setTappingStatus(tapStatusResponse);
} catch (error) {
console.error(error);
}
})()
}, [trafficViewerApiProp, closeWebSocket, setTappingStatus, setTrafficViewerApiState]);
const toggleConnection = () => {
if (!closeWebSocket()) {
openEmptyWebSocket();
scrollableRef.current.jumpToBottom();
setIsSnappedToBottom(true);
}
}
const reopenConnection = useCallback(async () => {
closeWebSocket()
openEmptyWebSocket();
scrollableRef.current.jumpToBottom();
setIsSnappedToBottom(true);
}, [scrollableRef, setIsSnappedToBottom, closeWebSocket, openEmptyWebSocket])
useEffect(() => {
reopenConnection()
// eslint-disable-next-line
}, [webSocketUrl])
useEffect(() => {
return () => {
if (ws?.current?.readyState === WebSocket.OPEN) {
ws.current.close();
}
};
}, []);
useEffect(() => {
setEntryDetailedConfigAtom(entryDetailedConfig)
}, [entryDetailedConfig, setEntryDetailedConfigAtom])
const getConnectionIndicator = () => {
switch (wsReadyState) {
case WebSocket.OPEN:
return <div
className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.greenIndicatorContainer}`}>
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.greenIndicator}`}/>
</div>
default:
return <div
className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.redIndicatorContainer}`}>
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.redIndicator}`}/>
</div>
}
}
const getConnectionTitle = () => {
switch (wsReadyState) {
case WebSocket.OPEN:
return "streaming live traffic"
default:
return "streaming paused";
}
}
const onSnapBrokenEvent = () => {
setIsSnappedToBottom(false);
if (ws?.current?.readyState === WebSocket.OPEN) {
ws.current.close();
}
}
return (
<div className={TrafficViewerStyles.TrafficPage}>
{tappingStatus && isShowStatusBar && <StatusBar disabled={ws?.current?.readyState !== WebSocket.OPEN} isDemoBannerView={isDemoBannerView}/>}
<div className={TrafficViewerStyles.TrafficPageHeader}>
<div className={TrafficViewerStyles.TrafficPageStreamStatus}>
<img id="pause-icon"
className={TrafficViewerStyles.playPauseIcon}
style={{visibility: wsReadyState === WebSocket.OPEN ? "visible" : "hidden"}}
alt="pause"
src={pauseIcon}
onClick={toggleConnection}/>
<img id="play-icon"
className={TrafficViewerStyles.playPauseIcon}
style={{position: "absolute", visibility: wsReadyState === WebSocket.OPEN ? "hidden" : "visible"}}
alt="play"
src={playIcon}
onClick={toggleConnection}/>
<div className={TrafficViewerStyles.connectionText}>
{getConnectionTitle()}
{getConnectionIndicator()}
</div>
</div>
{actionButtons}
</div>
{<div className={TrafficViewerStyles.TrafficPageContainer}>
<div className={TrafficViewerStyles.TrafficPageListContainer}>
<Filters
reopenConnection={reopenConnection}
/>
<div className={styles.container}>
<EntriesList
listEntryREF={listEntry}
onSnapBrokenEvent={onSnapBrokenEvent}
isSnappedToBottom={isSnappedToBottom}
setIsSnappedToBottom={setIsSnappedToBottom}
noMoreDataTop={noMoreDataTop}
setNoMoreDataTop={setNoMoreDataTop}
openWebSocket={openWebSocket}
scrollableRef={scrollableRef}
ws={ws}
/>
</div>
</div>
<div className={classes.details} id="rightSideContainer">
<EntryDetailed />
</div>
</div>}
</div>
);
};
const MemorizedTrafficViewer = React.memo(TrafficViewer)
const TrafficViewerContainer: React.FC<TrafficViewerProps> = (props) => {
return <RecoilRoot>
<MemorizedTrafficViewer {...props} />
<ToastContainer enableMultiContainer containerId={TOAST_CONTAINER_ID}
position="bottom-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover />
<ReplayRequestModalContainer />
</RecoilRoot>
}
export default TrafficViewerContainer

View File

@ -1,12 +0,0 @@
type TrafficViewerApi = {
validateQuery: (query: any) => any
tapStatus: () => any
fetchEntries: (leftOff: any, direction: number, query: any, limit: number, timeoutMs: number) => any
getEntry: (entryId: any, query: string) => any,
replayRequest: (request: { method: string, url: string, data: string, headers: {} }) => Promise<any>,
webSocket: {
close: () => void
}
}
export default TrafficViewerApi

View File

@ -1,5 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="15" fill="#F7B202"/>
<rect x="11" y="9" width="2" height="11" rx="1" fill="white"/>
<rect x="17" y="9" width="2" height="11" rx="1" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 283 B

View File

@ -1,4 +0,0 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15" cy="15" r="13.5" stroke="#205CF5" stroke-width="3"/>
<path d="M20 15C20 15.3167 19.8392 15.6335 19.5175 15.8189L12.5051 19.8624C11.8427 20.2444 11 19.7858 11 19.0435V10.9565C11 10.2142 11.8427 9.75564 12.5051 10.1376L19.5175 14.1811C19.8392 14.3665 20 14.6833 20 15Z" fill="#205CF5"/>
</svg>

Before

Width:  |  Height:  |  Size: 404 B

View File

@ -1,18 +0,0 @@
import React from "react";
export interface Props {
checked: boolean;
onToggle: (checked:boolean) => any;
disabled?: boolean;
}
const Checkbox: React.FC<Props> = ({checked, onToggle, disabled, ...props}) => {
return (
<div>
<input style={!disabled ? {cursor: "pointer"}: {}} type="checkbox" checked={checked} disabled={disabled} onChange={(event) => onToggle(event.target.checked)} {...props}/>
</div>
);
};
export default Checkbox;

View File

@ -1,49 +0,0 @@
import React from "react";
import AceEditor from "react-ace";
import { config } from 'ace-builds';
import "ace-builds/src-noconflict/ext-searchbox";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/mode-xml";
import "ace-builds/src-noconflict/mode-html";
config.set(
"basePath",
"https://cdn.jsdelivr.net/npm/ace-builds@1.4.6/src-noconflict/"
);
config.setModuleUrl(
"ace/mode/javascript_worker",
"https://cdn.jsdelivr.net/npm/ace-builds@1.4.6/src-noconflict/worker-javascript.js"
);
export interface CodeEditorProps {
code: string,
onChange?: (code: string) => void,
language?: string
}
const CodeEditor: React.FC<CodeEditorProps> = ({
language,
onChange,
code
}) => {
return (
<AceEditor
mode={language}
theme="github"
onChange={onChange}
editorProps={{ $blockScrolling: true }}
showPrintMargin={false}
value={code}
width="100%"
height="100%"
style={{ borderRadius: "inherit" }}
/>
);
}
export default CodeEditor

View File

@ -1,33 +0,0 @@
.CollapsibleContainerHeader
display: flex
align-items: center
cursor: pointer
min-height: 40px
.CollapsibleContainerHeaderSticky
background: #1f253f
position: sticky
top: 0
z-index: 1
.CollapsibleContainerBigger
padding: 10px
font-size: 14px !important
.CollapsibleContainerExpandCollapseButton
margin-left: auto
padding-right: 2%
.CollapsibleContainerExpanded
min-height: 40px
.CollapsibleContainerTitle
font-weight: 600
font-family: 'Source Sans Pro', sans-serif
font-size: 12px
.CollapsibleContainerExpanded .CollapsibleContainerTitle
color: rgba(186, 199, 255, 1)
.CollapsibleContainerCollapsed .CollapsibleContainerTitle
color: rgba(186, 199, 255, 0.75)

View File

@ -1,42 +0,0 @@
import React from "react";
import collapsedImg from "./assets/collapsed.svg";
import expandedImg from "./assets/expanded.svg";
import styles from "./CollapsibleContainer.module.sass";
interface Props {
title: string | React.ReactNode,
expanded: boolean,
titleClassName?: string,
className?: string,
stickyHeader?: boolean,
}
const CollapsibleContainer: React.FC<Props> = ({title, children, expanded, titleClassName, className, stickyHeader = false}) => {
const classNames = `${styles.CollapsibleContainer} ${expanded ? `${styles.CollapsibleContainerExpanded}` : `${styles.CollapsibleContainerCollapsed}`} ${className ? className : ''}`;
// This is needed to achieve the sticky header feature.
// It is needed an un-contained component for the css to work properly.
const content = <React.Fragment>
<div
className={`${styles.CollapsibleContainerHeader} ${stickyHeader ? `${styles.CollapsibleContainerHeaderSticky}` : ""}
${expanded ? `${styles.CollapsibleContainerHeaderExpanded}` : ""}`}>
{
React.isValidElement(title)?
<React.Fragment>{title}</React.Fragment> :
<React.Fragment>
<div className={`${styles.CollapsibleContainerTitle} ${titleClassName ? titleClassName : ''}`}>{title}</div>
<img
className={styles.CollapsibleContainerExpandCollapseButton}
src={expanded ? expandedImg : collapsedImg}
alt="Expand/Collapse Button"
/>
</React.Fragment>
}
</div>
{expanded ? children : null}
</React.Fragment>;
return stickyHeader ? content : <div className={classNames}>{content}</div>;
};
export default CollapsibleContainer;

View File

@ -1,14 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<defs>
<style>
.cls-1{fill:#fff;stroke:#707070;opacity:0}.cls-2{fill:#627ef7}.cls-3{stroke:none}.cls-4{fill:none}
</style>
</defs>
<g id="icon_down_diagonal" transform="translate(-1304 -166)">
<g id="Rectangle_924" class="cls-1" data-name="Rectangle 924">
<path d="M0 0h22v22H0z" class="cls-3" transform="translate(1304 166)"/>
<path d="M.5.5h21v21H.5z" class="cls-4" transform="translate(1304 166)"/>
</g>
<path id="icon_down" d="M5.117 0H3.07v3.07H0v2.047h5.117V0z" class="cls-2" transform="translate(1312.46 174.491)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 711 B

View File

@ -1,3 +0,0 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2427 11.8285L14.0711 9.00003L15.4853 10.4142L11.2427 14.6569L11.2426 14.6569L9.82846 13.2427L7 10.4142L8.41421 9L11.2427 11.8285Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 302 B

View File

@ -1,62 +0,0 @@
import React from 'react';
import { Modal, Backdrop, Fade, Box } from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import {useCommonStyles} from "../../../helpers/commonStyle";
const useStyles = makeStyles({
modal: {
display: "flex",
alignItems: "center",
justifyContent: "center"
},
modalContents: {
borderRadius: "5px",
outline: "none",
minWidth: "300px",
backgroundColor: "rgb(255, 255, 255)",
},
modalBackdrop :{
background : "rgba(24, 51, 121, 0.8)"
}
});
export interface CustomModalProps {
open: boolean;
children: React.ReactElement | React.ReactElement[];
disableBackdropClick?: boolean;
onClose?: () => void;
className?: string;
isPadding?: boolean;
isWide? :boolean;
}
const CustomModal: React.FunctionComponent<CustomModalProps> = ({ open = false, onClose, disableBackdropClick = false, children, className}) => {
const classes = useStyles({});
const globals = useCommonStyles().modal
const onModalClose = (reason) => {
if(reason === 'backdropClick' && disableBackdropClick)
return;
onClose();
}
return <Modal disableEnforceFocus open={open} onClose={(event, reason) => onModalClose(reason)}
className={`${classes.modal}`}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
className:`${classes.modalBackdrop}`
}}>
<div className={`${classes.modalContents} ${globals} ${className ? className : ''}`} >
<Fade in={open}>
<Box>
{children}
</Box>
</Fade>
</div>
</Modal>
}
export default CustomModal;

View File

@ -1,40 +0,0 @@
.FancyTextDisplayContainer
display: flex
align-items: center
&.displayIconOnMouseOver
.FancyTextDisplayIcon
opacity: 0
pointer-events: none
&:hover
.FancyTextDisplayIcon
opacity: 1
pointer-events: all
.FancyTextDisplayIcon
height: 22px
width: 22px
cursor: pointer
margin-right: 3px
&:hover
background-color: rgba(255, 255, 255, 0.06)
border-radius: 4px
&.FancyTextDisplayContainerEllipsis
.FancyTextDisplayText
text-align: left
text-overflow: ellipsis
overflow: hidden
white-space: nowrap
width: calc(100% - 30px)
.FancyTextDisplayCopyNotifier
background-color: #4252a5
padding: 2px 5px
border-radius: 4px
position: absolute
transform: translate(0, -80%)
color: white
z-index: 1000

View File

@ -1,63 +0,0 @@
import React, { useEffect, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import duplicateImg from "./assets/duplicate.svg";
import styles from './FancyTextDisplay.module.sass';
interface Props {
text: string | number,
className?: string,
isPossibleToCopy?: boolean,
applyTextEllipsis?: boolean,
flipped?: boolean,
useTooltip?: boolean,
displayIconOnMouseOver?: boolean,
buttonOnly?: boolean,
}
const FancyTextDisplay: React.FC<Props> = ({text, className, isPossibleToCopy = true, applyTextEllipsis = true, flipped = false, useTooltip= false, displayIconOnMouseOver = false, buttonOnly = false}) => {
const [showCopiedNotification, setCopied] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
text = String(text);
const onCopy = () => {
setCopied(true)
};
useEffect(() => {
let timer;
if (showCopiedNotification) {
timer = setTimeout(() => {
setCopied(false);
}, 1000);
}
return () => clearTimeout(timer);
}, [showCopiedNotification]);
const textElement = <span className={styles.FancyTextDisplayText}>{text}</span>;
const copyButton = isPossibleToCopy && text ? <CopyToClipboard text={text} onCopy={onCopy}>
<span
className={styles.FancyTextDisplayIcon}
title={`Copy "${text}" value to clipboard`}
>
<img src={duplicateImg} alt="Duplicate full value"/>
{showCopiedNotification && <span className={styles.FancyTextDisplayCopyNotifier}>Copied</span>}
</span>
</CopyToClipboard> : null;
return (
<p
className={`${styles.FancyTextDisplayContainer} ${className ? className : ''} ${displayIconOnMouseOver ? ` ${styles.displayIconOnMouseOver} ` : ''} ${applyTextEllipsis ? ` ${styles.FancyTextDisplayContainerEllipsis} `: ''}`}
title={text}
onMouseOver={ e => setShowTooltip(true)}
onMouseLeave={ e => setShowTooltip(false)}
>
{!buttonOnly && flipped && textElement}
{copyButton}
{!buttonOnly && !flipped && textElement}
{useTooltip && showTooltip && <span className={styles.FancyTextDisplayCopyNotifier}>{text}</span>}
</p>
);
};
export default FancyTextDisplay;

View File

@ -1,20 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<defs>
<style>
.cls-1,.cls-2,.cls-5{fill:#fff}.cls-1{opacity:0}.cls-3,.cls-6{fill:none}.cls-3{stroke:#8f9bb2}.cls-4,.cls-5{stroke:none}
</style>
</defs>
<g id="duplicate" transform="rotate(180 11 11)">
<path id="Rectangle_1845" d="M0 0h22v22H0z" class="cls-1" data-name="Rectangle 1845"/>
<g id="Group_4275" data-name="Group 4275" transform="translate(4 4)">
<g id="Subtraction_10" class="cls-2" data-name="Subtraction 10">
<path d="M5 8.498H2c-.827 0-1.5-.673-1.5-1.5v-5c0-.827.673-1.5 1.5-1.5h.611v2.409c0 1.378 1.122 2.5 2.5 2.5h1.39v1.591c0 .827-.674 1.5-1.5 1.5z" class="cls-4" transform="translate(1 5.202)"/>
<path d="M5 7.998a1 1 0 0 0 1-1V5.907h-.889c-1.654 0-3-1.346-3-3V.998H2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h3m0 1H2c-1.103 0-2-.897-2-2v-5c0-1.103.897-2 2-2H3.11v2.909c0 1.103.897 2 2 2h1.89v2.091c0 1.103-.898 2-2 2z" class="cls-5" transform="translate(1 5.202)"/>
</g>
<g id="Rectangle_680" class="cls-3" data-name="Rectangle 680" transform="translate(4)">
<rect width="9" height="11" class="cls-4" rx="2"/>
<rect width="8" height="10" x=".5" y=".5" class="cls-6" rx="1.5"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,33 +0,0 @@
import React from 'react';
import { useEffect } from 'react';
import { useFilePicker } from 'use-file-picker';
import { FileContent } from 'use-file-picker/dist/interfaces';
interface IFilePickerProps {
onLoadingComplete: (file: FileContent) => void;
elem: any
}
const FilePicker = ({ elem, onLoadingComplete }: IFilePickerProps) => {
const [openFileSelector, { filesContent }] = useFilePicker({
accept: ['.json'],
limitFilesConfig: { max: 1 },
maxFileSize: 1
});
const onFileSelectorClick = (e) => {
e.preventDefault();
e.stopPropagation();
openFileSelector();
}
useEffect(() => {
filesContent.length && onLoadingComplete(filesContent[0])
}, [filesContent, onLoadingComplete]);
return (<React.Fragment>
{React.cloneElement(elem, { onClick: onFileSelectorClick })}
</React.Fragment>)
}
export default FilePicker;

View File

@ -1,51 +0,0 @@
import React from "react";
export type HoverImageProps = {
src: string;
hoverSrc: string;
disabled?: boolean;
className?: string;
style?: any;
onClick?: React.MouseEventHandler;
alt?: string
};
const HoverImage: React.FC<HoverImageProps> = ({
src,
hoverSrc,
style,
disabled,
onClick,
className,
alt = ""
}) => {
const [imageSrc, setImageSrc] = React.useState<string>(src);
const mouseOver = React.useCallback(() => {
setImageSrc(hoverSrc);
}, [hoverSrc]);
const mouseOut = React.useCallback(() => {
setImageSrc(src);
}, [src]);
const handleClick = (e: React.MouseEvent) => {
if (!onClick) return;
if (!disabled) {
onClick(e);
}
};
return (
<img
src={imageSrc}
style={style}
onMouseOver={mouseOver}
onMouseOut={mouseOut}
onClick={handleClick}
className={className}
alt={alt}
/>
);
};
export default HoverImage;

View File

@ -1,8 +0,0 @@
.linkStyle
display: flex
color: #18253d
text-decoration: none
font-family: "Ubuntu", sans-serif
font-style: normal
font-weight: 600
font-size: 14px

View File

@ -1,23 +0,0 @@
import React from "react";
import styles from "./InformationIcon.module.sass"
const DEFUALT_LINK = "https://docs.kubeshark.co"
interface LinkProps {
link?: string,
className?: string,
title?: string,
children?: React.ReactNode
}
export const Link: React.FC<LinkProps> = ({ link, className, title, children }) => {
return <a href={link} className={className} title={title} target="_blank" rel="noreferrer">
{children}
</a>
}
export const InformationIcon: React.FC<LinkProps> = ({ className }) => {
return <Link title="documentation" className={`${styles.linkStyle} ${className}`} link={DEFUALT_LINK}>
<span>Docs</span>
</Link>
}

View File

@ -1,29 +0,0 @@
@import '../../../variables.module'
.keyValueTableContainer
width: 100%
background-color: inherit
border-radius: 4px
overflow-x: auto
overflow-y: auto
height: 100%
padding: 10px 0
box-sizing: border-box
.headerRow
display: flex
margin: 15px
align-items: center
.roundInputContainer
background-color: $main-background-color
border-radius: 15px
margin-right: 5px
padding: 5px
input
border: none
outline: none
background-color: transparent
font-size: 15px
width: 100%

View File

@ -1,80 +0,0 @@
import React from "react";
import { useEffect, useState } from "react";
import styles from "./KeyValueTable.module.sass"
import deleteIcon from "./assets/delete.svg"
import deleteIconActive from "./assets/delete-active.svg"
import HoverImage from "../HoverImage/HoverImage";
interface KeyValueTableProps {
data: any
onDataChange: (data: any) => void
keyPlaceholder?: string
valuePlaceholder?: string
}
type Row = { key: string, value: string }
const KeyValueTable: React.FC<KeyValueTableProps> = ({ data, onDataChange, keyPlaceholder, valuePlaceholder }) => {
const [keyValueData, setKeyValueData] = useState([] as Row[])
useEffect(() => {
if (!data) return;
const currentState = [...data, { key: "", value: "" }]
setKeyValueData(currentState)
}, [data])
const deleteRow = (index) => {
const newRows = [...keyValueData];
newRows.splice(index, 1);
setKeyValueData(newRows);
onDataChange(newRows.filter(row => row.key))
}
const addNewRow = (data: Row[]) => {
return data.filter(x => x.key === "").length === 0 ? [...data, { key: '', value: '' }] : data
}
const setNewVal = (mapFunc, index) => {
let currentData = keyValueData.map((row, i) => i === index ? mapFunc(row) : row)
if (currentData.every(row => row.key)) {
onDataChange(currentData)
currentData = addNewRow(currentData)
}
else {
onDataChange(currentData.filter(row => row.key))
}
setKeyValueData(currentData);
}
return <div className={styles.keyValueTableContainer}>
{keyValueData?.map((row, index) => {
return <div key={index} className={styles.headerRow}>
<div className={styles.roundInputContainer} style={{ width: "30%" }}>
<input
name="key" type="text"
placeholder={keyPlaceholder ? keyPlaceholder : "New key"}
onChange={(event) => setNewVal((row) => { return { key: event.target.value, value: row.value } }, index)}
value={row.key}
autoComplete="off"
spellCheck={false} />
</div>
<div className={styles.roundInputContainer} style={{ width: "65%" }}>
<input
name="value" type="text"
placeholder={valuePlaceholder ? valuePlaceholder : "New Value"}
onChange={(event) => setNewVal((row) => { return { key: row.key, value: event.target.value } }, index)}
value={row.value?.toString()}
autoComplete="off"
spellCheck={false} />
</div>
{(row.key !== "" || row.value !== "") && <HoverImage alt="delete" style={{ marginLeft: "5px", cursor: "pointer" }} className="deleteIcon" src={deleteIcon}
onClick={() => deleteRow(index)} hoverSrc={deleteIconActive} />}
</div>
})}
</div>
}
export const convertParamsToArr = (paramsObj) => Object.entries(paramsObj).map(([key, value]) => { return { key, value } })
export const convertArrToKeyValueObject = (arr) => arr.reduce((acc, curr) => { acc[curr.key] = curr.value; return acc }, {})
export default KeyValueTable

View File

@ -1,3 +0,0 @@
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9166 4C15.9166 4.20411 15.8416 4.40111 15.706 4.55364C15.5704 4.70617 15.3835 4.80362 15.1808 4.8275L15.0833 4.83333H14.3791L13.3533 15.2667C13.2974 15.8328 13.033 16.3579 12.6114 16.7399C12.1899 17.1219 11.6413 17.3334 11.0724 17.3333H4.92742C4.35854 17.3334 3.80998 17.1219 3.38842 16.7399C2.96686 16.3579 2.70244 15.8328 2.64659 15.2667L1.62075 4.83333H0.916585C0.695572 4.83333 0.48361 4.74554 0.32733 4.58926C0.171049 4.43298 0.083252 4.22101 0.083252 4C0.083252 3.77899 0.171049 3.56702 0.32733 3.41074C0.48361 3.25446 0.695572 3.16667 0.916585 3.16667H5.08325C5.08325 2.78364 5.15869 2.40437 5.30527 2.05051C5.45185 1.69664 5.66669 1.37511 5.93752 1.10427C6.20836 0.833434 6.52989 0.618594 6.88376 0.472018C7.23763 0.325442 7.6169 0.25 7.99992 0.25C8.38294 0.25 8.76221 0.325442 9.11608 0.472018C9.46994 0.618594 9.79148 0.833434 10.0623 1.10427C10.3332 1.37511 10.548 1.69664 10.6946 2.05051C10.8411 2.40437 10.9166 2.78364 10.9166 3.16667H15.0833C15.3043 3.16667 15.5162 3.25446 15.6725 3.41074C15.8288 3.56702 15.9166 3.77899 15.9166 4ZM9.87492 6.70833C9.72389 6.70834 9.57797 6.76304 9.46414 6.86231C9.35032 6.96158 9.27629 7.09871 9.25575 7.24833L9.24992 7.33333V13.1667L9.25575 13.2517C9.27633 13.4013 9.35038 13.5383 9.4642 13.6376C9.57802 13.7368 9.72392 13.7915 9.87492 13.7915C10.0259 13.7915 10.1718 13.7368 10.2856 13.6376C10.3995 13.5383 10.4735 13.4013 10.4941 13.2517L10.4999 13.1667V7.33333L10.4941 7.24833C10.4735 7.09871 10.3995 6.96158 10.2857 6.86231C10.1719 6.76304 10.0259 6.70834 9.87492 6.70833ZM6.12492 6.70833C5.97389 6.70834 5.82797 6.76304 5.71414 6.86231C5.60032 6.96158 5.52629 7.09871 5.50575 7.24833L5.49992 7.33333V13.1667L5.50575 13.2517C5.52633 13.4013 5.60038 13.5383 5.7142 13.6376C5.82802 13.7368 5.97392 13.7915 6.12492 13.7915C6.27592 13.7915 6.42182 13.7368 6.53564 13.6376C6.64946 13.5383 6.7235 13.4013 6.74409 13.2517L6.74992 13.1667V7.33333L6.74409 7.24833C6.72355 7.09871 6.64952 6.96158 6.53569 6.86231C6.42187 6.76304 6.27595 6.70834 6.12492 6.70833ZM7.99992 1.91667C7.6684 1.91667 7.35046 2.04836 7.11603 2.28278C6.88161 2.5172 6.74992 2.83515 6.74992 3.16667H9.24992C9.24992 2.83515 9.11822 2.5172 8.8838 2.28278C8.64938 2.04836 8.33144 1.91667 7.99992 1.91667Z" fill="#DB2156"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,3 +0,0 @@
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9166 4C15.9166 4.20411 15.8416 4.40111 15.706 4.55364C15.5704 4.70617 15.3835 4.80362 15.1808 4.8275L15.0833 4.83333H14.3791L13.3533 15.2667C13.2974 15.8328 13.033 16.3579 12.6114 16.7399C12.1899 17.1219 11.6413 17.3334 11.0724 17.3333H4.92742C4.35854 17.3334 3.80998 17.1219 3.38842 16.7399C2.96686 16.3579 2.70244 15.8328 2.64659 15.2667L1.62075 4.83333H0.916585C0.695572 4.83333 0.48361 4.74554 0.32733 4.58926C0.171049 4.43298 0.083252 4.22101 0.083252 4C0.083252 3.77899 0.171049 3.56702 0.32733 3.41074C0.48361 3.25446 0.695572 3.16667 0.916585 3.16667H5.08325C5.08325 2.78364 5.15869 2.40437 5.30527 2.05051C5.45185 1.69664 5.66669 1.37511 5.93752 1.10427C6.20836 0.833434 6.52989 0.618594 6.88376 0.472018C7.23763 0.325442 7.6169 0.25 7.99992 0.25C8.38294 0.25 8.76221 0.325442 9.11608 0.472018C9.46994 0.618594 9.79148 0.833434 10.0623 1.10427C10.3332 1.37511 10.548 1.69664 10.6946 2.05051C10.8411 2.40437 10.9166 2.78364 10.9166 3.16667H15.0833C15.3043 3.16667 15.5162 3.25446 15.6725 3.41074C15.8288 3.56702 15.9166 3.77899 15.9166 4ZM9.87492 6.70833C9.72389 6.70834 9.57797 6.76304 9.46414 6.86231C9.35032 6.96158 9.27629 7.09871 9.25575 7.24833L9.24992 7.33333V13.1667L9.25575 13.2517C9.27633 13.4013 9.35038 13.5383 9.4642 13.6376C9.57802 13.7368 9.72392 13.7915 9.87492 13.7915C10.0259 13.7915 10.1718 13.7368 10.2856 13.6376C10.3995 13.5383 10.4735 13.4013 10.4941 13.2517L10.4999 13.1667V7.33333L10.4941 7.24833C10.4735 7.09871 10.3995 6.96158 10.2857 6.86231C10.1719 6.76304 10.0259 6.70834 9.87492 6.70833ZM6.12492 6.70833C5.97389 6.70834 5.82797 6.76304 5.71414 6.86231C5.60032 6.96158 5.52629 7.09871 5.50575 7.24833L5.49992 7.33333V13.1667L5.50575 13.2517C5.52633 13.4013 5.60038 13.5383 5.7142 13.6376C5.82802 13.7368 5.97392 13.7915 6.12492 13.7915C6.27592 13.7915 6.42182 13.7368 6.53564 13.6376C6.64946 13.5383 6.7235 13.4013 6.74409 13.2517L6.74992 13.1667V7.33333L6.74409 7.24833C6.72355 7.09871 6.64952 6.96158 6.53569 6.86231C6.42187 6.76304 6.27595 6.70834 6.12492 6.70833ZM7.99992 1.91667C7.6684 1.91667 7.35046 2.04836 7.11603 2.28278C6.88161 2.5172 6.74992 2.83515 6.74992 3.16667H9.24992C9.24992 2.83515 9.11822 2.5172 8.8838 2.28278C8.64938 2.04836 8.33144 1.91667 7.99992 1.91667Z" fill="#8F9BB2"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,55 +0,0 @@
.loadingOverlayContainer
position: absolute
display: flex
width: 100%
height: 100%
top: 0
left: 0
align-items: center
justify-content: center
background-color: rgba(25, 25, 25, 0.5)
z-index: 1000
.loadingOverlaySpinner
width: 60px
height: 60px
top: 50%
left: 50%
z-index: 1000
-webkit-animation: rotation .6s infinite linear
-moz-animation: rotation .6s infinite linear
-o-animation: rotation .6s infinite linear
animation: rotation .6s infinite linear
border-left: 6px solid rgba(0, 174, 239, 0.15)
border-right: 6px solid rgba(0, 174, 239, 0.15)
border-bottom: 6px solid rgba(0, 174, 239, 0.15)
border-top: 6px solid rgba(0, 174, 239, 0.8)
border-radius: 100%
@-webkit-keyframes rotation
from
-webkit-transform: rotate(0deg)
to
-webkit-transform: rotate(359deg)
@-moz-keyframes rotation
from
-moz-transform: rotate(0deg)
to
-moz-transform: rotate(359deg)
@-o-keyframes rotation
from
-o-transform: rotate(0deg)
to
-o-transform: rotate(359deg)
@keyframes rotation
from
transform: rotate(0deg)
to
transform: rotate(359deg)

View File

@ -1,31 +0,0 @@
import React, {useEffect, useState} from "react";
import style from './LoadingOverlay.module.sass';
const SpinnerShowDelayMs = 350;
interface LoadingOverlayProps {
delay?: number
}
const LoadingOverlay: React.FC<LoadingOverlayProps> = ({delay}) => {
const [isVisible, setIsVisible] = useState(false);
// @ts-ignore
useEffect(() => {
let isRelevant = true;
setTimeout(() => {
if(isRelevant)
setIsVisible(true);
}, delay || SpinnerShowDelayMs);
return () => isRelevant = false;
}, [delay]);
return <div className={style.loadingOverlayContainer} hidden={!isVisible}>
<div className={style.loadingOverlaySpinner}/>
</div>
};
export default LoadingOverlay;

View File

@ -1,32 +0,0 @@
@import '../../../variables.module'
.messageContainer
width: 100%
margin-top: 20px
&__noData
display: flex
justify-content: space-between
flex-direction: column
height: 95px
margin: 2%
align-items: center
align-content: center
padding-top: 3%
padding-bottom: 3%
& .container
display: flex
justify-content: space-between
flex-direction: column
height: 95px
margin: 1%
align-items: center
align-content: center
&-message
font-style: normal
font-weight: 600
font-size: 12px
line-height: 15px
color: $light-gray

View File

@ -1,20 +0,0 @@
import React from "react";
import circleImg from "./assets/dotted-circle.svg";
import styles from "./NoDataMessage.module.sass"
export interface Props {
messageText: string;
}
const NoDataMessage: React.FC<Props> = ({ messageText = "No data found" }) => {
return (
<div data-cy="noDataMessage" className={styles.messageContainer__noData}>
<div className={styles.container}>
<img src={circleImg} alt="No data Found"/>
<div className={styles.messageContainer__noDataMessage}>{messageText}</div>
</div>
</div>
);
};
export default NoDataMessage;

View File

@ -1,3 +0,0 @@
<svg width="55" height="55" viewBox="0 0 55 55" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="27.5" cy="27.5" r="27" stroke="#BCCEFD" stroke-dasharray="6 6"/>
</svg>

Before

Width:  |  Height:  |  Size: 180 B

View File

@ -1,24 +0,0 @@
.base
display: inline-block
text-align: center
font-size: 10px
font-weight: 600
background-color: #000
color: #fff
margin-left: -8px
.vertical
line-height: 22px
letter-spacing: 0.5px
width: 22px
height: 48px
border-radius: 0px 4px 4px 0
writing-mode: vertical-lr
transform: rotate(-180deg)
text-orientation: mixed
.horizontal
border-radius: 4px
font-size: 22px
padding: 5px 10px
font-weight: 600

View File

@ -1,67 +0,0 @@
import React from "react";
import styles from './Protocol.module.sass';
import Queryable from "../Queryable/Queryable";
export interface ProtocolInterface {
name: string
longName: string
abbr: string
macro: string
backgroundColor: string
foregroundColor: string
fontSize: number
referenceLink: string
ports: string[]
inbound_ports: string
}
interface ProtocolProps {
protocol: ProtocolInterface
horizontal: boolean
}
const Protocol: React.FC<ProtocolProps> = ({protocol, horizontal}) => {
if (horizontal) {
return <Queryable
query={protocol.macro}
displayIconOnMouseOver={true}
>
<a target="_blank" rel="noopener noreferrer" href={protocol.referenceLink}>
<span
className={`${styles.base} ${styles.horizontal}`}
style={{
backgroundColor: protocol.backgroundColor,
color: protocol.foregroundColor,
fontSize: 13,
}}
title={protocol.abbr}
>
{protocol.longName}
</span>
</a>
</Queryable>
} else {
return <Queryable
query={protocol.macro}
displayIconOnMouseOver={true}
flipped={false}
iconStyle={{marginTop: "52px", marginRight: "10px", zIndex: 1000}}
tooltipStyle={{marginTop: "-22px", zIndex: 1001}}
>
<span
className={`${styles.base} ${styles.vertical}`}
style={{
backgroundColor: protocol.backgroundColor,
color: protocol.foregroundColor,
fontSize: protocol.fontSize,
marginRight: "-6px",
}}
title={protocol.longName}
>
{protocol.abbr}
</span>
</Queryable>
}
};
export default Protocol;

View File

@ -1,47 +0,0 @@
.QueryableContainer
display: flex
align-items: center
&.displayIconOnMouseOver
.QueryableIcon
opacity: 0
width: 0px
pointer-events: none
&:hover
.QueryableIcon
opacity: 1
pointer-events: all
.QueryableIcon
height: 22px
width: 22px
cursor: pointer
color: #27AE60
&:hover
background-color: rgba(255, 255, 255, 0.06)
border-radius: 4px
color: #1E884B
.QueryableAddNotifier
background-color: #1E884B
font-weight: normal
padding: 2px 5px
border-radius: 4px
position: absolute
transform: translate(0, 10%)
color: white
z-index: 1000
font-size: 11px
.QueryableTooltip
background-color: #1E884B
font-weight: normal
padding: 2px 5px
border-radius: 4px
position: absolute
transform: translate(0, -80%)
color: white
z-index: 1000
font-size: 11px

Some files were not shown because too many files have changed in this diff Show More