mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-03-07 04:00:45 +00:00
Compare commits
12 Commits
31.0-dev24
...
31.0-dev36
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df1fd2c3a7 | ||
|
|
f8496c0235 | ||
|
|
2de7107c0a | ||
|
|
22e3b3d8b2 | ||
|
|
45611c4c13 | ||
|
|
bb425fa6e2 | ||
|
|
4bc83ebcb5 | ||
|
|
bbb44dae79 | ||
|
|
72a1aba3e5 | ||
|
|
d8fb8ff710 | ||
|
|
f344bd2633 | ||
|
|
6575495fa5 |
2
.github/workflows/acceptance_tests.yml
vendored
2
.github/workflows/acceptance_tests.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
notification_title: 'Mizu {workflow} has {status_message}'
|
||||
message_format: '{emoji} *{workflow}* {status_message} during <{run_url}|run>, after commit <{commit_url}|{commit_sha}> by ${{ github.event.head_commit.committer.name }} <${{ github.event.head_commit.committer.email }}> ```${{ github.event.head_commit.message }}```'
|
||||
message_format: '{emoji} *{workflow}* {status_message} during <{run_url}|run>, after commit <{commit_url}|{commit_sha}> by ${{ github.event.head_commit.author.name }} <${{ github.event.head_commit.author.email }}> ```${{ github.event.head_commit.message }}```'
|
||||
footer: 'Linked Repo <{repo_url}|{repo}>'
|
||||
notify_when: 'failure'
|
||||
env:
|
||||
|
||||
@@ -15,6 +15,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.10.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/nav-inc/datetime v0.1.3
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
|
||||
@@ -428,6 +428,8 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
|
||||
@@ -199,7 +199,7 @@ func runInHarReaderMode() {
|
||||
func enableExpFeatureIfNeeded() {
|
||||
if config.Config.OAS {
|
||||
oasGenerator := dependency.GetInstance(dependency.OasGeneratorDependency).(oas.OasGenerator)
|
||||
oasGenerator.Start()
|
||||
oasGenerator.Start(nil)
|
||||
}
|
||||
if config.Config.ServiceMap {
|
||||
serviceMapGenerator := dependency.GetInstance(dependency.ServiceMapGeneratorDependency).(servicemap.ServiceMap)
|
||||
@@ -371,7 +371,7 @@ func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) {
|
||||
|
||||
func initializeDependencies() {
|
||||
dependency.RegisterGenerator(dependency.ServiceMapGeneratorDependency, func() interface{} { return servicemap.GetDefaultServiceMapInstance() })
|
||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance(nil) })
|
||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
||||
dependency.RegisterGenerator(dependency.EntriesProvider, func() interface{} { return &entries.BasenineEntriesProvider{} })
|
||||
dependency.RegisterGenerator(dependency.EntriesSocketStreamer, func() interface{} { return &api.BasenineEntryStreamer{} })
|
||||
dependency.RegisterGenerator(dependency.EntryStreamerSocketConnector, func() interface{} { return &api.DefaultEntryStreamerSocketConnector{} })
|
||||
|
||||
@@ -58,12 +58,12 @@ func getRecorderAndContext() (*httptest.ResponseRecorder, *gin.Context) {
|
||||
receiveBuffer: bytes.NewBufferString("\n"),
|
||||
}
|
||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} {
|
||||
return oas.GetDefaultOasGeneratorInstance(dummyConn)
|
||||
return oas.GetDefaultOasGeneratorInstance()
|
||||
})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
oas.GetDefaultOasGeneratorInstance(dummyConn).Start()
|
||||
oas.GetDefaultOasGeneratorInstance(dummyConn).GetServiceSpecs().Store("some", oas.NewGen("some"))
|
||||
oas.GetDefaultOasGeneratorInstance().Start(dummyConn)
|
||||
oas.GetDefaultOasGeneratorInstance().GetServiceSpecs().Store("some", oas.NewGen("some"))
|
||||
return recorder, c
|
||||
}
|
||||
|
||||
@@ -19,12 +19,11 @@ var (
|
||||
)
|
||||
|
||||
type OasGenerator interface {
|
||||
Start()
|
||||
Start(conn *basenine.Connection)
|
||||
Stop()
|
||||
IsStarted() bool
|
||||
Reset()
|
||||
GetServiceSpecs() *sync.Map
|
||||
SetEntriesQuery(query string)
|
||||
SetEntriesQuery(query string) bool
|
||||
}
|
||||
|
||||
type defaultOasGenerator struct {
|
||||
@@ -36,23 +35,41 @@ type defaultOasGenerator struct {
|
||||
entriesQuery string
|
||||
}
|
||||
|
||||
func GetDefaultOasGeneratorInstance(conn *basenine.Connection) *defaultOasGenerator {
|
||||
func GetDefaultOasGeneratorInstance() *defaultOasGenerator {
|
||||
syncOnce.Do(func() {
|
||||
instance = NewDefaultOasGenerator(conn)
|
||||
instance = NewDefaultOasGenerator()
|
||||
logger.Log.Debug("OAS Generator Initialized")
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
func (g *defaultOasGenerator) Start() {
|
||||
func (g *defaultOasGenerator) Start(conn *basenine.Connection) {
|
||||
if g.started {
|
||||
return
|
||||
}
|
||||
|
||||
if g.dbConn == nil {
|
||||
if conn == nil {
|
||||
logger.Log.Infof("Creating new DB connection for OAS generator to address %s:%s", shared.BasenineHost, shared.BaseninePort)
|
||||
newConn, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||
if err != nil {
|
||||
logger.Log.Error("Error connecting to DB for OAS generator, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
conn = newConn
|
||||
}
|
||||
|
||||
g.dbConn = conn
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g.cancel = cancel
|
||||
g.ctx = ctx
|
||||
g.serviceSpecs = &sync.Map{}
|
||||
|
||||
g.started = true
|
||||
|
||||
go g.runGenerator()
|
||||
}
|
||||
|
||||
@@ -60,8 +77,15 @@ func (g *defaultOasGenerator) Stop() {
|
||||
if !g.started {
|
||||
return
|
||||
}
|
||||
|
||||
if g.dbConn != nil {
|
||||
g.dbConn.Close()
|
||||
g.dbConn = nil
|
||||
}
|
||||
|
||||
g.cancel()
|
||||
g.Reset()
|
||||
g.reset()
|
||||
|
||||
g.started = false
|
||||
}
|
||||
|
||||
@@ -70,7 +94,7 @@ func (g *defaultOasGenerator) IsStarted() bool {
|
||||
}
|
||||
|
||||
func (g *defaultOasGenerator) runGenerator() {
|
||||
// Make []byte channels to recieve the data and the meta
|
||||
// Make []byte channels to receive the data and the meta
|
||||
dataChan := make(chan []byte)
|
||||
metaChan := make(chan []byte)
|
||||
|
||||
@@ -81,6 +105,8 @@ func (g *defaultOasGenerator) runGenerator() {
|
||||
select {
|
||||
case <-g.ctx.Done():
|
||||
logger.Log.Infof("OAS Generator was canceled")
|
||||
close(dataChan)
|
||||
close(metaChan)
|
||||
return
|
||||
|
||||
case metaBytes, ok := <-metaChan:
|
||||
@@ -168,7 +194,7 @@ func (g *defaultOasGenerator) getGen(dest string, urlStr string) *SpecGen {
|
||||
return gen
|
||||
}
|
||||
|
||||
func (g *defaultOasGenerator) Reset() {
|
||||
func (g *defaultOasGenerator) reset() {
|
||||
g.serviceSpecs = &sync.Map{}
|
||||
}
|
||||
|
||||
@@ -176,25 +202,18 @@ func (g *defaultOasGenerator) GetServiceSpecs() *sync.Map {
|
||||
return g.serviceSpecs
|
||||
}
|
||||
|
||||
func (g *defaultOasGenerator) SetEntriesQuery(query string) {
|
||||
func (g *defaultOasGenerator) SetEntriesQuery(query string) bool {
|
||||
changed := g.entriesQuery != query
|
||||
g.entriesQuery = query
|
||||
return changed
|
||||
}
|
||||
|
||||
func NewDefaultOasGenerator(conn *basenine.Connection) *defaultOasGenerator {
|
||||
if conn == nil {
|
||||
logger.Log.Infof("Creating new DB connection for OAS generator to address %s:%s", shared.BasenineHost, shared.BaseninePort)
|
||||
newConn, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conn = newConn
|
||||
}
|
||||
|
||||
func NewDefaultOasGenerator() *defaultOasGenerator {
|
||||
return &defaultOasGenerator{
|
||||
started: false,
|
||||
ctx: nil,
|
||||
cancel: nil,
|
||||
serviceSpecs: nil,
|
||||
dbConn: conn,
|
||||
dbConn: nil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,11 @@ package oas
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/up9inc/mizu/agent/pkg/har"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOASGen(t *testing.T) {
|
||||
gen := new(defaultOasGenerator)
|
||||
gen.dbConn = GetFakeDBConn(`{"startedDateTime": "20000101","request": {"url": "https://host/path", "method": "GET"}, "response": {"status": 200}}`)
|
||||
gen.serviceSpecs = &sync.Map{}
|
||||
|
||||
e := new(har.Entry)
|
||||
err := json.Unmarshal([]byte(`{"startedDateTime": "20000101","request": {"url": "https://host/path", "method": "GET"}, "response": {"status": 200}}`), e)
|
||||
@@ -22,7 +19,9 @@ func TestOASGen(t *testing.T) {
|
||||
Destination: "some",
|
||||
Entry: *e,
|
||||
}
|
||||
gen.Start()
|
||||
|
||||
dummyConn := GetFakeDBConn(`{"startedDateTime": "20000101","request": {"url": "https://host/path", "method": "GET"}, "response": {"status": 200}}`)
|
||||
gen.Start(dummyConn)
|
||||
gen.handleHARWithSource(ews)
|
||||
g, ok := gen.serviceSpecs.Load("some")
|
||||
if !ok {
|
||||
|
||||
@@ -61,8 +61,7 @@ func TestEntries(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
dummyConn := GetFakeDBConn("\n")
|
||||
gen := NewDefaultOasGenerator(dummyConn)
|
||||
gen := NewDefaultOasGenerator()
|
||||
gen.serviceSpecs = new(sync.Map)
|
||||
loadStartingOAS("test_artifacts/catalogue.json", "catalogue", gen.serviceSpecs)
|
||||
loadStartingOAS("test_artifacts/trcc.json", "trcc-api-service", gen.serviceSpecs)
|
||||
@@ -136,8 +135,7 @@ func TestEntries(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileSingle(t *testing.T) {
|
||||
dummyConn := GetFakeDBConn("\n")
|
||||
gen := NewDefaultOasGenerator(dummyConn)
|
||||
gen := NewDefaultOasGenerator()
|
||||
gen.serviceSpecs = new(sync.Map)
|
||||
// loadStartingOAS()
|
||||
file := "test_artifacts/params.har"
|
||||
@@ -227,8 +225,7 @@ func loadStartingOAS(file string, label string, specs *sync.Map) {
|
||||
}
|
||||
|
||||
func TestEntriesNegative(t *testing.T) {
|
||||
dummyConn := GetFakeDBConn("\n")
|
||||
gen := NewDefaultOasGenerator(dummyConn)
|
||||
gen := NewDefaultOasGenerator()
|
||||
gen.serviceSpecs = new(sync.Map)
|
||||
files := []string{"invalid"}
|
||||
_, err := feedEntries(files, false, gen)
|
||||
@@ -239,8 +236,7 @@ func TestEntriesNegative(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEntriesPositive(t *testing.T) {
|
||||
dummyConn := GetFakeDBConn("\n")
|
||||
gen := NewDefaultOasGenerator(dummyConn)
|
||||
gen := NewDefaultOasGenerator()
|
||||
gen.serviceSpecs = new(sync.Map)
|
||||
files := []string{"test_artifacts/params.har"}
|
||||
_, err := feedEntries(files, false, gen)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package servicemap
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/copier"
|
||||
"sync"
|
||||
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
@@ -183,8 +184,12 @@ func (s *defaultServiceMap) NewTCPEntry(src *tapApi.TCP, dst *tapApi.TCP, p *tap
|
||||
if len(src.Name) == 0 {
|
||||
srcEntry = &entryData{
|
||||
key: key(src.IP),
|
||||
entry: src,
|
||||
entry: &tapApi.TCP{},
|
||||
}
|
||||
if err := copier.Copy(srcEntry.entry, src); err != nil {
|
||||
logger.Log.Errorf("Error while copying src entry into src entry data")
|
||||
}
|
||||
|
||||
srcEntry.entry.Name = UnresolvedNodeName
|
||||
} else {
|
||||
srcEntry = &entryData{
|
||||
@@ -196,8 +201,12 @@ func (s *defaultServiceMap) NewTCPEntry(src *tapApi.TCP, dst *tapApi.TCP, p *tap
|
||||
if len(dst.Name) == 0 {
|
||||
dstEntry = &entryData{
|
||||
key: key(dst.IP),
|
||||
entry: dst,
|
||||
entry: &tapApi.TCP{},
|
||||
}
|
||||
if err := copier.Copy(dstEntry.entry, dst); err != nil {
|
||||
logger.Log.Errorf("Error while copying dst entry into dst entry data")
|
||||
}
|
||||
|
||||
dstEntry.entry.Name = UnresolvedNodeName
|
||||
} else {
|
||||
dstEntry = &entryData{
|
||||
|
||||
@@ -161,7 +161,7 @@ type Entry struct {
|
||||
Capture Capture `json:"capture"`
|
||||
Source *TCP `json:"src"`
|
||||
Destination *TCP `json:"dst"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Namespace string `json:"namespace"`
|
||||
Outgoing bool `json:"outgoing"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
|
||||
@@ -13,4 +13,4 @@ test-pull-bin:
|
||||
|
||||
test-pull-expect:
|
||||
@mkdir -p expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect5/amqp/\* expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect6/amqp/\* expect
|
||||
|
||||
@@ -13,4 +13,4 @@ test-pull-bin:
|
||||
|
||||
test-pull-expect:
|
||||
@mkdir -p expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect5/http/\* expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect6/http/\* expect
|
||||
|
||||
@@ -28,26 +28,6 @@ const protoMinorHTTP2 = 0
|
||||
|
||||
var maxHTTP2DataLen = 1 * 1024 * 1024 // 1MB
|
||||
|
||||
var grpcStatusCodes = []string{
|
||||
"OK",
|
||||
"CANCELLED",
|
||||
"UNKNOWN",
|
||||
"INVALID_ARGUMENT",
|
||||
"DEADLINE_EXCEEDED",
|
||||
"NOT_FOUND",
|
||||
"ALREADY_EXISTS",
|
||||
"PERMISSION_DENIED",
|
||||
"RESOURCE_EXHAUSTED",
|
||||
"FAILED_PRECONDITION",
|
||||
"ABORTED",
|
||||
"OUT_OF_RANGE",
|
||||
"UNIMPLEMENTED",
|
||||
"INTERNAL",
|
||||
"UNAVAILABLE",
|
||||
"DATA_LOSS",
|
||||
"UNAUTHENTICATED",
|
||||
}
|
||||
|
||||
type messageFragment struct {
|
||||
headers []hpack.HeaderField
|
||||
data []byte
|
||||
@@ -142,18 +122,8 @@ func (ga *Http2Assembler) readMessage() (streamID uint32, messageHTTP1 interface
|
||||
|
||||
// gRPC detection
|
||||
grpcStatus := headersHTTP1.Get("Grpc-Status")
|
||||
if grpcStatus != "" {
|
||||
if grpcStatus != "" || strings.Contains(headersHTTP1.Get("Content-Type"), "application/grpc") {
|
||||
isGrpc = true
|
||||
status = grpcStatus
|
||||
}
|
||||
|
||||
if strings.Contains(headersHTTP1.Get("Content-Type"), "application/grpc") {
|
||||
isGrpc = true
|
||||
grpcPath := headersHTTP1.Get(":path")
|
||||
pathSegments := strings.Split(grpcPath, "/")
|
||||
if len(pathSegments) > 0 {
|
||||
method = pathSegments[len(pathSegments)-1]
|
||||
}
|
||||
}
|
||||
|
||||
if method != "" {
|
||||
|
||||
@@ -248,11 +248,6 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
reqDetails["_queryStringMerged"] = mapSliceMergeRepeatedKeys(reqDetails["_queryString"].([]interface{}))
|
||||
reqDetails["queryString"] = mapSliceRebuildAsMap(reqDetails["_queryStringMerged"].([]interface{}))
|
||||
|
||||
statusCode := int(resDetails["status"].(float64))
|
||||
if item.Protocol.Abbreviation == "gRPC" {
|
||||
resDetails["statusText"] = grpcStatusCodes[statusCode]
|
||||
}
|
||||
|
||||
elapsedTime := item.Pair.Response.CaptureTime.Sub(item.Pair.Request.CaptureTime).Round(time.Millisecond).Milliseconds()
|
||||
if elapsedTime < 0 {
|
||||
elapsedTime = 0
|
||||
|
||||
@@ -13,4 +13,4 @@ test-pull-bin:
|
||||
|
||||
test-pull-expect:
|
||||
@mkdir -p expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect5/kafka/\* expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect6/kafka/\* expect
|
||||
|
||||
@@ -13,4 +13,4 @@ test-pull-bin:
|
||||
|
||||
test-pull-expect:
|
||||
@mkdir -p expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect5/redis/\* expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect6/redis/\* expect
|
||||
|
||||
@@ -26,15 +26,16 @@
|
||||
"@material-ui/core": "^4.11.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^12.20.10",
|
||||
"node-sass": "^6.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"recoil": "^0.5.2",
|
||||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^12.20.10"
|
||||
"react-dom": "^17.0.2",
|
||||
"recoil": "^0.5.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@types/lodash": "^4.14.179",
|
||||
"@uiw/react-textarea-code-editor": "^1.4.12",
|
||||
"axios": "^0.25.0",
|
||||
@@ -49,7 +50,6 @@
|
||||
"node-fetch": "^3.1.1",
|
||||
"numeral": "^2.0.6",
|
||||
"protobuf-decoder": "^0.1.0",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-graph-vis": "^1.0.7",
|
||||
"react-lowlight": "^3.0.0",
|
||||
"react-router-dom": "^6.2.1",
|
||||
@@ -59,8 +59,7 @@
|
||||
"redoc": "^2.0.0-rc.59",
|
||||
"styled-components": "^5.3.3",
|
||||
"web-vitals": "^1.1.1",
|
||||
"xml-formatter": "^2.6.0",
|
||||
"@craco/craco": "^6.4.3"
|
||||
"xml-formatter": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
|
||||
@@ -8,6 +8,7 @@ import openApiLogo from 'assets/openApiLogo.png'
|
||||
import { redocThemeOptions } from "./redocThemeOptions";
|
||||
import React from "react";
|
||||
import { Select } from "../UI/Select";
|
||||
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||
|
||||
|
||||
const modalStyle = {
|
||||
@@ -43,7 +44,7 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
|
||||
const data = await getOasByService(selectedService ? selectedService : oasServices[0]);
|
||||
setSelectedServiceSpec(data);
|
||||
} catch (e) {
|
||||
toast.error("Error occurred while fetching service OAS spec");
|
||||
toast.error("Error occurred while fetching service OAS spec", { containerId: TOAST_CONTAINER_ID });
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
.modalContainer
|
||||
display: flex
|
||||
flex-wrap: nowrap
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
@@ -11,8 +10,6 @@
|
||||
|
||||
.filterSection
|
||||
flex: 15%
|
||||
border-right: 1px solid $blue-color
|
||||
margin-right: 2%
|
||||
height: 100%
|
||||
|
||||
.filters table
|
||||
@@ -38,6 +35,7 @@
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin-right: 10px
|
||||
width: 100%
|
||||
|
||||
.servicesFilterSearch
|
||||
width: calc(100% - 10px)
|
||||
@@ -47,13 +45,13 @@
|
||||
margin-bottom: 5px
|
||||
|
||||
.servicesFilter
|
||||
margin-top: clamp(25px,15%,35px)
|
||||
margin-top: 15px
|
||||
height: 100%
|
||||
overflow: hidden
|
||||
|
||||
& .servicesFilterList
|
||||
overflow-y: auto
|
||||
height: 92%
|
||||
height: calc(100% - 30px - 5px)
|
||||
|
||||
.separtorLine
|
||||
margin-top: 10px
|
||||
|
||||
@@ -12,9 +12,9 @@ import closeIcon from "assets/close.svg"
|
||||
import styles from './ServiceMapModal.module.sass'
|
||||
import SelectList from "../UI/SelectList";
|
||||
import { GraphData, ServiceMapGraph } from "./ServiceMapModalTypes"
|
||||
import { ResizableBox } from "react-resizable"
|
||||
import "react-resizable/css/styles.css"
|
||||
import { Utils } from "../../helpers/Utils";
|
||||
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||
import Resizeable from "../UI/Resizeable"
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute',
|
||||
@@ -46,12 +46,12 @@ const LegentLabel: React.FC<LegentLabelProps> = ({ color, name }) => {
|
||||
}
|
||||
|
||||
const protocols = [
|
||||
{ key: "http", value: "HTTP", component: <LegentLabel color="#205cf5" name="HTTP" /> },
|
||||
{ key: "http/2", value: "HTTP/2", component: <LegentLabel color='#244c5a' name="HTTP/2" /> },
|
||||
{ key: "grpc", value: "gRPC", component: <LegentLabel color='#244c5a' name="gRPC" /> },
|
||||
{ key: "amqp", value: "AMQP", component: <LegentLabel color='#ff6600' name="AMQP" /> },
|
||||
{ key: "kafka", value: "KAFKA", component: <LegentLabel color='#000000' name="KAFKA" /> },
|
||||
{ key: "redis", value: "REDIS", component: <LegentLabel color='#a41e11' name="REDIS" /> },]
|
||||
{ key: "HTTP", value: "HTTP", component: <LegentLabel color="#205cf5" name="HTTP" /> },
|
||||
{ key: "HTTP/2", value: "HTTP/2", component: <LegentLabel color='#244c5a' name="HTTP/2" /> },
|
||||
{ key: "gRPC", value: "gRPC", component: <LegentLabel color='#244c5a' name="gRPC" /> },
|
||||
{ key: "AMQP", value: "AMQP", component: <LegentLabel color='#ff6600' name="AMQP" /> },
|
||||
{ key: "KAFKA", value: "KAFKA", component: <LegentLabel color='#000000' name="KAFKA" /> },
|
||||
{ key: "REDIS", value: "REDIS", component: <LegentLabel color='#a41e11' name="REDIS" /> },]
|
||||
|
||||
|
||||
interface ServiceMapModalProps {
|
||||
@@ -65,8 +65,8 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
const commonClasses = useCommonStyles();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [graphData, setGraphData] = useState<GraphData>({ nodes: [], edges: [] });
|
||||
const [filteredProtocols, setFilteredProtocols] = useState(protocols.map(x => x.key))
|
||||
const [filteredServices, setFilteredServices] = useState([])
|
||||
const [checkedProtocols, setCheckedProtocols] = useState(protocols.map(x => x.key))
|
||||
const [checkedServices, setCheckedServices] = useState([])
|
||||
const [serviceMapApiData, setServiceMapApiData] = useState<ServiceMapGraph>({ edges: [], nodes: [] })
|
||||
const [servicesSearchVal, setServicesSearchVal] = useState("")
|
||||
const [graphOptions, setGraphOptions] = useState(ServiceMapOptions);
|
||||
@@ -89,7 +89,7 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
|
||||
setGraphData(newGraphData)
|
||||
} catch (ex) {
|
||||
toast.error("An error occurred while loading Mizu Service Map, see console for mode details");
|
||||
toast.error("An error occurred while loading Mizu Service Map, see console for mode details", { containerId: TOAST_CONTAINER_ID });
|
||||
console.error(ex);
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
@@ -131,21 +131,20 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
}, [serviceMapApiData])
|
||||
|
||||
const filterServiceMap = (newProtocolsFilters?: any[], newServiceFilters?: string[]) => {
|
||||
const filterProt = newProtocolsFilters || filteredProtocols
|
||||
const filterService = newServiceFilters || filteredServices || getServicesForFilter.map(x => x.key)
|
||||
setFilteredProtocols(filterProt)
|
||||
setFilteredServices(filterService)
|
||||
const filterProt = newProtocolsFilters || checkedProtocols
|
||||
const filterService = newServiceFilters || checkedServices
|
||||
setCheckedProtocols(filterProt)
|
||||
setCheckedServices(filterService)
|
||||
const newGraphData: GraphData = {
|
||||
nodes: serviceMapApiData.nodes?.map(mapNodesDatatoGraph).filter(node => filterService.includes(node.label)),
|
||||
edges: serviceMapApiData.edges?.filter(edge => filterProt.includes(edge.protocol.name)).map(mapEdgesDatatoGraph)
|
||||
edges: serviceMapApiData.edges?.filter(edge => filterProt.includes(edge.protocol.abbr)).map(mapEdgesDatatoGraph)
|
||||
}
|
||||
setGraphData(newGraphData);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const resolvedServices = getServicesForFilter.map(x => x.key).filter(serviceName => !Utils.isIpAddress(serviceName))
|
||||
setFilteredServices(resolvedServices)
|
||||
filterServiceMap(filteredProtocols, resolvedServices)
|
||||
if (checkedServices.length > 0) return // only after refresh
|
||||
filterServiceMap(checkedProtocols, getServicesForFilter.map(x => x.key).filter(serviceName => !Utils.isIpAddress(serviceName)))
|
||||
}, [getServicesForFilter])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -176,28 +175,27 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
<Fade in={isOpen}>
|
||||
<Box sx={modalStyle}>
|
||||
<div className={styles.modalContainer}>
|
||||
{/* TODO: remove error missing height */}
|
||||
<ResizableBox width={200} style={{ height: '100%', minWidth: "200px" }} axis={"x"}>
|
||||
<div className={styles.filterSection}>
|
||||
<div className={styles.filterSection}>
|
||||
<Resizeable minWidth={170}>
|
||||
<div className={styles.filterWrapper}>
|
||||
<div className={styles.protocolsFilterList}>
|
||||
<SelectList items={protocols} checkBoxWidth="5%" tableName={"Protocols"} multiSelect={true}
|
||||
checkedValues={filteredProtocols} setCheckedValues={filterServiceMap} tableClassName={styles.filters} />
|
||||
checkedValues={checkedProtocols} setCheckedValues={filterServiceMap} tableClassName={styles.filters} />
|
||||
</div>
|
||||
<div className={styles.separtorLine}></div>
|
||||
<div className={styles.servicesFilter}>
|
||||
<input className={commonClasses.textField + ` ${styles.servicesFilterSearch}`} placeholder="search service" value={servicesSearchVal} onChange={(event) => setServicesSearchVal(event.target.value)} />
|
||||
<div className={styles.servicesFilterList}>
|
||||
<SelectList items={getServicesForFilter} tableName={"Services"} tableClassName={styles.filters} multiSelect={true} searchValue={servicesSearchVal}
|
||||
checkBoxWidth="5%" checkedValues={filteredServices} setCheckedValues={(newServicesForFilter) => filterServiceMap(null, newServicesForFilter)} />
|
||||
checkBoxWidth="5%" checkedValues={checkedServices} setCheckedValues={(newServicesForFilter) => filterServiceMap(null, newServicesForFilter)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ResizableBox>
|
||||
</Resizeable>
|
||||
</div>
|
||||
<div className={styles.graphSection}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Button style={{ marginRight: "3%" }}
|
||||
<Button style={{ marginLeft: "3%" }}
|
||||
startIcon={<img src={refreshIcon} className="custom" alt="refresh" style={{ marginRight: "8%" }}></img>}
|
||||
size="medium"
|
||||
variant="contained"
|
||||
@@ -206,7 +204,7 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
<img src={closeIcon} alt="close" onClick={() => onClose()} style={{ cursor: "pointer" }}></img>
|
||||
<img src={closeIcon} alt="close" onClick={() => onClose()} style={{ cursor: "pointer", userSelect: "none" }}></img>
|
||||
</div>
|
||||
{isLoading && <div className={spinnerStyle.spinnerContainer}>
|
||||
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
||||
|
||||
@@ -55,7 +55,7 @@ export const EntriesList: React.FC<EntriesListProps> = ({
|
||||
const [startTime, setStartTime] = useState(0);
|
||||
const [truncatedTimestamp, setTruncatedTimestamp] = useState(0);
|
||||
|
||||
const leftOffBottom = entries.length > 0 ? entries[entries.length - 1].id : -1;
|
||||
const leftOffBottom = entries.length > 0 ? entries[entries.length - 1].id + 1 : -1;
|
||||
|
||||
useEffect(() => {
|
||||
const list = document.getElementById('list').firstElementChild;
|
||||
@@ -98,6 +98,9 @@ export const EntriesList: React.FC<EntriesListProps> = ({
|
||||
setIsLoadingTop(false);
|
||||
|
||||
const newEntries = [...data.data.reverse(), ...entries];
|
||||
if(newEntries.length > 10000) {
|
||||
newEntries.splice(10000, newEntries.length - 10000)
|
||||
}
|
||||
setEntries(newEntries);
|
||||
|
||||
setQueriedTotal(data.meta.total);
|
||||
@@ -126,9 +129,9 @@ export const EntriesList: React.FC<EntriesListProps> = ({
|
||||
const entry = message.data;
|
||||
if (!focusedEntryId) setFocusedEntryId(entry.id.toString());
|
||||
const newEntries = [...entries, entry];
|
||||
if (newEntries.length === 10001) {
|
||||
if (newEntries.length > 10000) {
|
||||
setLeftOffTop(newEntries[0].id);
|
||||
newEntries.shift();
|
||||
newEntries.splice(0, newEntries.length - 10000)
|
||||
setNoMoreDataTop(false);
|
||||
}
|
||||
setEntries(newEntries);
|
||||
|
||||
@@ -89,12 +89,13 @@ const EntryTitle: React.FC<any> = ({ protocol, data, elapsedTime }) => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const EntrySummary: React.FC<any> = ({ entry }) => {
|
||||
const EntrySummary: React.FC<any> = ({ entry, namespace }) => {
|
||||
return <EntryItem
|
||||
key={`entry-${entry.id}`}
|
||||
entry={entry}
|
||||
style={{}}
|
||||
headingMode={true}
|
||||
namespace={namespace}
|
||||
/>;
|
||||
};
|
||||
|
||||
@@ -140,7 +141,7 @@ export const EntryDetailed = () => {
|
||||
data={entryData.data}
|
||||
elapsedTime={entryData.data.elapsedTime}
|
||||
/>}
|
||||
{!isLoading && entryData && <EntrySummary entry={entryData.base} />}
|
||||
{!isLoading && entryData && <EntrySummary entry={entryData.base} namespace={entryData.data.namespace} />}
|
||||
<React.Fragment>
|
||||
{!isLoading && entryData && <EntryViewer
|
||||
representation={entryData.representation}
|
||||
|
||||
@@ -52,6 +52,7 @@ interface EntryProps {
|
||||
entry: Entry;
|
||||
style: object;
|
||||
headingMode: boolean;
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
enum CaptureTypes {
|
||||
@@ -62,7 +63,7 @@ enum CaptureTypes {
|
||||
Ebpf = "ebpf",
|
||||
}
|
||||
|
||||
export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode}) => {
|
||||
export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode, namespace}) => {
|
||||
|
||||
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
||||
const [queryState, setQuery] = useRecoilState(queryAtom);
|
||||
@@ -224,6 +225,19 @@ export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode}) =>
|
||||
: ""
|
||||
}
|
||||
<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}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
flex-direction: column
|
||||
overflow: hidden
|
||||
flex-grow: 1
|
||||
height: calc(100vh - 70px)
|
||||
height: calc(100% - 70px)
|
||||
|
||||
.TrafficPageHeader
|
||||
padding: 20px 24px
|
||||
@@ -16,9 +16,8 @@
|
||||
justify-content: space-between
|
||||
|
||||
.TrafficPageStreamStatus
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
.TrafficPageHeaderImage
|
||||
width: 22px
|
||||
@@ -113,4 +112,4 @@
|
||||
.playPauseIcon
|
||||
cursor: pointer
|
||||
margin-right: 15px
|
||||
height: 30px
|
||||
height: 30px
|
||||
|
||||
@@ -201,7 +201,9 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
ws.current.close();
|
||||
if (ws?.current?.readyState === WebSocket.OPEN) {
|
||||
ws.current.close();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
61
ui-common/src/components/UI/Resizeable.tsx
Normal file
61
ui-common/src/components/UI/Resizeable.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
|
||||
import styles from './style/Resizeable.module.sass'
|
||||
|
||||
export interface Props {
|
||||
children
|
||||
minWidth: number
|
||||
}
|
||||
|
||||
const Resizeable: React.FC<Props> = ({ children, minWidth }) => {
|
||||
const resizeble = useRef(null)
|
||||
let mousePos = { x: 0, y: 0 }
|
||||
let elementDimention = { w: 0, h: 0 }
|
||||
let isPressed = false
|
||||
const [elemWidth, setElemWidth] = useState(resizeble?.current?.style?.width)
|
||||
|
||||
const mouseDownHandler = function (e) {
|
||||
// Get the current mouse position
|
||||
mousePos = { x: e.clientX, y: e.clientY }
|
||||
isPressed = true
|
||||
|
||||
// Calculate the dimension of element
|
||||
const styles = resizeble.current.getBoundingClientRect();
|
||||
elementDimention = { w: parseInt(styles.width, 10), h: parseInt(styles.height, 10) }
|
||||
// Attach the listeners to `document`
|
||||
window.addEventListener('mousemove', mouseMoveHandler);
|
||||
window.addEventListener('mouseup', mouseUpHandler);
|
||||
};
|
||||
|
||||
|
||||
const mouseMoveHandler = function (e) {
|
||||
if (isPressed) {
|
||||
// How far the mouse has been moved
|
||||
const dx = e.clientX - mousePos.x;
|
||||
const widthEl = elementDimention.w + dx
|
||||
|
||||
if (widthEl >= minWidth)
|
||||
// Adjust the dimension of element
|
||||
setElemWidth(widthEl)
|
||||
}
|
||||
};
|
||||
|
||||
const mouseUpHandler = function () {
|
||||
window.removeEventListener('mousemove', mouseMoveHandler);
|
||||
window.removeEventListener('mouseup', mouseUpHandler);
|
||||
isPressed = false
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={styles.resizable} ref={resizeble} style={{ width: elemWidth }}>
|
||||
{children}
|
||||
<div className={`${styles.resizer} ${styles.resizerRight}`} onMouseDown={mouseDownHandler}></div>
|
||||
{/* <div className={`${styles.resizer} ${styles.resizerB}`} onMouseDown={mouseDownHandler}></div> -- FutureUse*/}
|
||||
</div>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default Resizeable;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Radio from "./Radio";
|
||||
import styles from './style/SelectList.module.sass'
|
||||
import NoDataMessage from "./NoDataMessage";
|
||||
@@ -19,12 +19,16 @@ export interface Props {
|
||||
const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], multiSelect = true, searchValue = "", setCheckedValues, tableClassName,
|
||||
checkBoxWidth = 50 }) => {
|
||||
const noItemsMessage = "No items to show";
|
||||
const enabledItemsLength = useMemo(() => items.filter(item => !item.disabled).length, [items]);
|
||||
const [headerChecked, setHeaderChecked] = useState(false)
|
||||
|
||||
const filteredValues = useMemo(() => {
|
||||
return items.filter((listValue) => listValue?.value?.includes(searchValue));
|
||||
}, [items, searchValue])
|
||||
|
||||
const filteredValuesKeys = useMemo(() => {
|
||||
return filteredValues.map(x => x.key)
|
||||
}, [filteredValues])
|
||||
|
||||
const toggleValue = (checkedKey) => {
|
||||
if (!multiSelect) {
|
||||
const newCheckedValues = [];
|
||||
@@ -34,25 +38,31 @@ const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], mul
|
||||
else {
|
||||
const newCheckedValues = [...checkedValues];
|
||||
let index = newCheckedValues.indexOf(checkedKey);
|
||||
|
||||
if (index > -1)
|
||||
newCheckedValues.splice(index, 1);
|
||||
else
|
||||
newCheckedValues.push(checkedKey);
|
||||
|
||||
setCheckedValues(newCheckedValues);
|
||||
}
|
||||
}
|
||||
|
||||
const toggleAll = () => {
|
||||
const newCheckedValues = [...checkedValues];
|
||||
if (newCheckedValues.length === enabledItemsLength) setCheckedValues([]);
|
||||
else {
|
||||
items.forEach((obj) => {
|
||||
if (!obj.disabled && !newCheckedValues.includes(obj.key))
|
||||
newCheckedValues.push(obj.key);
|
||||
})
|
||||
setCheckedValues(newCheckedValues);
|
||||
useEffect(() => {
|
||||
const setAllChecked = filteredValuesKeys.every(val => checkedValues.includes(val))
|
||||
setHeaderChecked(setAllChecked)
|
||||
}, [filteredValuesKeys, checkedValues])
|
||||
|
||||
const toggleAll = useCallback((shouldCheckAll) => {
|
||||
let newChecked = checkedValues.filter(x => !filteredValuesKeys.includes(x))
|
||||
|
||||
if (shouldCheckAll) {
|
||||
const disabledItems = items.filter(i => i.disabled).map(x => x.key)
|
||||
newChecked = [...filteredValuesKeys, ...newChecked].filter(x => !disabledItems.includes(x))
|
||||
}
|
||||
}
|
||||
|
||||
setCheckedValues(newChecked)
|
||||
}, [searchValue, checkedValues, filteredValuesKeys])
|
||||
|
||||
const dataFieldFunc = (listValue) => listValue.component ? listValue.component :
|
||||
<span className={styles.nowrap} title={listValue.value}>
|
||||
@@ -60,8 +70,8 @@ const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], mul
|
||||
</span>
|
||||
|
||||
const tableHead = multiSelect ? <tr style={{ borderBottomWidth: "2px" }}>
|
||||
<th style={{ width: checkBoxWidth }}><Checkbox data-cy="checkbox-all" checked={enabledItemsLength === checkedValues.length}
|
||||
onToggle={toggleAll} /></th>
|
||||
<th style={{ width: checkBoxWidth }}><Checkbox data-cy="checkbox-all" checked={headerChecked}
|
||||
onToggle={(isChecked) => toggleAll(isChecked)} /></th>
|
||||
<th>{tableName}</th>
|
||||
</tr> :
|
||||
<tr style={{ borderBottomWidth: "2px" }}>
|
||||
@@ -70,7 +80,7 @@ const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], mul
|
||||
|
||||
const tableBody = filteredValues.length === 0 ?
|
||||
<tr>
|
||||
<td>
|
||||
<td colSpan={2}>
|
||||
<NoDataMessage messageText={noItemsMessage} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -37,9 +37,9 @@ export function getClassification(statusCode: number): string {
|
||||
|
||||
// 1 - 16 HTTP/2 (gRPC) status codes
|
||||
// 2xx - 5xx HTTP/1.x status codes
|
||||
if ((statusCode >= 200 && statusCode <= 399) || statusCode === 0) {
|
||||
if (statusCode >= 200 && statusCode <= 399) {
|
||||
classification = StatusCodeClassification.SUCCESS;
|
||||
} else if (statusCode >= 400 || (statusCode >= 1 && statusCode <= 16)) {
|
||||
} else if (statusCode >= 400) {
|
||||
classification = StatusCodeClassification.FAILURE;
|
||||
}
|
||||
|
||||
|
||||
29
ui-common/src/components/UI/style/Resizeable.module.sass
Normal file
29
ui-common/src/components/UI/style/Resizeable.module.sass
Normal file
@@ -0,0 +1,29 @@
|
||||
@import "../../../variables.module"
|
||||
|
||||
.resizable
|
||||
position: relative
|
||||
align-items: center
|
||||
display: flex
|
||||
overflow: hidden
|
||||
border-right: 1px solid $blue-color
|
||||
height: 100%
|
||||
width: 100%
|
||||
padding-right: 3px
|
||||
|
||||
.resizer
|
||||
position: absolute
|
||||
|
||||
&Right
|
||||
cursor: col-resize
|
||||
height: 100%
|
||||
right: 0
|
||||
top: 0
|
||||
width: 5px
|
||||
|
||||
// FutureUse
|
||||
&Bottom
|
||||
bottom: 0
|
||||
cursor: row-resize
|
||||
height: 5px
|
||||
left: 0
|
||||
width: 100%
|
||||
@@ -1,25 +1,27 @@
|
||||
@import '../../../variables.module'
|
||||
|
||||
.selectListTable
|
||||
overflow: auto
|
||||
height: 100%
|
||||
user-select: none // when resizble moved we get unwanted beheviour
|
||||
|
||||
table
|
||||
width: 100%
|
||||
margin-top: 20px
|
||||
height: 100%
|
||||
|
||||
tbody
|
||||
display: block
|
||||
border-collapse: collapse
|
||||
|
||||
th
|
||||
color: $blue-gray
|
||||
text-align: left
|
||||
padding: 10px
|
||||
position: sticky
|
||||
top: 0
|
||||
background: $main-background-color
|
||||
|
||||
tr
|
||||
border-bottom-width: 1px
|
||||
border-bottom-color: $data-background-color
|
||||
border-bottom-style: solid
|
||||
display: table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
|
||||
td
|
||||
|
||||
@@ -6,3 +6,4 @@ body
|
||||
.mizuApp
|
||||
color: $font-color
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
@import './variables.module'
|
||||
|
||||
#root
|
||||
height: 100%
|
||||
|
||||
html,
|
||||
body
|
||||
height: 100%
|
||||
@@ -153,4 +156,4 @@ button
|
||||
|
||||
// enable view elements inside redoc
|
||||
.sc-dwsnSq
|
||||
height: 80vh !important
|
||||
height: 80vh !important
|
||||
|
||||
Reference in New Issue
Block a user