🔥 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
23
.github/workflows/linter.yml
vendored
@ -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
|
||||
|
11
Dockerfile
@ -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"]
|
||||
|
@ -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 == "" {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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},
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
REACT_APP_OVERRIDE_WS_URL="ws://localhost:8899/ws"
|
||||
REACT_APP_OVERRIDE_API_URL="http://localhost:8899/"
|
@ -1,5 +0,0 @@
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
.snapshots/
|
||||
*.min.js
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
}
|
138
ui/.snyk
@ -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
|
@ -1,4 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 12
|
||||
- 10
|
@ -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
109
ui/package.json
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
@ -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 |
@ -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>
|
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 9.4 KiB |
@ -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"
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,9 +0,0 @@
|
||||
@import './variables.module'
|
||||
|
||||
body
|
||||
background-color: $main-background-color
|
||||
|
||||
.kubesharkApp
|
||||
color: $font-color
|
||||
width: 100%
|
||||
height: 100%
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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>}
|
||||
</>;
|
||||
}
|
@ -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
|
@ -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>;
|
||||
};
|
@ -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 |
@ -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 |
@ -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>
|
||||
};
|
@ -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
|
@ -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>
|
||||
}
|
@ -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>;
|
||||
}
|
@ -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
|
@ -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;
|
@ -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
|
@ -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 |
@ -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 |
@ -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 |
@ -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
|
@ -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>
|
||||
|
||||
}
|
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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
|
@ -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>
|
||||
}
|
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 24 KiB |
@ -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
|
@ -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>;
|
||||
}
|
@ -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 |
@ -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
|
||||
}} />
|
||||
</>
|
||||
);
|
||||
};
|
@ -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 |
@ -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 |
@ -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 |
@ -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
|
@ -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
|
@ -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
|
@ -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 |
@ -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 |
@ -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;
|
@ -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
|
@ -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)
|
@ -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;
|
@ -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 |
@ -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 |
@ -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;
|
@ -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
|
@ -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;
|
@ -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 |
@ -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;
|
@ -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;
|
@ -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
|
@ -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>
|
||||
}
|
@ -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%
|
@ -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
|
@ -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 |
@ -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 |
@ -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)
|
@ -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;
|
@ -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
|
@ -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;
|
@ -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 |
@ -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
|
@ -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;
|
@ -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
|