mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-03-03 02:00:32 +00:00
Compare commits
18 Commits
31.0-dev3
...
31.0-dev19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d99c632102 | ||
|
|
76a6a77a14 | ||
|
|
2bfc523bbc | ||
|
|
66ba778384 | ||
|
|
7adbf7bf1b | ||
|
|
a97b5b3b38 | ||
|
|
aa8dcc5f5c | ||
|
|
9d08dbdd5d | ||
|
|
b47718e094 | ||
|
|
6a7fad430c | ||
|
|
59ad8d8fad | ||
|
|
a49443f101 | ||
|
|
2427955aa4 | ||
|
|
27a73e21fb | ||
|
|
8eeb0e54c9 | ||
|
|
97db24aeba | ||
|
|
63cf7ac34e | ||
|
|
e867b7d0f1 |
2
.github/workflows/acceptance_tests.yml
vendored
2
.github/workflows/acceptance_tests.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
status: ${{ job.status }}
|
status: ${{ job.status }}
|
||||||
notification_title: 'Mizu {workflow} has {status_message}'
|
notification_title: 'Mizu {workflow} has {status_message}'
|
||||||
message_format: '{emoji} *{workflow}* {status_message} during <{run_url}|run>, after commit <{commit_url}|{commit_sha} ${{ github.event.head_commit.message }}> ${{ github.event.head_commit.committer.name }} <${{ github.event.head_commit.committer.email }}>'
|
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 }}```'
|
||||||
footer: 'Linked Repo <{repo_url}|{repo}>'
|
footer: 'Linked Repo <{repo_url}|{repo}>'
|
||||||
notify_when: 'failure'
|
notify_when: 'failure'
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check modified files
|
- name: Check modified files
|
||||||
id: modified_files
|
id: modified_files
|
||||||
run: devops/check_modified_files.sh agent/ shared/ tap/ ui/ Dockerfile
|
run: devops/check_modified_files.sh agent/ shared/ tap/ ui/ ui-common/ Dockerfile
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
if: steps.modified_files.outputs.matched == 'true'
|
if: steps.modified_files.outputs.matched == 'true'
|
||||||
|
|||||||
53
.github/workflows/static_code_analysis.yml
vendored
53
.github/workflows/static_code_analysis.yml
vendored
@@ -15,6 +15,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '^1.17'
|
go-version: '^1.17'
|
||||||
@@ -24,67 +27,117 @@ jobs:
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y libpcap-dev
|
sudo apt install -y libpcap-dev
|
||||||
|
|
||||||
|
- name: Check Agent modified files
|
||||||
|
id: agent_modified_files
|
||||||
|
run: devops/check_modified_files.sh agent/
|
||||||
|
|
||||||
- name: Go lint - agent
|
- name: Go lint - agent
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.agent_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: agent
|
working-directory: agent
|
||||||
args: --timeout=3m
|
args: --timeout=3m
|
||||||
|
|
||||||
|
- name: Check shared modified files
|
||||||
|
id: shared_modified_files
|
||||||
|
run: devops/check_modified_files.sh shared/
|
||||||
|
|
||||||
- name: Go lint - shared
|
- name: Go lint - shared
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.shared_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: shared
|
working-directory: shared
|
||||||
args: --timeout=3m
|
args: --timeout=3m
|
||||||
|
|
||||||
|
- name: Check tap modified files
|
||||||
|
id: tap_modified_files
|
||||||
|
run: devops/check_modified_files.sh tap/
|
||||||
|
|
||||||
- name: Go lint - tap
|
- name: Go lint - tap
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.tap_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: tap
|
working-directory: tap
|
||||||
args: --timeout=3m
|
args: --timeout=3m
|
||||||
|
|
||||||
|
- name: Check cli modified files
|
||||||
|
id: cli_modified_files
|
||||||
|
run: devops/check_modified_files.sh cli/
|
||||||
|
|
||||||
- name: Go lint - CLI
|
- name: Go lint - CLI
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.cli_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: cli
|
working-directory: cli
|
||||||
args: --timeout=3m
|
args: --timeout=3m
|
||||||
|
|
||||||
|
- name: Check acceptanceTests modified files
|
||||||
|
id: acceptanceTests_modified_files
|
||||||
|
run: devops/check_modified_files.sh acceptanceTests/
|
||||||
|
|
||||||
- name: Go lint - acceptanceTests
|
- name: Go lint - acceptanceTests
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.acceptanceTests_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: acceptanceTests
|
working-directory: acceptanceTests
|
||||||
args: --timeout=3m
|
args: --timeout=3m
|
||||||
|
|
||||||
|
- name: Check tap/api modified files
|
||||||
|
id: tap_api_modified_files
|
||||||
|
run: devops/check_modified_files.sh tap/api/
|
||||||
|
|
||||||
- name: Go lint - tap/api
|
- name: Go lint - tap/api
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.tap_api_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: tap/api
|
working-directory: tap/api
|
||||||
|
|
||||||
|
- name: Check tap/extensions/amqp modified files
|
||||||
|
id: tap_amqp_modified_files
|
||||||
|
run: devops/check_modified_files.sh tap/extensions/amqp/
|
||||||
|
|
||||||
- name: Go lint - tap/extensions/amqp
|
- name: Go lint - tap/extensions/amqp
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.tap_amqp_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: tap/extensions/amqp
|
working-directory: tap/extensions/amqp
|
||||||
|
|
||||||
|
- name: Check tap/extensions/http modified files
|
||||||
|
id: tap_http_modified_files
|
||||||
|
run: devops/check_modified_files.sh tap/extensions/http/
|
||||||
|
|
||||||
- name: Go lint - tap/extensions/http
|
- name: Go lint - tap/extensions/http
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.tap_http_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: tap/extensions/http
|
working-directory: tap/extensions/http
|
||||||
|
|
||||||
|
- name: Check tap/extensions/kafka modified files
|
||||||
|
id: tap_kafka_modified_files
|
||||||
|
run: devops/check_modified_files.sh tap/extensions/kafka/
|
||||||
|
|
||||||
- name: Go lint - tap/extensions/kafka
|
- name: Go lint - tap/extensions/kafka
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.tap_kafka_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: tap/extensions/kafka
|
working-directory: tap/extensions/kafka
|
||||||
|
|
||||||
|
- name: Check tap/extensions/redis modified files
|
||||||
|
id: tap_redis_modified_files
|
||||||
|
run: devops/check_modified_files.sh tap/extensions/redis/
|
||||||
|
|
||||||
- name: Go lint - tap/extensions/redis
|
- name: Go lint - tap/extensions/redis
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
if: steps.tap_redis_modified_files.outputs.matched == 'true'
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
working-directory: tap/extensions/redis
|
working-directory: tap/extensions/redis
|
||||||
|
|||||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
|||||||
run-unit-tests:
|
run-unit-tests:
|
||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -1,12 +1,23 @@
|
|||||||
ARG BUILDARCH=amd64
|
ARG BUILDARCH=amd64
|
||||||
ARG TARGETARCH=amd64
|
ARG TARGETARCH=amd64
|
||||||
|
|
||||||
|
### Front-end common
|
||||||
|
FROM node:16 AS front-end-common
|
||||||
|
|
||||||
|
WORKDIR /app/ui-build
|
||||||
|
COPY ui-common/package.json .
|
||||||
|
COPY ui-common/package-lock.json .
|
||||||
|
RUN npm i
|
||||||
|
COPY ui-common .
|
||||||
|
RUN npm pack
|
||||||
|
|
||||||
### Front-end
|
### Front-end
|
||||||
FROM node:16 AS front-end
|
FROM node:16 AS front-end
|
||||||
|
|
||||||
WORKDIR /app/ui-build
|
WORKDIR /app/ui-build
|
||||||
|
|
||||||
COPY ui/package.json ui/package-lock.json ./
|
COPY ui/package.json ui/package-lock.json ./
|
||||||
|
COPY --from=front-end-common ["/app/ui-build/up9-mizu-common-0.0.0.tgz", "."]
|
||||||
RUN npm i
|
RUN npm i
|
||||||
COPY ui .
|
COPY ui .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/up9inc/mizu/agent/pkg/dependency"
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
"github.com/up9inc/mizu/agent/pkg/elastic"
|
"github.com/up9inc/mizu/agent/pkg/elastic"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/entries"
|
||||||
"github.com/up9inc/mizu/agent/pkg/middlewares"
|
"github.com/up9inc/mizu/agent/pkg/middlewares"
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"github.com/up9inc/mizu/agent/pkg/oas"
|
"github.com/up9inc/mizu/agent/pkg/oas"
|
||||||
@@ -370,5 +371,8 @@ func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) {
|
|||||||
|
|
||||||
func initializeDependencies() {
|
func initializeDependencies() {
|
||||||
dependency.RegisterGenerator(dependency.ServiceMapGeneratorDependency, func() interface{} { return servicemap.GetDefaultServiceMapInstance() })
|
dependency.RegisterGenerator(dependency.ServiceMapGeneratorDependency, func() interface{} { return servicemap.GetDefaultServiceMapInstance() })
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance(nil) })
|
||||||
|
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{} })
|
||||||
}
|
}
|
||||||
|
|||||||
57
agent/pkg/api/entry_streamer_socket_connector.go
Normal file
57
agent/pkg/api/entry_streamer_socket_connector.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntryStreamerSocketConnector interface {
|
||||||
|
SendEntry(socketId int, entry *tapApi.Entry, params *WebSocketParams)
|
||||||
|
SendMetadata(socketId int, metadata *basenine.Metadata)
|
||||||
|
SendToastError(socketId int, err error)
|
||||||
|
CleanupSocket(socketId int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultEntryStreamerSocketConnector struct{}
|
||||||
|
|
||||||
|
func (e *DefaultEntryStreamerSocketConnector) SendEntry(socketId int, entry *tapApi.Entry, params *WebSocketParams) {
|
||||||
|
var message []byte
|
||||||
|
if params.EnableFullEntries {
|
||||||
|
message, _ = models.CreateFullEntryWebSocketMessage(entry)
|
||||||
|
} else {
|
||||||
|
extension := extensionsMap[entry.Protocol.Name]
|
||||||
|
base := extension.Dissector.Summarize(entry)
|
||||||
|
message, _ = models.CreateBaseEntryWebSocketMessage(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := SendToSocket(socketId, message); err != nil {
|
||||||
|
logger.Log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DefaultEntryStreamerSocketConnector) SendMetadata(socketId int, metadata *basenine.Metadata) {
|
||||||
|
metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata)
|
||||||
|
if err := SendToSocket(socketId, metadataBytes); err != nil {
|
||||||
|
logger.Log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DefaultEntryStreamerSocketConnector) SendToastError(socketId int, err error) {
|
||||||
|
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
|
||||||
|
Type: "error",
|
||||||
|
AutoClose: 5000,
|
||||||
|
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
|
||||||
|
})
|
||||||
|
if err := SendToSocket(socketId, toastBytes); err != nil {
|
||||||
|
logger.Log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DefaultEntryStreamerSocketConnector) CleanupSocket(socketId int) {
|
||||||
|
socketObj := connectedWebsockets[socketId]
|
||||||
|
socketCleanup(socketId, socketObj)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -19,8 +20,6 @@ import (
|
|||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/servicemap"
|
"github.com/up9inc/mizu/agent/pkg/servicemap"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/oas"
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/resolver"
|
"github.com/up9inc/mizu/agent/pkg/resolver"
|
||||||
"github.com/up9inc/mizu/agent/pkg/utils"
|
"github.com/up9inc/mizu/agent/pkg/utils"
|
||||||
|
|
||||||
@@ -140,20 +139,6 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
|
|||||||
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Destination.Name)
|
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Destination.Name)
|
||||||
mizuEntry.Rules = rules
|
mizuEntry.Rules = rules
|
||||||
}
|
}
|
||||||
|
|
||||||
entryWSource := oas.EntryWithSource{
|
|
||||||
Entry: *harEntry,
|
|
||||||
Source: mizuEntry.Source.Name,
|
|
||||||
Destination: mizuEntry.Destination.Name,
|
|
||||||
Id: mizuEntry.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if entryWSource.Destination == "" {
|
|
||||||
entryWSource.Destination = mizuEntry.Destination.IP + ":" + mizuEntry.Destination.Port
|
|
||||||
}
|
|
||||||
|
|
||||||
oasGenerator := dependency.GetInstance(dependency.OasGeneratorDependency).(oas.OasGeneratorSink)
|
|
||||||
oasGenerator.PushEntry(&entryWSource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(mizuEntry)
|
data, err := json.Marshal(mizuEntry)
|
||||||
@@ -183,6 +168,7 @@ func resolveIP(connectionInfo *tapApi.ConnectionInfo) (resolvedSource string, re
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolvedSource = resolvedSourceObject.FullAddress
|
resolvedSource = resolvedSourceObject.FullAddress
|
||||||
|
namespace = resolvedSourceObject.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
unresolvedDestination := fmt.Sprintf("%s:%s", connectionInfo.ServerIP, connectionInfo.ServerPort)
|
unresolvedDestination := fmt.Sprintf("%s:%s", connectionInfo.ServerIP, connectionInfo.ServerPort)
|
||||||
@@ -194,7 +180,11 @@ func resolveIP(connectionInfo *tapApi.ConnectionInfo) (resolvedSource string, re
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolvedDestination = resolvedDestinationObject.FullAddress
|
resolvedDestination = resolvedDestinationObject.FullAddress
|
||||||
namespace = resolvedDestinationObject.Namespace
|
// Overwrite namespace (if it was set according to the source)
|
||||||
|
// Only overwrite if non-empty
|
||||||
|
if resolvedDestinationObject.Namespace != "" {
|
||||||
|
namespace = resolvedDestinationObject.Namespace
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resolvedSource, resolvedDestination, namespace
|
return resolvedSource, resolvedDestination, namespace
|
||||||
|
|||||||
92
agent/pkg/api/socket_data_streamer.go
Normal file
92
agent/pkg/api/socket_data_streamer.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntryStreamer interface {
|
||||||
|
Get(ctx context.Context, socketId int, params *WebSocketParams) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasenineEntryStreamer struct{}
|
||||||
|
|
||||||
|
func (e *BasenineEntryStreamer) Get(ctx context.Context, socketId int, params *WebSocketParams) error {
|
||||||
|
var connection *basenine.Connection
|
||||||
|
|
||||||
|
entryStreamerSocketConnector := dependency.GetInstance(dependency.EntryStreamerSocketConnector).(EntryStreamerSocketConnector)
|
||||||
|
|
||||||
|
connection, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf("failed to establish a connection to Basenine: %v", err)
|
||||||
|
entryStreamerSocketConnector.CleanupSocket(socketId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(chan []byte)
|
||||||
|
meta := make(chan []byte)
|
||||||
|
|
||||||
|
query := params.Query
|
||||||
|
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
||||||
|
if err != nil {
|
||||||
|
entryStreamerSocketConnector.SendToastError(socketId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
|
||||||
|
for {
|
||||||
|
bytes := <-data
|
||||||
|
|
||||||
|
if string(bytes) == basenine.CloseChannel {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry *tapApi.Entry
|
||||||
|
err = json.Unmarshal(bytes, &entry)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("error unmarshalling entry: %v", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entryStreamerSocketConnector.SendEntry(socketId, entry, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMetaChannel := func(c *basenine.Connection, meta chan []byte) {
|
||||||
|
for {
|
||||||
|
bytes := <-meta
|
||||||
|
|
||||||
|
if string(bytes) == basenine.CloseChannel {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata *basenine.Metadata
|
||||||
|
err = json.Unmarshal(bytes, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("Error unmarshalling metadata: %v", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entryStreamerSocketConnector.SendMetadata(socketId, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go handleDataChannel(connection, data)
|
||||||
|
go handleMetaChannel(connection, meta)
|
||||||
|
|
||||||
|
connection.Query(query, data, meta)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
data <- []byte(basenine.CloseChannel)
|
||||||
|
meta <- []byte(basenine.CloseChannel)
|
||||||
|
connection.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,19 +1,15 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/utils"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
basenine "github.com/up9inc/basenine/client/go"
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/agent/pkg/utils"
|
||||||
"github.com/up9inc/mizu/shared/logger"
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
tapApi "github.com/up9inc/mizu/tap/api"
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
)
|
)
|
||||||
@@ -25,9 +21,9 @@ func InitExtensionsMap(ref map[string]*tapApi.Extension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EventHandlers interface {
|
type EventHandlers interface {
|
||||||
WebSocketConnect(socketId int, isTapper bool)
|
WebSocketConnect(c *gin.Context, socketId int, isTapper bool)
|
||||||
WebSocketDisconnect(socketId int, isTapper bool)
|
WebSocketDisconnect(socketId int, isTapper bool)
|
||||||
WebSocketMessage(socketId int, message []byte)
|
WebSocketMessage(socketId int, isTapper bool, message []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SocketConnection struct {
|
type SocketConnection struct {
|
||||||
@@ -62,11 +58,11 @@ func init() {
|
|||||||
|
|
||||||
func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers) {
|
func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers) {
|
||||||
SocketGetBrowserHandler = func(c *gin.Context) {
|
SocketGetBrowserHandler = func(c *gin.Context) {
|
||||||
websocketHandler(c.Writer, c.Request, eventHandlers, false)
|
websocketHandler(c, eventHandlers, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketGetTapperHandler = func(c *gin.Context) {
|
SocketGetTapperHandler = func(c *gin.Context) {
|
||||||
websocketHandler(c.Writer, c.Request, eventHandlers, true)
|
websocketHandler(c, eventHandlers, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.GET("/ws", func(c *gin.Context) {
|
app.GET("/ws", func(c *gin.Context) {
|
||||||
@@ -78,10 +74,10 @@ func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers EventHandlers, isTapper bool) {
|
func websocketHandler(c *gin.Context, eventHandlers EventHandlers, isTapper bool) {
|
||||||
ws, err := websocketUpgrader.Upgrade(w, r, nil)
|
ws, err := websocketUpgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Errorf("Failed to set websocket upgrade: %v", err)
|
logger.Log.Errorf("failed to set websocket upgrade: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,30 +89,11 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
|||||||
|
|
||||||
websocketIdsLock.Unlock()
|
websocketIdsLock.Unlock()
|
||||||
|
|
||||||
var connection *basenine.Connection
|
|
||||||
var isQuerySet bool
|
|
||||||
|
|
||||||
// `!isTapper` means it's a connection from the web UI
|
|
||||||
if !isTapper {
|
|
||||||
connection, err = basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Errorf("Failed to establish a connection to Basenine: %v", err)
|
|
||||||
socketCleanup(socketId, connectedWebsockets[socketId])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make(chan []byte)
|
|
||||||
meta := make(chan []byte)
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
socketCleanup(socketId, connectedWebsockets[socketId])
|
socketCleanup(socketId, connectedWebsockets[socketId])
|
||||||
data <- []byte(basenine.CloseChannel)
|
|
||||||
meta <- []byte(basenine.CloseChannel)
|
|
||||||
connection.Close()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
eventHandlers.WebSocketConnect(socketId, isTapper)
|
eventHandlers.WebSocketConnect(c, socketId, isTapper)
|
||||||
|
|
||||||
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(utils.StartTime)
|
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(utils.StartTime)
|
||||||
|
|
||||||
@@ -124,127 +101,32 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
|||||||
logger.Log.Error(err)
|
logger.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var params WebSocketParams
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, msg, err := ws.ReadMessage()
|
_, msg, err := ws.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*websocket.CloseError); ok {
|
if _, ok := err.(*websocket.CloseError); ok {
|
||||||
logger.Log.Debugf("Received websocket close message, socket id: %d", socketId)
|
logger.Log.Debugf("received websocket close message, socket id: %d", socketId)
|
||||||
} else {
|
} else {
|
||||||
logger.Log.Errorf("Error reading message, socket id: %d, error: %v", socketId, err)
|
logger.Log.Errorf("error reading message, socket id: %d, error: %v", socketId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isTapper && !isQuerySet {
|
eventHandlers.WebSocketMessage(socketId, isTapper, msg)
|
||||||
if err := json.Unmarshal(msg, ¶ms); err != nil {
|
|
||||||
logger.Log.Errorf("Error unmarshalling parameters: %v", socketId, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
query := params.Query
|
|
||||||
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
|
||||||
if err != nil {
|
|
||||||
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
|
|
||||||
Type: "error",
|
|
||||||
AutoClose: 5000,
|
|
||||||
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
|
|
||||||
})
|
|
||||||
if err := SendToSocket(socketId, toastBytes); err != nil {
|
|
||||||
logger.Log.Error(err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
isQuerySet = true
|
|
||||||
|
|
||||||
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
|
|
||||||
for {
|
|
||||||
bytes := <-data
|
|
||||||
|
|
||||||
if string(bytes) == basenine.CloseChannel {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry *tapApi.Entry
|
|
||||||
err = json.Unmarshal(bytes, &entry)
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Debugf("Error unmarshalling entry: %v", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var message []byte
|
|
||||||
if params.EnableFullEntries {
|
|
||||||
message, _ = models.CreateFullEntryWebSocketMessage(entry)
|
|
||||||
} else {
|
|
||||||
extension := extensionsMap[entry.Protocol.Name]
|
|
||||||
base := extension.Dissector.Summarize(entry)
|
|
||||||
message, _ = models.CreateBaseEntryWebSocketMessage(base)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := SendToSocket(socketId, message); err != nil {
|
|
||||||
logger.Log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMetaChannel := func(c *basenine.Connection, meta chan []byte) {
|
|
||||||
for {
|
|
||||||
bytes := <-meta
|
|
||||||
|
|
||||||
if string(bytes) == basenine.CloseChannel {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadata *basenine.Metadata
|
|
||||||
err = json.Unmarshal(bytes, &metadata)
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Debugf("Error unmarshalling metadata: %v", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata)
|
|
||||||
if err := SendToSocket(socketId, metadataBytes); err != nil {
|
|
||||||
logger.Log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go handleDataChannel(connection, data)
|
|
||||||
go handleMetaChannel(connection, meta)
|
|
||||||
|
|
||||||
connection.Query(query, data, meta)
|
|
||||||
} else {
|
|
||||||
eventHandlers.WebSocketMessage(socketId, msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func socketCleanup(socketId int, socketConnection *SocketConnection) {
|
|
||||||
err := socketConnection.connection.Close()
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Errorf("Error closing socket connection for socket id %d: %v", socketId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
websocketIdsLock.Lock()
|
|
||||||
connectedWebsockets[socketId] = nil
|
|
||||||
websocketIdsLock.Unlock()
|
|
||||||
|
|
||||||
socketConnection.eventHandlers.WebSocketDisconnect(socketId, socketConnection.isTapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendToSocket(socketId int, message []byte) error {
|
func SendToSocket(socketId int, message []byte) error {
|
||||||
socketObj := connectedWebsockets[socketId]
|
socketObj := connectedWebsockets[socketId]
|
||||||
if socketObj == nil {
|
if socketObj == nil {
|
||||||
return fmt.Errorf("Socket %v is disconnected", socketId)
|
return fmt.Errorf("socket %v is disconnected", socketId)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sent = false
|
var sent = false
|
||||||
time.AfterFunc(time.Second*5, func() {
|
time.AfterFunc(time.Second*5, func() {
|
||||||
if !sent {
|
if !sent {
|
||||||
logger.Log.Error("Socket timed out")
|
logger.Log.Error("socket timed out")
|
||||||
socketCleanup(socketId, socketObj)
|
socketCleanup(socketId, socketObj)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -255,7 +137,20 @@ func SendToSocket(socketId int, message []byte) error {
|
|||||||
sent = true
|
sent = true
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to write message to socket %v, err: %w", socketId, err)
|
return fmt.Errorf("failed to write message to socket %v, err: %w", socketId, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func socketCleanup(socketId int, socketConnection *SocketConnection) {
|
||||||
|
err := socketConnection.connection.Close()
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf("error closing socket connection for socket id %d: %v", socketId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
websocketIdsLock.Lock()
|
||||||
|
connectedWebsockets[socketId] = nil
|
||||||
|
websocketIdsLock.Unlock()
|
||||||
|
|
||||||
|
socketConnection.eventHandlers.WebSocketDisconnect(socketId, socketConnection.isTapper)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"github.com/up9inc/mizu/agent/pkg/providers"
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/providers/tappedPods"
|
"github.com/up9inc/mizu/agent/pkg/providers/tappedPods"
|
||||||
"github.com/up9inc/mizu/agent/pkg/providers/tappers"
|
"github.com/up9inc/mizu/agent/pkg/providers/tappers"
|
||||||
"github.com/up9inc/mizu/agent/pkg/up9"
|
"github.com/up9inc/mizu/agent/pkg/up9"
|
||||||
@@ -17,7 +18,11 @@ import (
|
|||||||
"github.com/up9inc/mizu/shared/logger"
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var browserClientSocketUUIDs = make([]int, 0)
|
type BrowserClient struct {
|
||||||
|
dataStreamCancelFunc context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
var browserClients = make(map[int]*BrowserClient, 0)
|
||||||
var tapperClientSocketUUIDs = make([]int, 0)
|
var tapperClientSocketUUIDs = make([]int, 0)
|
||||||
var socketListLock = sync.Mutex{}
|
var socketListLock = sync.Mutex{}
|
||||||
|
|
||||||
@@ -30,7 +35,7 @@ func init() {
|
|||||||
go up9.UpdateAnalyzeStatus(BroadcastToBrowserClients)
|
go up9.UpdateAnalyzeStatus(BroadcastToBrowserClients)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
func (h *RoutesEventHandlers) WebSocketConnect(_ *gin.Context, socketId int, isTapper bool) {
|
||||||
if isTapper {
|
if isTapper {
|
||||||
logger.Log.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
logger.Log.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
||||||
tappers.Connected()
|
tappers.Connected()
|
||||||
@@ -45,7 +50,7 @@ func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
|||||||
logger.Log.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
logger.Log.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
||||||
|
|
||||||
socketListLock.Lock()
|
socketListLock.Lock()
|
||||||
browserClientSocketUUIDs = append(browserClientSocketUUIDs, socketId)
|
browserClients[socketId] = &BrowserClient{}
|
||||||
socketListLock.Unlock()
|
socketListLock.Unlock()
|
||||||
|
|
||||||
BroadcastTappedPodsStatus()
|
BroadcastTappedPodsStatus()
|
||||||
@@ -63,13 +68,16 @@ func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
|
|||||||
} else {
|
} else {
|
||||||
logger.Log.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
logger.Log.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
||||||
socketListLock.Lock()
|
socketListLock.Lock()
|
||||||
removeSocketUUIDFromBrowserSlice(socketId)
|
if browserClients[socketId] != nil && browserClients[socketId].dataStreamCancelFunc != nil {
|
||||||
|
browserClients[socketId].dataStreamCancelFunc()
|
||||||
|
}
|
||||||
|
delete(browserClients, socketId)
|
||||||
socketListLock.Unlock()
|
socketListLock.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BroadcastToBrowserClients(message []byte) {
|
func BroadcastToBrowserClients(message []byte) {
|
||||||
for _, socketId := range browserClientSocketUUIDs {
|
for socketId := range browserClients {
|
||||||
go func(socketId int) {
|
go func(socketId int) {
|
||||||
if err := SendToSocket(socketId, message); err != nil {
|
if err := SendToSocket(socketId, message); err != nil {
|
||||||
logger.Log.Error(err)
|
logger.Log.Error(err)
|
||||||
@@ -88,7 +96,33 @@ func BroadcastToTapperClients(message []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
|
func (h *RoutesEventHandlers) WebSocketMessage(socketId int, isTapper bool, message []byte) {
|
||||||
|
if isTapper {
|
||||||
|
HandleTapperIncomingMessage(message, h.SocketOutChannel, BroadcastToBrowserClients)
|
||||||
|
} else {
|
||||||
|
// we initiate the basenine stream after the first websocket message we receive (it contains the entry query), we then store a cancelfunc to later cancel this stream
|
||||||
|
if browserClients[socketId] != nil && browserClients[socketId].dataStreamCancelFunc == nil {
|
||||||
|
var params WebSocketParams
|
||||||
|
if err := json.Unmarshal(message, ¶ms); err != nil {
|
||||||
|
logger.Log.Errorf("Error: %v", socketId, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entriesStreamer := dependency.GetInstance(dependency.EntriesSocketStreamer).(EntryStreamer)
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
err := entriesStreamer.Get(ctx, socketId, ¶ms)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf("error initializing basenine stream for browser socket %d %+v", socketId, err)
|
||||||
|
cancelFunc()
|
||||||
|
} else {
|
||||||
|
browserClients[socketId].dataStreamCancelFunc = cancelFunc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleTapperIncomingMessage(message []byte, socketOutChannel chan<- *tapApi.OutputChannelItem, broadcastMessageFunc func([]byte)) {
|
||||||
var socketMessageBase shared.WebSocketMessageMetadata
|
var socketMessageBase shared.WebSocketMessageMetadata
|
||||||
err := json.Unmarshal(message, &socketMessageBase)
|
err := json.Unmarshal(message, &socketMessageBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,7 +136,7 @@ func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
|
|||||||
logger.Log.Infof("Could not unmarshal message of message type %s %v", socketMessageBase.MessageType, err)
|
logger.Log.Infof("Could not unmarshal message of message type %s %v", socketMessageBase.MessageType, err)
|
||||||
} else {
|
} else {
|
||||||
// NOTE: This is where the message comes back from the intermediate WebSocket to code.
|
// NOTE: This is where the message comes back from the intermediate WebSocket to code.
|
||||||
h.SocketOutChannel <- tappedEntryMessage.Data
|
socketOutChannel <- tappedEntryMessage.Data
|
||||||
}
|
}
|
||||||
case shared.WebSocketMessageTypeUpdateStatus:
|
case shared.WebSocketMessageTypeUpdateStatus:
|
||||||
var statusMessage shared.WebSocketStatusMessage
|
var statusMessage shared.WebSocketStatusMessage
|
||||||
@@ -110,15 +144,7 @@ func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Infof("Could not unmarshal message of message type %s %v", socketMessageBase.MessageType, err)
|
logger.Log.Infof("Could not unmarshal message of message type %s %v", socketMessageBase.MessageType, err)
|
||||||
} else {
|
} else {
|
||||||
BroadcastToBrowserClients(message)
|
broadcastMessageFunc(message)
|
||||||
}
|
|
||||||
case shared.WebsocketMessageTypeOutboundLink:
|
|
||||||
var outboundLinkMessage models.WebsocketOutboundLinkMessage
|
|
||||||
err := json.Unmarshal(message, &outboundLinkMessage)
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Infof("Could not unmarshal message of message type %s %v", socketMessageBase.MessageType, err)
|
|
||||||
} else {
|
|
||||||
handleTLSLink(outboundLinkMessage)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
logger.Log.Infof("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
|
logger.Log.Infof("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
|
||||||
@@ -126,39 +152,6 @@ func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTLSLink(outboundLinkMessage models.WebsocketOutboundLinkMessage) {
|
|
||||||
resolvedNameObject := k8sResolver.Resolve(outboundLinkMessage.Data.DstIP)
|
|
||||||
if resolvedNameObject != nil {
|
|
||||||
outboundLinkMessage.Data.DstIP = resolvedNameObject.FullAddress
|
|
||||||
} else if outboundLinkMessage.Data.SuggestedResolvedName != "" {
|
|
||||||
outboundLinkMessage.Data.DstIP = outboundLinkMessage.Data.SuggestedResolvedName
|
|
||||||
}
|
|
||||||
cacheKey := fmt.Sprintf("%s -> %s:%d", outboundLinkMessage.Data.Src, outboundLinkMessage.Data.DstIP, outboundLinkMessage.Data.DstPort)
|
|
||||||
_, isInCache := providers.RecentTLSLinks.Get(cacheKey)
|
|
||||||
if isInCache {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
providers.RecentTLSLinks.SetDefault(cacheKey, outboundLinkMessage.Data)
|
|
||||||
}
|
|
||||||
marshaledMessage, err := json.Marshal(outboundLinkMessage)
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Errorf("Error marshaling outbound link message for broadcasting: %v", err)
|
|
||||||
} else {
|
|
||||||
logger.Log.Errorf("Broadcasting outboundlink message %s", string(marshaledMessage))
|
|
||||||
BroadcastToBrowserClients(marshaledMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeSocketUUIDFromBrowserSlice(uuidToRemove int) {
|
|
||||||
newUUIDSlice := make([]int, 0, len(browserClientSocketUUIDs))
|
|
||||||
for _, uuid := range browserClientSocketUUIDs {
|
|
||||||
if uuid != uuidToRemove {
|
|
||||||
newUUIDSlice = append(newUUIDSlice, uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
browserClientSocketUUIDs = newUUIDSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeSocketUUIDFromTapperSlice(uuidToRemove int) {
|
func removeSocketUUIDFromTapperSlice(uuidToRemove int) {
|
||||||
newUUIDSlice := make([]int, 0, len(tapperClientSocketUUIDs))
|
newUUIDSlice := make([]int, 0, len(tapperClientSocketUUIDs))
|
||||||
for _, uuid := range tapperClientSocketUUIDs {
|
for _, uuid := range tapperClientSocketUUIDs {
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/app"
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
"github.com/up9inc/mizu/agent/pkg/har"
|
"github.com/up9inc/mizu/agent/pkg/entries"
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"github.com/up9inc/mizu/agent/pkg/validation"
|
"github.com/up9inc/mizu/agent/pkg/validation"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
basenine "github.com/up9inc/basenine/client/go"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"github.com/up9inc/mizu/shared/logger"
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
tapApi "github.com/up9inc/mizu/tap/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Error(c *gin.Context, err error) bool {
|
func HandleEntriesError(c *gin.Context, err error) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Errorf("Error getting entry: %v", err)
|
logger.Log.Errorf("Error getting entry: %v", err)
|
||||||
_ = c.Error(err)
|
_ = c.Error(err)
|
||||||
@@ -49,45 +44,18 @@ func GetEntries(c *gin.Context) {
|
|||||||
entriesRequest.TimeoutMs = 3000
|
entriesRequest.TimeoutMs = 3000
|
||||||
}
|
}
|
||||||
|
|
||||||
data, meta, err := basenine.Fetch(shared.BasenineHost, shared.BaseninePort,
|
entriesProvider := dependency.GetInstance(dependency.EntriesProvider).(entries.EntriesProvider)
|
||||||
entriesRequest.LeftOff, entriesRequest.Direction, entriesRequest.Query,
|
entries, metadata, err := entriesProvider.GetEntries(entriesRequest)
|
||||||
entriesRequest.Limit, time.Duration(entriesRequest.TimeoutMs)*time.Millisecond)
|
if !HandleEntriesError(c, err) {
|
||||||
if err != nil {
|
baseEntries := make([]interface{}, 0)
|
||||||
c.JSON(http.StatusInternalServerError, validationError)
|
for _, entry := range entries {
|
||||||
}
|
baseEntries = append(baseEntries, entry.Base)
|
||||||
|
|
||||||
response := &models.EntriesResponse{}
|
|
||||||
var dataSlice []interface{}
|
|
||||||
|
|
||||||
for _, row := range data {
|
|
||||||
var entry *tapApi.Entry
|
|
||||||
err = json.Unmarshal(row, &entry)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": true,
|
|
||||||
"type": "error",
|
|
||||||
"autoClose": "5000",
|
|
||||||
"msg": string(row),
|
|
||||||
})
|
|
||||||
return // exit
|
|
||||||
}
|
}
|
||||||
|
c.JSON(http.StatusOK, models.EntriesResponse{
|
||||||
extension := app.ExtensionsMap[entry.Protocol.Name]
|
Data: baseEntries,
|
||||||
base := extension.Dissector.Summarize(entry)
|
Meta: metadata,
|
||||||
|
})
|
||||||
dataSlice = append(dataSlice, base)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadata *basenine.Metadata
|
|
||||||
err = json.Unmarshal(meta, &metadata)
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
response.Data = dataSlice
|
|
||||||
response.Meta = metadata
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEntry(c *gin.Context) {
|
func GetEntry(c *gin.Context) {
|
||||||
@@ -102,54 +70,11 @@ func GetEntry(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, _ := strconv.Atoi(c.Param("id"))
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
var entry *tapApi.Entry
|
|
||||||
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id, singleEntryRequest.Query)
|
|
||||||
if Error(c, err) {
|
|
||||||
return // exit
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(bytes, &entry)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"error": true,
|
|
||||||
"type": "error",
|
|
||||||
"autoClose": "5000",
|
|
||||||
"msg": string(bytes),
|
|
||||||
})
|
|
||||||
return // exit
|
|
||||||
}
|
|
||||||
|
|
||||||
extension := app.ExtensionsMap[entry.Protocol.Name]
|
entriesProvider := dependency.GetInstance(dependency.EntriesProvider).(entries.EntriesProvider)
|
||||||
base := extension.Dissector.Summarize(entry)
|
entry, err := entriesProvider.GetEntry(singleEntryRequest, id)
|
||||||
var representation []byte
|
|
||||||
representation, err = extension.Dissector.Represent(entry.Request, entry.Response)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"error": true,
|
|
||||||
"type": "error",
|
|
||||||
"autoClose": "5000",
|
|
||||||
"msg": err.Error(),
|
|
||||||
})
|
|
||||||
return // exit
|
|
||||||
}
|
|
||||||
|
|
||||||
var rules []map[string]interface{}
|
if !HandleEntriesError(c, err) {
|
||||||
var isRulesEnabled bool
|
c.JSON(http.StatusOK, entry)
|
||||||
if entry.Protocol.Name == "http" {
|
|
||||||
harEntry, _ := har.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
|
|
||||||
_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entry.Destination.Name)
|
|
||||||
isRulesEnabled = _isRulesEnabled
|
|
||||||
inrec, _ := json.Marshal(rulesMatched)
|
|
||||||
if err := json.Unmarshal(inrec, &rules); err != nil {
|
|
||||||
logger.Log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, tapApi.EntryWrapper{
|
|
||||||
Protocol: entry.Protocol,
|
|
||||||
Representation: string(representation),
|
|
||||||
Data: entry,
|
|
||||||
Base: base,
|
|
||||||
Rules: rules,
|
|
||||||
IsRulesEnabled: isRulesEnabled,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"net"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/dependency"
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
"github.com/up9inc/mizu/agent/pkg/oas"
|
"github.com/up9inc/mizu/agent/pkg/oas"
|
||||||
@@ -11,39 +15,55 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetOASServers(t *testing.T) {
|
func TestGetOASServers(t *testing.T) {
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
recorder, c := getRecorderAndContext()
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
c, _ := gin.CreateTestContext(recorder)
|
|
||||||
oas.GetDefaultOasGeneratorInstance().Start()
|
|
||||||
oas.GetDefaultOasGeneratorInstance().GetServiceSpecs().Store("some", oas.NewGen("some"))
|
|
||||||
|
|
||||||
GetOASServers(c)
|
GetOASServers(c)
|
||||||
t.Logf("Written body: %s", recorder.Body.String())
|
t.Logf("Written body: %s", recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOASAllSpecs(t *testing.T) {
|
func TestGetOASAllSpecs(t *testing.T) {
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
recorder, c := getRecorderAndContext()
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
c, _ := gin.CreateTestContext(recorder)
|
|
||||||
oas.GetDefaultOasGeneratorInstance().Start()
|
|
||||||
oas.GetDefaultOasGeneratorInstance().GetServiceSpecs().Store("some", oas.NewGen("some"))
|
|
||||||
|
|
||||||
GetOASAllSpecs(c)
|
GetOASAllSpecs(c)
|
||||||
t.Logf("Written body: %s", recorder.Body.String())
|
t.Logf("Written body: %s", recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOASSpec(t *testing.T) {
|
func TestGetOASSpec(t *testing.T) {
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
recorder, c := getRecorderAndContext()
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
c, _ := gin.CreateTestContext(recorder)
|
|
||||||
oas.GetDefaultOasGeneratorInstance().Start()
|
|
||||||
oas.GetDefaultOasGeneratorInstance().GetServiceSpecs().Store("some", oas.NewGen("some"))
|
|
||||||
|
|
||||||
c.Params = []gin.Param{{Key: "id", Value: "some"}}
|
c.Params = []gin.Param{{Key: "id", Value: "some"}}
|
||||||
|
|
||||||
GetOASSpec(c)
|
GetOASSpec(c)
|
||||||
t.Logf("Written body: %s", recorder.Body.String())
|
t.Logf("Written body: %s", recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeConn struct {
|
||||||
|
sendBuffer *bytes.Buffer
|
||||||
|
receiveBuffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeConn) Read(p []byte) (int, error) { return f.sendBuffer.Read(p) }
|
||||||
|
func (f fakeConn) Write(p []byte) (int, error) { return f.receiveBuffer.Write(p) }
|
||||||
|
func (fakeConn) Close() error { return nil }
|
||||||
|
func (fakeConn) LocalAddr() net.Addr { return nil }
|
||||||
|
func (fakeConn) RemoteAddr() net.Addr { return nil }
|
||||||
|
func (fakeConn) SetDeadline(t time.Time) error { return nil }
|
||||||
|
func (fakeConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
|
func (fakeConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
func getRecorderAndContext() (*httptest.ResponseRecorder, *gin.Context) {
|
||||||
|
dummyConn := new(basenine.Connection)
|
||||||
|
dummyConn.Conn = fakeConn{
|
||||||
|
sendBuffer: bytes.NewBufferString("\n"),
|
||||||
|
receiveBuffer: bytes.NewBufferString("\n"),
|
||||||
|
}
|
||||||
|
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} {
|
||||||
|
return oas.GetDefaultOasGeneratorInstance(dummyConn)
|
||||||
|
})
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(recorder)
|
||||||
|
oas.GetDefaultOasGeneratorInstance(dummyConn).Start()
|
||||||
|
oas.GetDefaultOasGeneratorInstance(dummyConn).GetServiceSpecs().Store("some", oas.NewGen("some"))
|
||||||
|
return recorder, c
|
||||||
|
}
|
||||||
|
|||||||
@@ -101,16 +101,18 @@ func (s *ServiceMapControllerSuite) TestGet() {
|
|||||||
|
|
||||||
// response nodes
|
// response nodes
|
||||||
aNode := servicemap.ServiceMapNode{
|
aNode := servicemap.ServiceMapNode{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
Name: TCPEntryA.Name,
|
Name: TCPEntryA.Name,
|
||||||
Entry: TCPEntryA,
|
Entry: TCPEntryA,
|
||||||
Count: 1,
|
Resolved: true,
|
||||||
|
Count: 1,
|
||||||
}
|
}
|
||||||
bNode := servicemap.ServiceMapNode{
|
bNode := servicemap.ServiceMapNode{
|
||||||
Id: 2,
|
Id: 2,
|
||||||
Name: TCPEntryB.Name,
|
Name: TCPEntryB.Name,
|
||||||
Entry: TCPEntryB,
|
Entry: TCPEntryB,
|
||||||
Count: 1,
|
Resolved: true,
|
||||||
|
Count: 1,
|
||||||
}
|
}
|
||||||
assert.Contains(response.Nodes, aNode)
|
assert.Contains(response.Nodes, aNode)
|
||||||
assert.Contains(response.Nodes, bNode)
|
assert.Contains(response.Nodes, bNode)
|
||||||
|
|||||||
@@ -5,4 +5,7 @@ type DependencyContainerType string
|
|||||||
const (
|
const (
|
||||||
ServiceMapGeneratorDependency = "ServiceMapGeneratorDependency"
|
ServiceMapGeneratorDependency = "ServiceMapGeneratorDependency"
|
||||||
OasGeneratorDependency = "OasGeneratorDependency"
|
OasGeneratorDependency = "OasGeneratorDependency"
|
||||||
|
EntriesProvider = "EntriesProvider"
|
||||||
|
EntriesSocketStreamer = "EntriesSocketStreamer"
|
||||||
|
EntryStreamerSocketConnector = "EntryStreamerSocketConnector"
|
||||||
)
|
)
|
||||||
|
|||||||
98
agent/pkg/entries/entries_provider.go
Normal file
98
agent/pkg/entries/entries_provider.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package entries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/app"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/har"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntriesProvider interface {
|
||||||
|
GetEntries(entriesRequest *models.EntriesRequest) ([]*tapApi.EntryWrapper, *basenine.Metadata, error)
|
||||||
|
GetEntry(singleEntryRequest *models.SingleEntryRequest, entryId int) (*tapApi.EntryWrapper, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasenineEntriesProvider struct{}
|
||||||
|
|
||||||
|
func (e *BasenineEntriesProvider) GetEntries(entriesRequest *models.EntriesRequest) ([]*tapApi.EntryWrapper, *basenine.Metadata, error) {
|
||||||
|
data, meta, err := basenine.Fetch(shared.BasenineHost, shared.BaseninePort,
|
||||||
|
entriesRequest.LeftOff, entriesRequest.Direction, entriesRequest.Query,
|
||||||
|
entriesRequest.Limit, time.Duration(entriesRequest.TimeoutMs)*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataSlice []*tapApi.EntryWrapper
|
||||||
|
|
||||||
|
for _, row := range data {
|
||||||
|
var entry *tapApi.Entry
|
||||||
|
err = json.Unmarshal(row, &entry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := app.ExtensionsMap[entry.Protocol.Name]
|
||||||
|
base := extension.Dissector.Summarize(entry)
|
||||||
|
|
||||||
|
dataSlice = append(dataSlice, &tapApi.EntryWrapper{
|
||||||
|
Protocol: entry.Protocol,
|
||||||
|
Data: entry,
|
||||||
|
Base: base,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata *basenine.Metadata
|
||||||
|
err = json.Unmarshal(meta, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSlice, metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BasenineEntriesProvider) GetEntry(singleEntryRequest *models.SingleEntryRequest, entryId int) (*tapApi.EntryWrapper, error) {
|
||||||
|
var entry *tapApi.Entry
|
||||||
|
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, entryId, singleEntryRequest.Query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(bytes, &entry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := app.ExtensionsMap[entry.Protocol.Name]
|
||||||
|
base := extension.Dissector.Summarize(entry)
|
||||||
|
var representation []byte
|
||||||
|
representation, err = extension.Dissector.Represent(entry.Request, entry.Response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rules []map[string]interface{}
|
||||||
|
var isRulesEnabled bool
|
||||||
|
if entry.Protocol.Name == "http" {
|
||||||
|
harEntry, _ := har.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
|
||||||
|
_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entry.Destination.Name)
|
||||||
|
isRulesEnabled = _isRulesEnabled
|
||||||
|
inrec, _ := json.Marshal(rulesMatched)
|
||||||
|
if err := json.Unmarshal(inrec, &rules); err != nil {
|
||||||
|
logger.Log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tapApi.EntryWrapper{
|
||||||
|
Protocol: entry.Protocol,
|
||||||
|
Representation: string(representation),
|
||||||
|
Data: entry,
|
||||||
|
Base: base,
|
||||||
|
Rules: rules,
|
||||||
|
IsRulesEnabled: isRulesEnabled,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -67,23 +67,23 @@ func fileSize(fname string) int64 {
|
|||||||
return fi.Size()
|
return fi.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedEntries(fromFiles []string, isSync bool) (count int, err error) {
|
func feedEntries(fromFiles []string, isSync bool, gen *defaultOasGenerator) (count uint, err error) {
|
||||||
badFiles := make([]string, 0)
|
badFiles := make([]string, 0)
|
||||||
cnt := 0
|
cnt := uint(0)
|
||||||
for _, file := range fromFiles {
|
for _, file := range fromFiles {
|
||||||
logger.Log.Info("Processing file: " + file)
|
logger.Log.Info("Processing file: " + file)
|
||||||
ext := strings.ToLower(filepath.Ext(file))
|
ext := strings.ToLower(filepath.Ext(file))
|
||||||
eCnt := 0
|
eCnt := uint(0)
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".har":
|
case ".har":
|
||||||
eCnt, err = feedFromHAR(file, isSync)
|
eCnt, err = feedFromHAR(file, isSync, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||||
badFiles = append(badFiles, file)
|
badFiles = append(badFiles, file)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case ".ldjson":
|
case ".ldjson":
|
||||||
eCnt, err = feedFromLDJSON(file, isSync)
|
eCnt, err = feedFromLDJSON(file, isSync, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||||
badFiles = append(badFiles, file)
|
badFiles = append(badFiles, file)
|
||||||
@@ -102,7 +102,7 @@ func feedEntries(fromFiles []string, isSync bool) (count int, err error) {
|
|||||||
return cnt, nil
|
return cnt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedFromHAR(file string, isSync bool) (int, error) {
|
func feedFromHAR(file string, isSync bool, gen *defaultOasGenerator) (uint, error) {
|
||||||
fd, err := os.Open(file)
|
fd, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -121,16 +121,16 @@ func feedFromHAR(file string, isSync bool) (int, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt := 0
|
cnt := uint(0)
|
||||||
for _, entry := range harDoc.Log.Entries {
|
for _, entry := range harDoc.Log.Entries {
|
||||||
cnt += 1
|
cnt += 1
|
||||||
feedEntry(&entry, "", isSync, file)
|
feedEntry(&entry, "", file, gen, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cnt, nil
|
return cnt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedEntry(entry *har.Entry, source string, isSync bool, file string) {
|
func feedEntry(entry *har.Entry, source string, file string, gen *defaultOasGenerator, cnt uint) {
|
||||||
entry.Comment = file
|
entry.Comment = file
|
||||||
if entry.Response.Status == 302 {
|
if entry.Response.Status == 302 {
|
||||||
logger.Log.Debugf("Dropped traffic entry due to permanent redirect status: %s", entry.StartedDateTime)
|
logger.Log.Debugf("Dropped traffic entry due to permanent redirect status: %s", entry.StartedDateTime)
|
||||||
@@ -145,15 +145,11 @@ func feedEntry(entry *har.Entry, source string, isSync bool, file string) {
|
|||||||
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err)
|
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ews := EntryWithSource{Entry: *entry, Source: source, Destination: u.Host, Id: uint(0)}
|
ews := EntryWithSource{Entry: *entry, Source: source, Destination: u.Host, Id: cnt}
|
||||||
if isSync {
|
gen.handleHARWithSource(&ews)
|
||||||
GetDefaultOasGeneratorInstance().entriesChan <- ews // blocking variant, right?
|
|
||||||
} else {
|
|
||||||
GetDefaultOasGeneratorInstance().PushEntry(&ews)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedFromLDJSON(file string, isSync bool) (int, error) {
|
func feedFromLDJSON(file string, isSync bool, gen *defaultOasGenerator) (uint, error) {
|
||||||
fd, err := os.Open(file)
|
fd, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -165,7 +161,7 @@ func feedFromLDJSON(file string, isSync bool) (int, error) {
|
|||||||
|
|
||||||
var meta map[string]interface{}
|
var meta map[string]interface{}
|
||||||
buf := strings.Builder{}
|
buf := strings.Builder{}
|
||||||
cnt := 0
|
cnt := uint(0)
|
||||||
source := ""
|
source := ""
|
||||||
for {
|
for {
|
||||||
substr, isPrefix, err := reader.ReadLine()
|
substr, isPrefix, err := reader.ReadLine()
|
||||||
@@ -196,7 +192,7 @@ func feedFromLDJSON(file string, isSync bool) (int, error) {
|
|||||||
logger.Log.Warningf("Failed decoding entry: %s", line)
|
logger.Log.Warningf("Failed decoding entry: %s", line)
|
||||||
} else {
|
} else {
|
||||||
cnt += 1
|
cnt += 1
|
||||||
feedEntry(&entry, source, isSync, file)
|
feedEntry(&entry, source, file, gen, cnt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package oas
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/har"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/tap/api"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/har"
|
|
||||||
"github.com/up9inc/mizu/shared/logger"
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,10 +18,6 @@ var (
|
|||||||
instance *defaultOasGenerator
|
instance *defaultOasGenerator
|
||||||
)
|
)
|
||||||
|
|
||||||
type OasGeneratorSink interface {
|
|
||||||
PushEntry(entryWithSource *EntryWithSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OasGenerator interface {
|
type OasGenerator interface {
|
||||||
Start()
|
Start()
|
||||||
Stop()
|
Stop()
|
||||||
@@ -32,12 +31,20 @@ type defaultOasGenerator struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
serviceSpecs *sync.Map
|
serviceSpecs *sync.Map
|
||||||
entriesChan chan EntryWithSource
|
dbConn *basenine.Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultOasGeneratorInstance() *defaultOasGenerator {
|
func GetDefaultOasGeneratorInstance(conn *basenine.Connection) *defaultOasGenerator {
|
||||||
syncOnce.Do(func() {
|
syncOnce.Do(func() {
|
||||||
instance = NewDefaultOasGenerator()
|
if conn == nil {
|
||||||
|
c, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
conn = c
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = NewDefaultOasGenerator(conn)
|
||||||
logger.Log.Debug("OAS Generator Initialized")
|
logger.Log.Debug("OAS Generator Initialized")
|
||||||
})
|
})
|
||||||
return instance
|
return instance
|
||||||
@@ -50,7 +57,6 @@ func (g *defaultOasGenerator) Start() {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
g.cancel = cancel
|
g.cancel = cancel
|
||||||
g.ctx = ctx
|
g.ctx = ctx
|
||||||
g.entriesChan = make(chan EntryWithSource, 100) // buffer up to 100 entries for OAS processing
|
|
||||||
g.serviceSpecs = &sync.Map{}
|
g.serviceSpecs = &sync.Map{}
|
||||||
g.started = true
|
g.started = true
|
||||||
go g.runGenerator()
|
go g.runGenerator()
|
||||||
@@ -70,80 +76,117 @@ func (g *defaultOasGenerator) IsStarted() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *defaultOasGenerator) runGenerator() {
|
func (g *defaultOasGenerator) runGenerator() {
|
||||||
|
// Make []byte channels to recieve the data and the meta
|
||||||
|
dataChan := make(chan []byte)
|
||||||
|
metaChan := make(chan []byte)
|
||||||
|
|
||||||
|
g.dbConn.Query("", dataChan, metaChan)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-g.ctx.Done():
|
case <-g.ctx.Done():
|
||||||
logger.Log.Infof("OAS Generator was canceled")
|
logger.Log.Infof("OAS Generator was canceled")
|
||||||
return
|
return
|
||||||
|
|
||||||
case entryWithSource, ok := <-g.entriesChan:
|
case metaBytes, ok := <-metaChan:
|
||||||
|
if !ok {
|
||||||
|
logger.Log.Infof("OAS Generator - meta channel closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logger.Log.Debugf("Meta: %s", metaBytes)
|
||||||
|
|
||||||
|
case dataBytes, ok := <-dataChan:
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Log.Infof("OAS Generator - entries channel closed")
|
logger.Log.Infof("OAS Generator - entries channel closed")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
entry := entryWithSource.Entry
|
|
||||||
u, err := url.Parse(entry.Request.URL)
|
logger.Log.Debugf("Data: %s", dataBytes)
|
||||||
|
e := new(api.Entry)
|
||||||
|
err := json.Unmarshal(dataBytes, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val, found := g.serviceSpecs.Load(entryWithSource.Destination)
|
|
||||||
var gen *SpecGen
|
|
||||||
if !found {
|
|
||||||
gen = NewGen(u.Scheme + "://" + entryWithSource.Destination)
|
|
||||||
g.serviceSpecs.Store(entryWithSource.Destination, gen)
|
|
||||||
} else {
|
|
||||||
gen = val.(*SpecGen)
|
|
||||||
}
|
|
||||||
|
|
||||||
opId, err := gen.feedEntry(entryWithSource)
|
|
||||||
if err != nil {
|
|
||||||
txt, suberr := json.Marshal(entry)
|
|
||||||
if suberr == nil {
|
|
||||||
logger.Log.Debugf("Problematic entry: %s", txt)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Log.Warningf("Failed processing entry: %s", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
g.handleEntry(e)
|
||||||
logger.Log.Debugf("Handled entry %s as opId: %s", entry.Request.URL, opId) // TODO: set opId back to entry?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *defaultOasGenerator) handleEntry(mizuEntry *api.Entry) {
|
||||||
|
if mizuEntry.Protocol.Name == "http" {
|
||||||
|
entry, err := har.NewEntry(mizuEntry.Request, mizuEntry.Response, mizuEntry.StartTime, mizuEntry.ElapsedTime)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Warningf("Failed to turn MizuEntry %d into HAR Entry: %s", mizuEntry.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := mizuEntry.Destination.Name
|
||||||
|
if dest == "" {
|
||||||
|
dest = mizuEntry.Destination.IP + ":" + mizuEntry.Destination.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
entryWSource := &EntryWithSource{
|
||||||
|
Entry: *entry,
|
||||||
|
Source: mizuEntry.Source.Name,
|
||||||
|
Destination: dest,
|
||||||
|
Id: mizuEntry.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
g.handleHARWithSource(entryWSource)
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("OAS: Unsupported protocol in entry %d: %s", mizuEntry.Id, mizuEntry.Protocol.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *defaultOasGenerator) handleHARWithSource(entryWSource *EntryWithSource) {
|
||||||
|
entry := entryWSource.Entry
|
||||||
|
gen := g.getGen(entryWSource.Destination, entry.Request.URL)
|
||||||
|
|
||||||
|
opId, err := gen.feedEntry(entryWSource)
|
||||||
|
if err != nil {
|
||||||
|
txt, suberr := json.Marshal(entry)
|
||||||
|
if suberr == nil {
|
||||||
|
logger.Log.Debugf("Problematic entry: %s", txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Warningf("Failed processing entry %d: %s", entryWSource.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Handled entry %d as opId: %s", entryWSource.Id, opId) // TODO: set opId back to entry?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *defaultOasGenerator) getGen(dest string, urlStr string) *SpecGen {
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", urlStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, found := g.serviceSpecs.Load(dest)
|
||||||
|
var gen *SpecGen
|
||||||
|
if !found {
|
||||||
|
gen = NewGen(u.Scheme + "://" + dest)
|
||||||
|
g.serviceSpecs.Store(dest, gen)
|
||||||
|
} else {
|
||||||
|
gen = val.(*SpecGen)
|
||||||
|
}
|
||||||
|
return gen
|
||||||
|
}
|
||||||
|
|
||||||
func (g *defaultOasGenerator) Reset() {
|
func (g *defaultOasGenerator) Reset() {
|
||||||
g.serviceSpecs = &sync.Map{}
|
g.serviceSpecs = &sync.Map{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *defaultOasGenerator) PushEntry(entryWithSource *EntryWithSource) {
|
|
||||||
if !g.started {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case g.entriesChan <- *entryWithSource:
|
|
||||||
default:
|
|
||||||
logger.Log.Warningf("OAS Generator - entry wasn't sent to channel because the channel has no buffer or there is no receiver")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *defaultOasGenerator) GetServiceSpecs() *sync.Map {
|
func (g *defaultOasGenerator) GetServiceSpecs() *sync.Map {
|
||||||
return g.serviceSpecs
|
return g.serviceSpecs
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultOasGenerator() *defaultOasGenerator {
|
func NewDefaultOasGenerator(c *basenine.Connection) *defaultOasGenerator {
|
||||||
return &defaultOasGenerator{
|
return &defaultOasGenerator{
|
||||||
started: false,
|
started: false,
|
||||||
ctx: nil,
|
ctx: nil,
|
||||||
cancel: nil,
|
cancel: nil,
|
||||||
serviceSpecs: nil,
|
serviceSpecs: nil,
|
||||||
entriesChan: nil,
|
dbConn: c,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryWithSource struct {
|
|
||||||
Source string
|
|
||||||
Destination string
|
|
||||||
Entry har.Entry
|
|
||||||
Id uint
|
|
||||||
}
|
|
||||||
|
|||||||
36
agent/pkg/oas/oas_generator_test.go
Normal file
36
agent/pkg/oas/oas_generator_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package oas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/har"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOASGen(t *testing.T) {
|
||||||
|
gen := new(defaultOasGenerator)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ews := &EntryWithSource{
|
||||||
|
Destination: "some",
|
||||||
|
Entry: *e,
|
||||||
|
}
|
||||||
|
gen.handleHARWithSource(ews)
|
||||||
|
g, ok := gen.serviceSpecs.Load("some")
|
||||||
|
if !ok {
|
||||||
|
panic("Failed")
|
||||||
|
}
|
||||||
|
sg := g.(*SpecGen)
|
||||||
|
spec, err := sg.GetSpec()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
specText, _ := json.Marshal(spec)
|
||||||
|
t.Log(string(specText))
|
||||||
|
}
|
||||||
@@ -28,6 +28,13 @@ const CountersTotal = "x-counters-total"
|
|||||||
const CountersPerSource = "x-counters-per-source"
|
const CountersPerSource = "x-counters-per-source"
|
||||||
const SampleId = "x-sample-entry"
|
const SampleId = "x-sample-entry"
|
||||||
|
|
||||||
|
type EntryWithSource struct {
|
||||||
|
Source string
|
||||||
|
Destination string
|
||||||
|
Entry har.Entry
|
||||||
|
Id uint
|
||||||
|
}
|
||||||
|
|
||||||
type reqResp struct { // hello, generics in Go
|
type reqResp struct { // hello, generics in Go
|
||||||
Req *har.Request
|
Req *har.Request
|
||||||
Resp *har.Response
|
Resp *har.Response
|
||||||
@@ -60,7 +67,7 @@ func (g *SpecGen) StartFromSpec(oas *openapi.OpenAPI) {
|
|||||||
g.tree = new(Node)
|
g.tree = new(Node)
|
||||||
for pathStr, pathObj := range oas.Paths.Items {
|
for pathStr, pathObj := range oas.Paths.Items {
|
||||||
pathSplit := strings.Split(string(pathStr), "/")
|
pathSplit := strings.Split(string(pathStr), "/")
|
||||||
g.tree.getOrSet(pathSplit, pathObj)
|
g.tree.getOrSet(pathSplit, pathObj, 0)
|
||||||
|
|
||||||
// clean "last entry timestamp" markers from the past
|
// clean "last entry timestamp" markers from the past
|
||||||
for _, pathAndOp := range g.tree.listOps() {
|
for _, pathAndOp := range g.tree.listOps() {
|
||||||
@@ -69,11 +76,11 @@ func (g *SpecGen) StartFromSpec(oas *openapi.OpenAPI) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *SpecGen) feedEntry(entryWithSource EntryWithSource) (string, error) {
|
func (g *SpecGen) feedEntry(entryWithSource *EntryWithSource) (string, error) {
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
defer g.lock.Unlock()
|
defer g.lock.Unlock()
|
||||||
|
|
||||||
opId, err := g.handlePathObj(&entryWithSource)
|
opId, err := g.handlePathObj(entryWithSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -219,7 +226,7 @@ func (g *SpecGen) handlePathObj(entryWithSource *EntryWithSource) (string, error
|
|||||||
} else {
|
} else {
|
||||||
split = strings.Split(urlParsed.Path, "/")
|
split = strings.Split(urlParsed.Path, "/")
|
||||||
}
|
}
|
||||||
node := g.tree.getOrSet(split, new(openapi.PathObj))
|
node := g.tree.getOrSet(split, new(openapi.PathObj), entryWithSource.Id)
|
||||||
opObj, err := handleOpObj(entryWithSource, node.pathObj)
|
opObj, err := handleOpObj(entryWithSource, node.pathObj)
|
||||||
|
|
||||||
if opObj != nil {
|
if opObj != nil {
|
||||||
@@ -242,12 +249,12 @@ func handleOpObj(entryWithSource *EntryWithSource, pathObj *openapi.PathObj) (*o
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleRequest(&entry.Request, opObj, isSuccess)
|
err = handleRequest(&entry.Request, opObj, isSuccess, entryWithSource.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleResponse(&entry.Response, opObj, isSuccess)
|
err = handleResponse(&entry.Response, opObj, isSuccess, entryWithSource.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -257,6 +264,8 @@ func handleOpObj(entryWithSource *EntryWithSource, pathObj *openapi.PathObj) (*o
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSampleID(&opObj.Extensions, entryWithSource.Id)
|
||||||
|
|
||||||
return opObj, nil
|
return opObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,15 +338,10 @@ func handleCounters(opObj *openapi.Operation, success bool, entryWithSource *Ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = opObj.Extensions.SetExtension(SampleId, entryWithSource.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) error {
|
func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool, sampleId uint) error {
|
||||||
// TODO: we don't handle the situation when header/qstr param can be defined on pathObj level. Also the path param defined on opObj
|
// TODO: we don't handle the situation when header/qstr param can be defined on pathObj level. Also the path param defined on opObj
|
||||||
urlParsed, err := url.Parse(req.URL)
|
urlParsed, err := url.Parse(req.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -361,7 +365,7 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
IsIgnored: func(name string) bool { return false },
|
IsIgnored: func(name string) bool { return false },
|
||||||
GeneralizeName: func(name string) string { return name },
|
GeneralizeName: func(name string) string { return name },
|
||||||
}
|
}
|
||||||
handleNameVals(qstrGW, &opObj.Parameters, false)
|
handleNameVals(qstrGW, &opObj.Parameters, false, sampleId)
|
||||||
|
|
||||||
hdrGW := nvParams{
|
hdrGW := nvParams{
|
||||||
In: openapi.InHeader,
|
In: openapi.InHeader,
|
||||||
@@ -369,7 +373,7 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
IsIgnored: isHeaderIgnored,
|
IsIgnored: isHeaderIgnored,
|
||||||
GeneralizeName: strings.ToLower,
|
GeneralizeName: strings.ToLower,
|
||||||
}
|
}
|
||||||
handleNameVals(hdrGW, &opObj.Parameters, true)
|
handleNameVals(hdrGW, &opObj.Parameters, true, sampleId)
|
||||||
|
|
||||||
if isSuccess {
|
if isSuccess {
|
||||||
reqBody, err := getRequestBody(req, opObj)
|
reqBody, err := getRequestBody(req, opObj)
|
||||||
@@ -378,12 +382,14 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reqBody != nil {
|
if reqBody != nil {
|
||||||
|
setSampleID(&reqBody.Extensions, sampleId)
|
||||||
|
|
||||||
if req.PostData.Text == "" {
|
if req.PostData.Text == "" {
|
||||||
reqBody.Required = false
|
reqBody.Required = false
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
reqCtype, _ := getReqCtype(req)
|
reqCtype, _ := getReqCtype(req)
|
||||||
reqMedia, err := fillContent(reqResp{Req: req}, reqBody.Content, reqCtype)
|
reqMedia, err := fillContent(reqResp{Req: req}, reqBody.Content, reqCtype, sampleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -395,18 +401,20 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleResponse(resp *har.Response, opObj *openapi.Operation, isSuccess bool) error {
|
func handleResponse(resp *har.Response, opObj *openapi.Operation, isSuccess bool, sampleId uint) error {
|
||||||
// TODO: we don't support "default" response
|
// TODO: we don't support "default" response
|
||||||
respObj, err := getResponseObj(resp, opObj, isSuccess)
|
respObj, err := getResponseObj(resp, opObj, isSuccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRespHeaders(resp.Headers, respObj)
|
setSampleID(&respObj.Extensions, sampleId)
|
||||||
|
|
||||||
|
handleRespHeaders(resp.Headers, respObj, sampleId)
|
||||||
|
|
||||||
respCtype := getRespCtype(resp)
|
respCtype := getRespCtype(resp)
|
||||||
respContent := respObj.Content
|
respContent := respObj.Content
|
||||||
respMedia, err := fillContent(reqResp{Resp: resp}, respContent, respCtype)
|
respMedia, err := fillContent(reqResp{Resp: resp}, respContent, respCtype, sampleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -414,7 +422,7 @@ func handleResponse(resp *har.Response, opObj *openapi.Operation, isSuccess bool
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj) {
|
func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj, sampleId uint) {
|
||||||
visited := map[string]*openapi.HeaderObj{}
|
visited := map[string]*openapi.HeaderObj{}
|
||||||
for _, pair := range reqHeaders {
|
for _, pair := range reqHeaders {
|
||||||
if isHeaderIgnored(pair.Name) {
|
if isHeaderIgnored(pair.Name) {
|
||||||
@@ -436,6 +444,8 @@ func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj) {
|
|||||||
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
||||||
}
|
}
|
||||||
visited[nameGeneral] = param
|
visited[nameGeneral] = param
|
||||||
|
|
||||||
|
setSampleID(¶m.Extensions, sampleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// maintain "required" flag
|
// maintain "required" flag
|
||||||
@@ -456,13 +466,15 @@ func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillContent(reqResp reqResp, respContent openapi.Content, ctype string) (*openapi.MediaType, error) {
|
func fillContent(reqResp reqResp, respContent openapi.Content, ctype string, sampleId uint) (*openapi.MediaType, error) {
|
||||||
content, found := respContent[ctype]
|
content, found := respContent[ctype]
|
||||||
if !found {
|
if !found {
|
||||||
respContent[ctype] = &openapi.MediaType{}
|
respContent[ctype] = &openapi.MediaType{}
|
||||||
content = respContent[ctype]
|
content = respContent[ctype]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSampleID(&content.Extensions, sampleId)
|
||||||
|
|
||||||
var text string
|
var text string
|
||||||
var isBinary bool
|
var isBinary bool
|
||||||
if reqResp.Req != nil {
|
if reqResp.Req != nil {
|
||||||
@@ -474,10 +486,10 @@ func fillContent(reqResp reqResp, respContent openapi.Content, ctype string) (*o
|
|||||||
if !isBinary && text != "" {
|
if !isBinary && text != "" {
|
||||||
var exampleMsg []byte
|
var exampleMsg []byte
|
||||||
// try treating it as json
|
// try treating it as json
|
||||||
any, isJSON := anyJSON(text)
|
anyVal, isJSON := anyJSON(text)
|
||||||
if isJSON {
|
if isJSON {
|
||||||
// re-marshal with forced indent
|
// re-marshal with forced indent
|
||||||
if msg, err := json.MarshalIndent(any, "", "\t"); err != nil {
|
if msg, err := json.MarshalIndent(anyVal, "", "\t"); err != nil {
|
||||||
panic("Failed to re-marshal value, super-strange")
|
panic("Failed to re-marshal value, super-strange")
|
||||||
} else {
|
} else {
|
||||||
exampleMsg = msg
|
exampleMsg = msg
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ import (
|
|||||||
|
|
||||||
// if started via env, write file into subdir
|
// if started via env, write file into subdir
|
||||||
func outputSpec(label string, spec *openapi.OpenAPI, t *testing.T) string {
|
func outputSpec(label string, spec *openapi.OpenAPI, t *testing.T) string {
|
||||||
content, err := json.MarshalIndent(spec, "", "\t")
|
content, err := json.MarshalIndent(spec, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -48,14 +49,16 @@ func TestEntries(t *testing.T) {
|
|||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
GetDefaultOasGeneratorInstance().Start()
|
|
||||||
loadStartingOAS("test_artifacts/catalogue.json", "catalogue")
|
gen := NewDefaultOasGenerator(nil)
|
||||||
loadStartingOAS("test_artifacts/trcc.json", "trcc-api-service")
|
gen.serviceSpecs = new(sync.Map)
|
||||||
|
loadStartingOAS("test_artifacts/catalogue.json", "catalogue", gen.serviceSpecs)
|
||||||
|
loadStartingOAS("test_artifacts/trcc.json", "trcc-api-service", gen.serviceSpecs)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
t.Logf("Getting spec for %s", svc)
|
t.Logf("Getting spec for %s", svc)
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
@@ -68,16 +71,14 @@ func TestEntries(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cnt, err := feedEntries(files, true)
|
cnt, err := feedEntries(files, true, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
waitQueueProcessed()
|
|
||||||
|
|
||||||
svcs := strings.Builder{}
|
svcs := strings.Builder{}
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
svcs.WriteString(svc + ",")
|
svcs.WriteString(svc + ",")
|
||||||
@@ -99,7 +100,7 @@ func TestEntries(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
spec, err := gen.GetSpec()
|
spec, err := gen.GetSpec()
|
||||||
@@ -123,20 +124,18 @@ func TestEntries(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSingle(t *testing.T) {
|
func TestFileSingle(t *testing.T) {
|
||||||
GetDefaultOasGeneratorInstance().Start()
|
gen := NewDefaultOasGenerator(nil)
|
||||||
GetDefaultOasGeneratorInstance().Reset()
|
gen.serviceSpecs = new(sync.Map)
|
||||||
// loadStartingOAS()
|
// loadStartingOAS()
|
||||||
file := "test_artifacts/params.har"
|
file := "test_artifacts/params.har"
|
||||||
files := []string{file}
|
files := []string{file}
|
||||||
cnt, err := feedEntries(files, true)
|
cnt, err := feedEntries(files, true, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
waitQueueProcessed()
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
|
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
spec, err := gen.GetSpec()
|
spec, err := gen.GetSpec()
|
||||||
@@ -189,18 +188,7 @@ func TestFileSingle(t *testing.T) {
|
|||||||
logger.Log.Infof("Processed entries: %d", cnt)
|
logger.Log.Infof("Processed entries: %d", cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitQueueProcessed() {
|
func loadStartingOAS(file string, label string, specs *sync.Map) {
|
||||||
for {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
queue := len(GetDefaultOasGeneratorInstance().entriesChan)
|
|
||||||
logger.Log.Infof("Queue: %d", queue)
|
|
||||||
if queue < 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadStartingOAS(file string, label string) {
|
|
||||||
fd, err := os.Open(file)
|
fd, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -222,12 +210,14 @@ func loadStartingOAS(file string, label string) {
|
|||||||
gen := NewGen(label)
|
gen := NewGen(label)
|
||||||
gen.StartFromSpec(doc)
|
gen.StartFromSpec(doc)
|
||||||
|
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Store(label, gen)
|
specs.Store(label, gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEntriesNegative(t *testing.T) {
|
func TestEntriesNegative(t *testing.T) {
|
||||||
|
gen := NewDefaultOasGenerator(nil)
|
||||||
|
gen.serviceSpecs = new(sync.Map)
|
||||||
files := []string{"invalid"}
|
files := []string{"invalid"}
|
||||||
_, err := feedEntries(files, false)
|
_, err := feedEntries(files, false, gen)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Logf("Should have failed")
|
t.Logf("Should have failed")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@@ -235,8 +225,10 @@ func TestEntriesNegative(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEntriesPositive(t *testing.T) {
|
func TestEntriesPositive(t *testing.T) {
|
||||||
|
gen := NewDefaultOasGenerator(nil)
|
||||||
|
gen.serviceSpecs = new(sync.Map)
|
||||||
files := []string{"test_artifacts/params.har"}
|
files := []string{"test_artifacts/params.har"}
|
||||||
_, err := feedEntries(files, false)
|
_, err := feedEntries(files, false, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Failed")
|
t.Logf("Failed")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
|||||||
@@ -21,9 +21,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 4
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -45,7 +47,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750580.04,
|
"x-last-seen-ts": 1567750580.04,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/appears-twice": {
|
"/appears-twice": {
|
||||||
@@ -58,9 +60,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 6
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -82,7 +86,7 @@
|
|||||||
"sumDuration": 1
|
"sumDuration": 1
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.74,
|
"x-last-seen-ts": 1567750581.74,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/body-optional": {
|
"/body-optional": {
|
||||||
@@ -94,8 +98,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 12
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -117,14 +124,16 @@
|
|||||||
"sumDuration": 0.01
|
"sumDuration": 0.01
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.75,
|
"x-last-seen-ts": 1567750581.75,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 12,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": "{\"key\", \"val\"}"
|
"example": "{\"key\", \"val\"}",
|
||||||
|
"x-sample-entry": 11
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -137,8 +146,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 13
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -160,15 +172,17 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.75,
|
"x-last-seen-ts": 1567750581.75,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 13,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
"": {
|
"": {
|
||||||
"example": "body exists"
|
"example": "body exists",
|
||||||
|
"x-sample-entry": 13
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true,
|
||||||
|
"x-sample-entry": 13
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -182,9 +196,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {
|
"": {
|
||||||
"example": {}
|
"example": {},
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -206,7 +222,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.74,
|
"x-last-seen-ts": 1567750582.74,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 9,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -233,10 +249,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": "--BOUNDARY\r\nContent-Disposition: form-data; name=\"file\"; filename=\"metadata.json\"\r\nContent-Type: application/json\r\n\r\n{\"functions\": 123}\r\n--BOUNDARY\r\nContent-Disposition: form-data; name=\"path\"\r\n\r\n/content/components\r\n--BOUNDARY--\r\n"
|
"example": "--BOUNDARY\r\nContent-Disposition: form-data; name=\"file\"; filename=\"metadata.json\"\r\nContent-Type: application/json\r\n\r\n{\"functions\": 123}\r\n--BOUNDARY\r\nContent-Disposition: form-data; name=\"path\"\r\n\r\n/content/components\r\n--BOUNDARY--\r\n",
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true,
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -249,8 +267,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -272,7 +293,7 @@
|
|||||||
"sumDuration": 1
|
"sumDuration": 1
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.74,
|
"x-last-seen-ts": 1567750581.74,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 8,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -312,10 +333,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": "agent-id=ade\u0026callback-url=\u0026token=sometoken"
|
"example": "agent-id=ade\u0026callback-url=\u0026token=sometoken",
|
||||||
|
"x-sample-entry": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true,
|
||||||
|
"x-sample-entry": 8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -331,8 +354,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 14
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -354,7 +380,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582,
|
"x-last-seen-ts": 1567750582,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 14
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -369,7 +395,8 @@
|
|||||||
"example #0": {
|
"example #0": {
|
||||||
"value": "234324"
|
"value": "234324"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 14
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -385,8 +412,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -408,7 +438,7 @@
|
|||||||
"sumDuration": 9.53e-7
|
"sumDuration": 9.53e-7
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 18
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -436,7 +466,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -452,8 +483,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 15
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -475,7 +509,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 15
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -503,7 +537,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -519,8 +554,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 16
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -542,7 +580,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 16
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -570,7 +608,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -586,8 +625,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 19
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -609,7 +651,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 19
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -624,7 +666,8 @@
|
|||||||
"example #0": {
|
"example #0": {
|
||||||
"value": "23421"
|
"value": "23421"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parampatternId",
|
"name": "parampatternId",
|
||||||
@@ -651,7 +694,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -665,9 +709,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -689,7 +735,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750579.74,
|
"x-last-seen-ts": 1567750579.74,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 3
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -707,7 +753,8 @@
|
|||||||
"example #1": {
|
"example #1": {
|
||||||
"value": "<UUID4>"
|
"value": "<UUID4>"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -720,8 +767,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"text/html": {}
|
"text/html": {
|
||||||
}
|
"x-sample-entry": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -743,7 +793,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750483.86,
|
"x-last-seen-ts": 1567750483.86,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 1
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -761,7 +811,8 @@
|
|||||||
"example #1": {
|
"example #1": {
|
||||||
"value": "<UUID4>"
|
"value": "<UUID4>"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -775,9 +826,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 2
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -799,7 +852,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750578.74,
|
"x-last-seen-ts": 1567750578.74,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 2
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -817,7 +870,8 @@
|
|||||||
"example #1": {
|
"example #1": {
|
||||||
"value": "<UUID4>"
|
"value": "<UUID4>"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type Node struct {
|
|||||||
children []*Node
|
children []*Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node *Node) {
|
func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj, sampleId uint) (node *Node) {
|
||||||
if existingPathObj == nil {
|
if existingPathObj == nil {
|
||||||
panic("Invalid function call")
|
panic("Invalid function call")
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,10 @@ func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if node.pathParam != nil {
|
||||||
|
setSampleID(&node.pathParam.Extensions, sampleId)
|
||||||
|
}
|
||||||
|
|
||||||
// add example if it's a gibberish chunk
|
// add example if it's a gibberish chunk
|
||||||
if node.pathParam != nil && !chunkIsParam {
|
if node.pathParam != nil && !chunkIsParam {
|
||||||
exmp := &node.pathParam.Examples
|
exmp := &node.pathParam.Examples
|
||||||
@@ -85,7 +89,7 @@ func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node *
|
|||||||
|
|
||||||
// TODO: eat up trailing slash, in a smart way: node.pathObj!=nil && path[1]==""
|
// TODO: eat up trailing slash, in a smart way: node.pathObj!=nil && path[1]==""
|
||||||
if len(path) > 1 {
|
if len(path) > 1 {
|
||||||
return node.getOrSet(path[1:], existingPathObj)
|
return node.getOrSet(path[1:], existingPathObj, sampleId)
|
||||||
} else if node.pathObj == nil {
|
} else if node.pathObj == nil {
|
||||||
node.pathObj = existingPathObj
|
node.pathObj = existingPathObj
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ func TestTree(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tree := new(Node)
|
tree := new(Node)
|
||||||
for _, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
split := strings.Split(tc.inp, "/")
|
split := strings.Split(tc.inp, "/")
|
||||||
pathObj := new(openapi.PathObj)
|
pathObj := new(openapi.PathObj)
|
||||||
node := tree.getOrSet(split, pathObj)
|
node := tree.getOrSet(split, pathObj, uint(i))
|
||||||
|
|
||||||
fillPathParams(node, pathObj)
|
fillPathParams(node, pathObj)
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ type nvParams struct {
|
|||||||
GeneralizeName func(name string) string
|
GeneralizeName func(name string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNameVals(gw nvParams, params **openapi.ParameterList, checkIgnore bool) {
|
func handleNameVals(gw nvParams, params **openapi.ParameterList, checkIgnore bool, sampleId uint) {
|
||||||
visited := map[string]*openapi.ParameterObj{}
|
visited := map[string]*openapi.ParameterObj{}
|
||||||
for _, pair := range gw.Pairs {
|
for _, pair := range gw.Pairs {
|
||||||
if (checkIgnore && gw.IsIgnored(pair.Name)) || pair.Name == "" {
|
if (checkIgnore && gw.IsIgnored(pair.Name)) || pair.Name == "" {
|
||||||
@@ -137,6 +137,8 @@ func handleNameVals(gw nvParams, params **openapi.ParameterList, checkIgnore boo
|
|||||||
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
||||||
}
|
}
|
||||||
visited[nameGeneral] = param
|
visited[nameGeneral] = param
|
||||||
|
|
||||||
|
setSampleID(¶m.Extensions, sampleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// maintain "required" flag
|
// maintain "required" flag
|
||||||
@@ -474,3 +476,15 @@ func intersectSliceWithMap(required []string, names map[string]struct{}) []strin
|
|||||||
}
|
}
|
||||||
return required
|
return required
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSampleID(extensions *openapi.Extensions, id uint) {
|
||||||
|
if id > 0 {
|
||||||
|
if *extensions == nil {
|
||||||
|
*extensions = openapi.Extensions{}
|
||||||
|
}
|
||||||
|
err := (extensions).SetExtension(SampleId, id)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Warningf("Failed to set sample ID: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ type ServiceMapResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServiceMapNode struct {
|
type ServiceMapNode struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Entry *tapApi.TCP `json:"entry"`
|
Entry *tapApi.TCP `json:"entry"`
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
|
Resolved bool `json:"resolved"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceMapEdge struct {
|
type ServiceMapEdge struct {
|
||||||
|
|||||||
@@ -227,10 +227,11 @@ func (s *defaultServiceMap) GetNodes() []ServiceMapNode {
|
|||||||
var nodes []ServiceMapNode
|
var nodes []ServiceMapNode
|
||||||
for i, n := range s.graph.Nodes {
|
for i, n := range s.graph.Nodes {
|
||||||
nodes = append(nodes, ServiceMapNode{
|
nodes = append(nodes, ServiceMapNode{
|
||||||
Id: n.id,
|
Id: n.id,
|
||||||
Name: string(i),
|
Name: string(i),
|
||||||
Entry: n.entry,
|
Resolved: n.entry.Name != UnresolvedNodeName,
|
||||||
Count: n.count,
|
Entry: n.entry,
|
||||||
|
Count: n.count,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nodes
|
return nodes
|
||||||
@@ -243,16 +244,18 @@ func (s *defaultServiceMap) GetEdges() []ServiceMapEdge {
|
|||||||
for _, p := range s.graph.Edges[u][v].data {
|
for _, p := range s.graph.Edges[u][v].data {
|
||||||
edges = append(edges, ServiceMapEdge{
|
edges = append(edges, ServiceMapEdge{
|
||||||
Source: ServiceMapNode{
|
Source: ServiceMapNode{
|
||||||
Id: s.graph.Nodes[u].id,
|
Id: s.graph.Nodes[u].id,
|
||||||
Name: string(u),
|
Name: string(u),
|
||||||
Entry: s.graph.Nodes[u].entry,
|
Entry: s.graph.Nodes[u].entry,
|
||||||
Count: s.graph.Nodes[u].count,
|
Resolved: s.graph.Nodes[u].entry.Name != UnresolvedNodeName,
|
||||||
|
Count: s.graph.Nodes[u].count,
|
||||||
},
|
},
|
||||||
Destination: ServiceMapNode{
|
Destination: ServiceMapNode{
|
||||||
Id: s.graph.Nodes[v].id,
|
Id: s.graph.Nodes[v].id,
|
||||||
Name: string(v),
|
Name: string(v),
|
||||||
Entry: s.graph.Nodes[v].entry,
|
Entry: s.graph.Nodes[v].entry,
|
||||||
Count: s.graph.Nodes[v].count,
|
Resolved: s.graph.Nodes[v].entry.Name != UnresolvedNodeName,
|
||||||
|
Count: s.graph.Nodes[v].count,
|
||||||
},
|
},
|
||||||
Count: p.count,
|
Count: p.count,
|
||||||
Protocol: p.protocol,
|
Protocol: p.protocol,
|
||||||
|
|||||||
@@ -53,7 +53,16 @@ func representMapSliceAsTable(mapSlice []interface{}, selectorPrefix string) (re
|
|||||||
h := item.(map[string]interface{})
|
h := item.(map[string]interface{})
|
||||||
key := h["name"].(string)
|
key := h["name"].(string)
|
||||||
value := h["value"]
|
value := h["value"]
|
||||||
switch reflect.TypeOf(value).Kind() {
|
|
||||||
|
var reflectKind reflect.Kind
|
||||||
|
reflectType := reflect.TypeOf(value)
|
||||||
|
if reflectType == nil {
|
||||||
|
reflectKind = reflect.Interface
|
||||||
|
} else {
|
||||||
|
reflectKind = reflect.TypeOf(value).Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch reflectKind {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
fallthrough
|
fallthrough
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ require (
|
|||||||
github.com/segmentio/kafka-go v0.4.27
|
github.com/segmentio/kafka-go v0.4.27
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/up9inc/mizu/tap/api v0.0.0
|
github.com/up9inc/mizu/tap/api v0.0.0
|
||||||
|
golang.org/x/text v0.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package kafka
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -891,8 +893,9 @@ func representMapAsTable(mapData map[string]interface{}, selectorPrefix string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
selector := fmt.Sprintf("%s[\"%s\"]", selectorPrefix, key)
|
selector := fmt.Sprintf("%s[\"%s\"]", selectorPrefix, key)
|
||||||
|
caser := cases.Title(language.Und, cases.NoLower)
|
||||||
table = append(table, api.TableData{
|
table = append(table, api.TableData{
|
||||||
Name: strings.Join(camelcase.Split(strings.Title(key)), " "),
|
Name: strings.Join(camelcase.Split(caser.String(key)), " "),
|
||||||
Value: value,
|
Value: value,
|
||||||
Selector: selector,
|
Selector: selector,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func UpdateTapTargets(newTapTargets []v1.Pod) {
|
|||||||
|
|
||||||
tapTargets = newTapTargets
|
tapTargets = newTapTargets
|
||||||
|
|
||||||
packetSourceManager.UpdatePods(tapTargets)
|
packetSourceManager.UpdatePods(tapTargets, !*nodefrag, mainPacketInputChan)
|
||||||
|
|
||||||
if tlsTapperInstance != nil {
|
if tlsTapperInstance != nil {
|
||||||
if err := tlstapper.UpdateTapTargets(tlsTapperInstance, &tapTargets, *procfs); err != nil {
|
if err := tlstapper.UpdateTapTargets(tlsTapperInstance, &tapTargets, *procfs); err != nil {
|
||||||
@@ -198,12 +198,8 @@ func initializePacketSources() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if packetSourceManager, err = source.NewPacketSourceManager(*procfs, *fname, *iface, *servicemesh, tapTargets, behaviour); err != nil {
|
packetSourceManager, err = source.NewPacketSourceManager(*procfs, *fname, *iface, *servicemesh, tapTargets, behaviour, !*nodefrag, mainPacketInputChan)
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
packetSourceManager.ReadPackets(!*nodefrag, mainPacketInputChan)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializePassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem) (*tcpStreamMap, *tcpAssembler) {
|
func initializePassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem) (*tcpStreamMap, *tcpAssembler) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type PacketSourceManager struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketSourceManager(procfs string, filename string, interfaceName string,
|
func NewPacketSourceManager(procfs string, filename string, interfaceName string,
|
||||||
mtls bool, pods []v1.Pod, behaviour TcpPacketSourceBehaviour) (*PacketSourceManager, error) {
|
mtls bool, pods []v1.Pod, behaviour TcpPacketSourceBehaviour, ipdefrag bool, packets chan<- TcpPacketInfo) (*PacketSourceManager, error) {
|
||||||
hostSource, err := newHostPacketSource(filename, interfaceName, behaviour)
|
hostSource, err := newHostPacketSource(filename, interfaceName, behaviour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -43,7 +43,7 @@ func NewPacketSourceManager(procfs string, filename string, interfaceName string
|
|||||||
behaviour: behaviour,
|
behaviour: behaviour,
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceManager.UpdatePods(pods)
|
go hostSource.readPackets(ipdefrag, packets)
|
||||||
return sourceManager, nil
|
return sourceManager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,16 +64,16 @@ func newHostPacketSource(filename string, interfaceName string,
|
|||||||
return source, nil
|
return source, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *PacketSourceManager) UpdatePods(pods []v1.Pod) {
|
func (m *PacketSourceManager) UpdatePods(pods []v1.Pod, ipdefrag bool, packets chan<- TcpPacketInfo) {
|
||||||
if m.config.mtls {
|
if m.config.mtls {
|
||||||
m.updateMtlsPods(m.config.procfs, pods, m.config.interfaceName, m.config.behaviour)
|
m.updateMtlsPods(m.config.procfs, pods, m.config.interfaceName, m.config.behaviour, ipdefrag, packets)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.setBPFFilter(pods)
|
m.setBPFFilter(pods)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *PacketSourceManager) updateMtlsPods(procfs string, pods []v1.Pod,
|
func (m *PacketSourceManager) updateMtlsPods(procfs string, pods []v1.Pod,
|
||||||
interfaceName string, behaviour TcpPacketSourceBehaviour) {
|
interfaceName string, behaviour TcpPacketSourceBehaviour, ipdefrag bool, packets chan<- TcpPacketInfo) {
|
||||||
|
|
||||||
relevantPids := m.getRelevantPids(procfs, pods)
|
relevantPids := m.getRelevantPids(procfs, pods)
|
||||||
logger.Log.Infof("Updating mtls pods (new: %v) (current: %v)", relevantPids, m.sources)
|
logger.Log.Infof("Updating mtls pods (new: %v) (current: %v)", relevantPids, m.sources)
|
||||||
@@ -90,6 +90,7 @@ func (m *PacketSourceManager) updateMtlsPods(procfs string, pods []v1.Pod,
|
|||||||
source, err := newNetnsPacketSource(procfs, pid, interfaceName, behaviour)
|
source, err := newNetnsPacketSource(procfs, pid, interfaceName, behaviour)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
go source.readPackets(ipdefrag, packets)
|
||||||
m.sources[pid] = source
|
m.sources[pid] = source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,12 +154,6 @@ func (m *PacketSourceManager) setBPFFilter(pods []v1.Pod) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *PacketSourceManager) ReadPackets(ipdefrag bool, packets chan<- TcpPacketInfo) {
|
|
||||||
for _, src := range m.sources {
|
|
||||||
go src.readPackets(ipdefrag, packets)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *PacketSourceManager) Close() {
|
func (m *PacketSourceManager) Close() {
|
||||||
for _, src := range m.sources {
|
for _, src := range m.sources {
|
||||||
src.close()
|
src.close()
|
||||||
|
|||||||
4
ui-common/package-lock.json
generated
4
ui-common/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@up9/mizu-common",
|
"name": "@up9/mizu-common",
|
||||||
"version": "1.0.135",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@up9/mizu-common",
|
"name": "@up9/mizu-common",
|
||||||
"version": "1.0.135",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "^6.4.3",
|
"@craco/craco": "^6.4.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@up9/mizu-common",
|
"name": "@up9/mizu-common",
|
||||||
"version": "1.0.145",
|
"version": "0.0.0",
|
||||||
"description": "Made with create-react-library",
|
"description": "Made with create-react-library",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
|
|||||||
try {
|
try {
|
||||||
const data = await getOasByService(selectedService ? selectedService : oasServices[0]);
|
const data = await getOasByService(selectedService ? selectedService : oasServices[0]);
|
||||||
setSelectedServiceSpec(data);
|
setSelectedServiceSpec(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error("Error occurred while fetching service OAS spec");
|
toast.error("Error occurred while fetching service OAS spec");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -61,7 +61,7 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onSelectedOASService(null);
|
onSelectedOASService(null);
|
||||||
},[oasServices])
|
}, [oasServices])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -80,28 +80,28 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
|
|||||||
<div className={style.boxContainer}>
|
<div className={style.boxContainer}>
|
||||||
<div className={style.selectHeader}>
|
<div className={style.selectHeader}>
|
||||||
<div><img src={openApiLogo} alt="openAPI" className={style.openApilogo} /></div>
|
<div><img src={openApiLogo} alt="openAPI" className={style.openApilogo} /></div>
|
||||||
<div className={style.title}>OpenAPI</div>
|
<div className={style.title}>Service Catalog</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ cursor: "pointer" }}>
|
<div style={{ cursor: "pointer" }}>
|
||||||
<img src={closeIcon} alt="close" onClick={handleCloseModal} />
|
<img src={closeIcon} alt="close" onClick={handleCloseModal} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.selectContainer} >
|
<div className={style.selectContainer} >
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Select
|
||||||
labelId="service-select-label"
|
labelId="service-select-label"
|
||||||
id="service-select"
|
id="service-select"
|
||||||
value={selectedServiceName}
|
value={selectedServiceName}
|
||||||
onChangeCb={onSelectedOASService}
|
onChangeCb={onSelectedOASService}
|
||||||
>
|
>
|
||||||
{oasServices.map((service) => (
|
{oasServices.map((service) => (
|
||||||
<MenuItem key={service} value={service}>
|
<MenuItem key={service} value={service}>
|
||||||
{service}
|
{service}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.borderLine}></div>
|
<div className={style.borderLine}></div>
|
||||||
<div className={style.redoc}>
|
<div className={style.redoc}>
|
||||||
{selectedServiceSpec && <RedocStandalone
|
{selectedServiceSpec && <RedocStandalone
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import entriesAtom from "../../recoil/entries";
|
|||||||
import queryAtom from "../../recoil/query";
|
import queryAtom from "../../recoil/query";
|
||||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
|
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
|
||||||
import TrafficViewerApi from "./TrafficViewerApi";
|
import TrafficViewerApi from "./TrafficViewerApi";
|
||||||
|
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||||
|
|
||||||
interface EntriesListProps {
|
interface EntriesListProps {
|
||||||
listEntryREF: any;
|
listEntryREF: any;
|
||||||
@@ -18,8 +19,6 @@ interface EntriesListProps {
|
|||||||
setIsSnappedToBottom: any;
|
setIsSnappedToBottom: any;
|
||||||
queriedCurrent: number;
|
queriedCurrent: number;
|
||||||
setQueriedCurrent: any;
|
setQueriedCurrent: any;
|
||||||
queriedTotal: number;
|
|
||||||
setQueriedTotal: any;
|
|
||||||
startTime: number;
|
startTime: number;
|
||||||
noMoreDataTop: boolean;
|
noMoreDataTop: boolean;
|
||||||
setNoMoreDataTop: (flag: boolean) => void;
|
setNoMoreDataTop: (flag: boolean) => void;
|
||||||
@@ -33,16 +32,18 @@ interface EntriesListProps {
|
|||||||
ws: any;
|
ws: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, queriedTotal, setQueriedTotal, startTime, noMoreDataTop, setNoMoreDataTop, leftOffTop, setLeftOffTop, openWebSocket, leftOffBottom, truncatedTimestamp, setTruncatedTimestamp, scrollableRef, ws}) => {
|
export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, startTime, noMoreDataTop, setNoMoreDataTop, leftOffTop, setLeftOffTop, openWebSocket, leftOffBottom, truncatedTimestamp, setTruncatedTimestamp, scrollableRef, ws}) => {
|
||||||
|
|
||||||
const [entries, setEntries] = useRecoilState(entriesAtom);
|
const [entries, setEntries] = useRecoilState(entriesAtom);
|
||||||
const query = useRecoilValue(queryAtom);
|
const query = useRecoilValue(queryAtom);
|
||||||
const isWsConnectionClosed = ws?.current?.readyState !== WebSocket.OPEN;
|
const isWsConnectionClosed = ws?.current?.readyState !== WebSocket.OPEN;
|
||||||
|
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
||||||
|
|
||||||
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||||
|
|
||||||
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
||||||
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
||||||
|
const [queriedTotal, setQueriedTotal] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const list = document.getElementById('list').firstElementChild;
|
const list = document.getElementById('list').firstElementChild;
|
||||||
@@ -103,6 +104,29 @@ export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBro
|
|||||||
|
|
||||||
const scrollbarVisible = scrollableRef.current?.childWrapperRef.current.clientHeight > scrollableRef.current?.wrapperRef.current.clientHeight;
|
const scrollbarVisible = scrollableRef.current?.childWrapperRef.current.clientHeight > scrollableRef.current?.wrapperRef.current.clientHeight;
|
||||||
|
|
||||||
|
if (ws.current) {
|
||||||
|
ws.current.addEventListener("message", (e) => {
|
||||||
|
if (!e?.data) return;
|
||||||
|
const message = JSON.parse(e.data);
|
||||||
|
switch (message.messageType) {
|
||||||
|
case "entry":
|
||||||
|
const entry = message.data;
|
||||||
|
if (!focusedEntryId) setFocusedEntryId(entry.id.toString());
|
||||||
|
const newEntries = [...entries, entry];
|
||||||
|
if (newEntries.length === 10001) {
|
||||||
|
setLeftOffTop(newEntries[0].entry.id);
|
||||||
|
newEntries.shift();
|
||||||
|
setNoMoreDataTop(false);
|
||||||
|
}
|
||||||
|
setEntries(newEntries);
|
||||||
|
break;
|
||||||
|
case "queryMetadata":
|
||||||
|
setQueriedTotal(message.data.total);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<div className={styles.list}>
|
<div className={styles.list}>
|
||||||
<div id="list" ref={listEntryREF} className={styles.list}>
|
<div id="list" ref={listEntryREF} className={styles.list}>
|
||||||
|
|||||||
@@ -66,8 +66,10 @@
|
|||||||
margin-top: -60px
|
margin-top: -60px
|
||||||
|
|
||||||
.capture img
|
.capture img
|
||||||
height: 20px
|
height: 14px
|
||||||
z-index: 1000
|
z-index: 1000
|
||||||
|
margin-top: 12px
|
||||||
|
margin-left: -2px
|
||||||
|
|
||||||
.endpointServiceContainer
|
.endpointServiceContainer
|
||||||
display: flex
|
display: flex
|
||||||
@@ -76,6 +78,7 @@
|
|||||||
padding-right: 10px
|
padding-right: 10px
|
||||||
padding-top: 4px
|
padding-top: 4px
|
||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
|
padding-left: 10px
|
||||||
|
|
||||||
.separatorRight
|
.separatorRight
|
||||||
display: flex
|
display: flex
|
||||||
|
|||||||
@@ -140,8 +140,6 @@ export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode}) =>
|
|||||||
|
|
||||||
|
|
||||||
const isStatusCodeEnabled = ((entry.proto.name === "http" && "status" in entry) || entry.status !== 0);
|
const isStatusCodeEnabled = ((entry.proto.name === "http" && "status" in entry) || entry.status !== 0);
|
||||||
let endpointServiceContainer = "10px";
|
|
||||||
if (!isStatusCodeEnabled) endpointServiceContainer = "20px";
|
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<div
|
<div
|
||||||
@@ -178,7 +176,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode}) =>
|
|||||||
{isStatusCodeEnabled && <div>
|
{isStatusCodeEnabled && <div>
|
||||||
<StatusCode statusCode={entry.status} statusQuery={entry.statusQuery}/>
|
<StatusCode statusCode={entry.status} statusQuery={entry.statusQuery}/>
|
||||||
</div>}
|
</div>}
|
||||||
<div className={styles.endpointServiceContainer} style={{paddingLeft: endpointServiceContainer}}>
|
<div className={styles.endpointServiceContainer}>
|
||||||
<Summary method={entry.method} methodQuery={entry.methodQuery} summary={entry.summary} summaryQuery={entry.summaryQuery}/>
|
<Summary method={entry.method} methodQuery={entry.methodQuery} summary={entry.summary} summaryQuery={entry.summaryQuery}/>
|
||||||
<div className={styles.resolvedName}>
|
<div className={styles.resolvedName}>
|
||||||
<Queryable
|
<Queryable
|
||||||
|
|||||||
@@ -58,19 +58,18 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
|
|
||||||
const classes = useLayoutStyles();
|
const classes = useLayoutStyles();
|
||||||
|
|
||||||
const [entries, setEntries] = useRecoilState(entriesAtom);
|
const setEntries = useSetRecoilState(entriesAtom);
|
||||||
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
||||||
const query = useRecoilValue(queryAtom);
|
const query = useRecoilValue(queryAtom);
|
||||||
const setTrafficViewerApiState = useSetRecoilState(trafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
const setTrafficViewerApiState = useSetRecoilState(trafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||||
const [tappingStatus, setTappingStatus] = useRecoilState(tappingStatusAtom);
|
const [tappingStatus, setTappingStatus] = useRecoilState(tappingStatusAtom);
|
||||||
const [noMoreDataTop, setNoMoreDataTop] = useState(false);
|
const [noMoreDataTop, setNoMoreDataTop] = useState(false);
|
||||||
const [isSnappedToBottom, setIsSnappedToBottom] = useState(true);
|
const [isSnappedToBottom, setIsSnappedToBottom] = useState(true);
|
||||||
const [forceRender, setForceRender] = useState(0);
|
const [wsReadyState, setWsReadyState] = useState(0);
|
||||||
|
|
||||||
const [queryBackgroundColor, setQueryBackgroundColor] = useState("#f5f5f5");
|
const [queryBackgroundColor, setQueryBackgroundColor] = useState("#f5f5f5");
|
||||||
|
|
||||||
const [queriedCurrent, setQueriedCurrent] = useState(0);
|
const [queriedCurrent, setQueriedCurrent] = useState(0);
|
||||||
const [queriedTotal, setQueriedTotal] = useState(0);
|
|
||||||
const [leftOffBottom, setLeftOffBottom] = useState(0);
|
const [leftOffBottom, setLeftOffBottom] = useState(0);
|
||||||
const [leftOffTop, setLeftOffTop] = useState(null);
|
const [leftOffTop, setLeftOffTop] = useState(null);
|
||||||
const [truncatedTimestamp, setTruncatedTimestamp] = useState(0);
|
const [truncatedTimestamp, setTruncatedTimestamp] = useState(0);
|
||||||
@@ -144,9 +143,12 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
ws.current = new WebSocket(webSocketUrl);
|
ws.current = new WebSocket(webSocketUrl);
|
||||||
sendQueryWhenWsOpen(query);
|
sendQueryWhenWsOpen(query);
|
||||||
|
|
||||||
|
ws.current.onopen = () => {
|
||||||
|
setWsReadyState(ws?.current?.readyState);
|
||||||
|
}
|
||||||
|
|
||||||
ws.current.onclose = () => {
|
ws.current.onclose = () => {
|
||||||
if (window.location.pathname === "/")
|
setWsReadyState(ws?.current?.readyState);
|
||||||
setForceRender(forceRender + 1);
|
|
||||||
}
|
}
|
||||||
ws.current.onerror = (event) => {
|
ws.current.onerror = (event) => {
|
||||||
console.error("WebSocket error:", event);
|
console.error("WebSocket error:", event);
|
||||||
@@ -173,21 +175,10 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ws.current) {
|
if (ws.current) {
|
||||||
ws.current.onmessage = (e) => {
|
ws.current.addEventListener("message", (e) => {
|
||||||
if (!e?.data) return;
|
if (!e?.data) return;
|
||||||
const message = JSON.parse(e.data);
|
const message = JSON.parse(e.data);
|
||||||
switch (message.messageType) {
|
switch (message.messageType) {
|
||||||
case "entry":
|
|
||||||
const entry = message.data;
|
|
||||||
if (!focusedEntryId) setFocusedEntryId(entry.id.toString());
|
|
||||||
const newEntries = [...entries, entry];
|
|
||||||
if (newEntries.length === 10001) {
|
|
||||||
setLeftOffTop(newEntries[0].entry.id);
|
|
||||||
newEntries.shift();
|
|
||||||
setNoMoreDataTop(false);
|
|
||||||
}
|
|
||||||
setEntries(newEntries);
|
|
||||||
break;
|
|
||||||
case "status":
|
case "status":
|
||||||
setTappingStatus(message.tappingStatus);
|
setTappingStatus(message.tappingStatus);
|
||||||
break;
|
break;
|
||||||
@@ -208,7 +199,6 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
break;
|
break;
|
||||||
case "queryMetadata":
|
case "queryMetadata":
|
||||||
setQueriedCurrent(queriedCurrent + message.data.current);
|
setQueriedCurrent(queriedCurrent + message.data.current);
|
||||||
setQueriedTotal(message.data.total);
|
|
||||||
setLeftOffBottom(message.data.leftOff);
|
setLeftOffBottom(message.data.leftOff);
|
||||||
setTruncatedTimestamp(message.data.truncatedTimestamp);
|
setTruncatedTimestamp(message.data.truncatedTimestamp);
|
||||||
if (leftOffTop === null) {
|
if (leftOffTop === null) {
|
||||||
@@ -218,12 +208,8 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
case "startTime":
|
case "startTime":
|
||||||
setStartTime(message.data);
|
setStartTime(message.data);
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
console.error(
|
|
||||||
`unsupported websocket message type, Got: ${message.messageType}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -272,7 +258,7 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getConnectionIndicator = () => {
|
const getConnectionIndicator = () => {
|
||||||
switch (ws?.current?.readyState) {
|
switch (wsReadyState) {
|
||||||
case WebSocket.OPEN:
|
case WebSocket.OPEN:
|
||||||
return <div className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.greenIndicatorContainer}`}>
|
return <div className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.greenIndicatorContainer}`}>
|
||||||
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.greenIndicator}`} />
|
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.greenIndicator}`} />
|
||||||
@@ -285,7 +271,7 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getConnectionTitle = () => {
|
const getConnectionTitle = () => {
|
||||||
switch (ws?.current?.readyState) {
|
switch (wsReadyState) {
|
||||||
case WebSocket.OPEN:
|
case WebSocket.OPEN:
|
||||||
return "streaming live traffic"
|
return "streaming live traffic"
|
||||||
default:
|
default:
|
||||||
@@ -305,9 +291,9 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
{tappingStatus && isShowStatusBar && <StatusBar isDemoBannerView={isDemoBannerView} />}
|
{tappingStatus && isShowStatusBar && <StatusBar isDemoBannerView={isDemoBannerView} />}
|
||||||
<div className={TrafficViewerStyles.TrafficPageHeader}>
|
<div className={TrafficViewerStyles.TrafficPageHeader}>
|
||||||
<div className={TrafficViewerStyles.TrafficPageStreamStatus}>
|
<div className={TrafficViewerStyles.TrafficPageStreamStatus}>
|
||||||
<img className={TrafficViewerStyles.playPauseIcon} style={{ visibility: ws?.current?.readyState === WebSocket.OPEN ? "visible" : "hidden" }} alt="pause"
|
<img className={TrafficViewerStyles.playPauseIcon} style={{ visibility: wsReadyState === WebSocket.OPEN ? "visible" : "hidden" }} alt="pause"
|
||||||
src={pauseIcon} onClick={toggleConnection} />
|
src={pauseIcon} onClick={toggleConnection} />
|
||||||
<img className={TrafficViewerStyles.playPauseIcon} style={{ position: "absolute", visibility: ws?.current?.readyState === WebSocket.OPEN ? "hidden" : "visible" }} alt="play"
|
<img className={TrafficViewerStyles.playPauseIcon} style={{ position: "absolute", visibility: wsReadyState === WebSocket.OPEN ? "hidden" : "visible" }} alt="play"
|
||||||
src={playIcon} onClick={toggleConnection} />
|
src={playIcon} onClick={toggleConnection} />
|
||||||
<div className={TrafficViewerStyles.connectionText}>
|
<div className={TrafficViewerStyles.connectionText}>
|
||||||
{getConnectionTitle()}
|
{getConnectionTitle()}
|
||||||
@@ -331,8 +317,6 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
setIsSnappedToBottom={setIsSnappedToBottom}
|
setIsSnappedToBottom={setIsSnappedToBottom}
|
||||||
queriedCurrent={queriedCurrent}
|
queriedCurrent={queriedCurrent}
|
||||||
setQueriedCurrent={setQueriedCurrent}
|
setQueriedCurrent={setQueriedCurrent}
|
||||||
queriedTotal={queriedTotal}
|
|
||||||
setQueriedTotal={setQueriedTotal}
|
|
||||||
startTime={startTime}
|
startTime={startTime}
|
||||||
noMoreDataTop={noMoreDataTop}
|
noMoreDataTop={noMoreDataTop}
|
||||||
setNoMoreDataTop={setNoMoreDataTop}
|
setNoMoreDataTop={setNoMoreDataTop}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import React, { CSSProperties } from "react";
|
import React, { CSSProperties } from "react";
|
||||||
import infoImg from 'assets/info.svg';
|
|
||||||
import styles from "./style/InformationIcon.module.sass"
|
import styles from "./style/InformationIcon.module.sass"
|
||||||
|
|
||||||
const DEFUALT_LINK = "https://getmizu.io/docs"
|
const DEFUALT_LINK = "https://getmizu.io/docs"
|
||||||
|
|
||||||
export interface InformationIconProps{
|
export interface InformationIconProps {
|
||||||
link?: string,
|
link?: string,
|
||||||
style? : CSSProperties
|
style?: CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InformationIcon: React.FC<InformationIconProps> = ({link,style}) => {
|
export const InformationIcon: React.FC<InformationIconProps> = ({ link, style }) => {
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<a href={DEFUALT_LINK ? DEFUALT_LINK : link} style={style} className={styles.flex} title="documentation" target="_blank">
|
<a href={DEFUALT_LINK ? DEFUALT_LINK : link} style={style} className={styles.linkStyle} title="documentation" target="_blank">
|
||||||
<img className="headerIcon" src={infoImg} alt="Info icon"/>
|
<span>Docs</span>
|
||||||
</a>
|
</a>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const Protocol: React.FC<ProtocolProps> = ({protocol, horizontal}) => {
|
|||||||
backgroundColor: protocol.backgroundColor,
|
backgroundColor: protocol.backgroundColor,
|
||||||
color: protocol.foregroundColor,
|
color: protocol.foregroundColor,
|
||||||
fontSize: protocol.fontSize,
|
fontSize: protocol.fontSize,
|
||||||
marginRight: "-20px",
|
marginRight: "-6px",
|
||||||
}}
|
}}
|
||||||
title={protocol.longName}
|
title={protocol.longName}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M19 21H6.14286C5.07143 21 4 20.32 4 18.96C4 17.6 5.07143 16.92 6.14286 16.92H19V4H6.14286C5.07143 4 4 5.02 4 6.04V18.96M16.8571 17.6V20.32V17.6Z" stroke="#627EF7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<rect x="8" y="7" width="7" height="2" fill="#627EF7"/>
|
|
||||||
<rect x="8" y="11" width="4" height="2" fill="#627EF7"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 454 B |
@@ -1,2 +1,8 @@
|
|||||||
.flex
|
.linkStyle
|
||||||
display: flex
|
display: flex
|
||||||
|
color: #18253d
|
||||||
|
text-decoration: none
|
||||||
|
font-family: "Ubuntu", sans-serif
|
||||||
|
font-style: normal
|
||||||
|
font-weight: 600
|
||||||
|
font-size: 14px
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
position: absolute
|
position: absolute
|
||||||
transform: translate(-50%, -3px)
|
transform: translate(-50%, -3px)
|
||||||
left: 50%
|
left: 50%
|
||||||
z-index: 9999
|
z-index: 100
|
||||||
min-width: 200px
|
min-width: 200px
|
||||||
background: $blue-color
|
background: $blue-color
|
||||||
color: rgba(255,255,255,0.75)
|
color: rgba(255,255,255,0.75)
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
overflow: hidden
|
overflow: hidden
|
||||||
max-width: clamp(150px,50%,600px)
|
max-width: clamp(150px,50%,600px)
|
||||||
|
|
||||||
&.banner
|
&.banner
|
||||||
top: 53px
|
top: 53px
|
||||||
|
|
||||||
.podsCount
|
.podsCount
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
table
|
table
|
||||||
width: 100%
|
width: 100%
|
||||||
margin-top: 20px
|
margin-top: 20px
|
||||||
|
|
||||||
tbody
|
tbody
|
||||||
max-height: 70vh
|
max-height: 70vh
|
||||||
overflow-y: auto
|
overflow-y: auto
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
text-align: center
|
text-align: center
|
||||||
line-height: 22px
|
line-height: 22px
|
||||||
font-weight: 600
|
font-weight: 600
|
||||||
margin-left: 8px
|
margin-left: 3px
|
||||||
|
|
||||||
.neutral
|
.neutral
|
||||||
background: gray
|
background: gray
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"@types/jest": "^26.0.22",
|
"@types/jest": "^26.0.22",
|
||||||
"@types/node": "^12.20.10",
|
"@types/node": "^12.20.10",
|
||||||
"@uiw/react-textarea-code-editor": "^1.4.12",
|
"@uiw/react-textarea-code-editor": "^1.4.12",
|
||||||
"@up9/mizu-common": "1.0.145",
|
"@up9/mizu-common": "file:up9-mizu-common-0.0.0.tgz",
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"core-js": "^3.20.2",
|
"core-js": "^3.20.2",
|
||||||
"craco-babel-loader": "^1.0.3",
|
"craco-babel-loader": "^1.0.3",
|
||||||
@@ -48,7 +48,8 @@
|
|||||||
"npm-link-shared": "^0.5.6",
|
"npm-link-shared": "^0.5.6",
|
||||||
"react-app-rewire-alias": "^1.1.7",
|
"react-app-rewire-alias": "^1.1.7",
|
||||||
"react-dev-utils": "^12.0.0",
|
"react-dev-utils": "^12.0.0",
|
||||||
"recoil": "^0.5.2"
|
"recoil": "^0.5.2",
|
||||||
|
"react-error-overlay": "6.0.9"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "craco start",
|
"start": "craco start",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, { useState } from "react";
|
||||||
import { Button } from "@material-ui/core";
|
import { Button } from "@material-ui/core";
|
||||||
import Api, { MizuWebsocketURL } from "../../../helpers/api";
|
import Api, { MizuWebsocketURL } from "../../../helpers/api";
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import {useSetRecoilState, useRecoilState} from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
import {useCommonStyles} from "../../../helpers/commonStyle"
|
import { useCommonStyles } from "../../../helpers/commonStyle"
|
||||||
import serviceMapModalOpenAtom from "../../../recoil/serviceMapModalOpen";
|
import serviceMapModalOpenAtom from "../../../recoil/serviceMapModalOpen";
|
||||||
import TrafficViewer from "@up9/mizu-common"
|
import TrafficViewer from "@up9/mizu-common"
|
||||||
import "@up9/mizu-common/dist/index.css"
|
import "@up9/mizu-common/dist/index.css"
|
||||||
@@ -17,13 +17,13 @@ interface TrafficPageProps {
|
|||||||
|
|
||||||
const api = Api.getInstance();
|
const api = Api.getInstance();
|
||||||
|
|
||||||
export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus}) => {
|
export const TrafficPage: React.FC<TrafficPageProps> = ({ setAnalyzeStatus }) => {
|
||||||
const commonClasses = useCommonStyles();
|
const commonClasses = useCommonStyles();
|
||||||
const setServiceMapModalOpen = useSetRecoilState(serviceMapModalOpenAtom);
|
const [serviceMapModalOpen, setServiceMapModalOpen] = useRecoilState(serviceMapModalOpenAtom);
|
||||||
const [openOasModal, setOpenOasModal] = useRecoilState(oasModalOpenAtom);
|
const [openOasModal, setOpenOasModal] = useRecoilState(oasModalOpenAtom);
|
||||||
const [openWebSocket, setOpenWebSocket] = useState(true);
|
const [openWebSocket, setOpenWebSocket] = useState(true);
|
||||||
|
|
||||||
const trafficViewerApi = {...api}
|
const trafficViewerApi = { ...api }
|
||||||
|
|
||||||
const handleOpenOasModal = () => {
|
const handleOpenOasModal = () => {
|
||||||
setOpenWebSocket(false)
|
setOpenWebSocket(false)
|
||||||
@@ -36,37 +36,31 @@ const trafficViewerApi = {...api}
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const actionButtons = (window["isOasEnabled"] || window["isServiceMapEnabled"]) &&
|
const actionButtons = (window["isOasEnabled"] || window["isServiceMapEnabled"]) &&
|
||||||
<div style={{ display: 'flex', height: "100%" }}>
|
<div style={{ display: 'flex', height: "100%" }}>
|
||||||
{window["isOasEnabled"] && <Button
|
{window["isOasEnabled"] && <Button
|
||||||
startIcon={<img className="custom" src={services} alt="services"></img>}
|
startIcon={<img className="custom" src={services} alt="services"></img>}
|
||||||
size="large"
|
size="large"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||||
style={{ marginRight: 25, textTransform: 'unset' }}
|
style={{ marginRight: 25, textTransform: 'unset' }}
|
||||||
onClick={handleOpenOasModal}>
|
onClick={handleOpenOasModal}>
|
||||||
OpenAPI Specs
|
Service Catalog
|
||||||
</Button>}
|
</Button>}
|
||||||
{window["isServiceMapEnabled"] && <Button
|
{window["isServiceMapEnabled"] && <Button
|
||||||
startIcon={<img src={serviceMap} className="custom" alt="service-map" style={{marginRight:"8%"}}></img>}
|
startIcon={<img src={serviceMap} className="custom" alt="service-map" style={{ marginRight: "8%" }}></img>}
|
||||||
size="large"
|
size="large"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||||
onClick={openServiceMapModalDebounce}
|
onClick={openServiceMapModalDebounce}
|
||||||
style={{textTransform: 'unset'}}>
|
style={{ textTransform: 'unset' }}>
|
||||||
Service Map
|
Service Map
|
||||||
</Button>}
|
</Button>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
//closeSocket()
|
|
||||||
}
|
|
||||||
},[])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TrafficViewer setAnalyzeStatus={setAnalyzeStatus} webSocketUrl={MizuWebsocketURL} isCloseWebSocket={!openWebSocket}
|
<TrafficViewer setAnalyzeStatus={setAnalyzeStatus} webSocketUrl={MizuWebsocketURL} isCloseWebSocket={!openWebSocket}
|
||||||
trafficViewerApiProp={trafficViewerApi} actionButtons={actionButtons} isShowStatusBar={!openOasModal} isDemoBannerView={false}/>
|
trafficViewerApiProp={trafficViewerApi} actionButtons={actionButtons} isShowStatusBar={!(openOasModal || serviceMapModalOpen)} isDemoBannerView={false} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onOpen
|
|||||||
const commonClasses = useCommonStyles();
|
const commonClasses = useCommonStyles();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
const [graphData, setGraphData] = useState<GraphData>({ nodes: [], edges: [] });
|
const [graphData, setGraphData] = useState<GraphData>({ nodes: [], edges: [] });
|
||||||
|
const [graphOptions, setGraphOptions] = useState(ServiceMapOptions);
|
||||||
|
|
||||||
const getServiceMapData = useCallback(async () => {
|
const getServiceMapData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -149,6 +150,14 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onOpen
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [isOpen])
|
}, [isOpen])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(graphData?.nodes?.length === 0) return;
|
||||||
|
let options = {...graphOptions};
|
||||||
|
options.physics.barnesHut.avoidOverlap = graphData?.nodes?.length > 10 ? 0 : 1;
|
||||||
|
setGraphOptions(options);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
},[graphData?.nodes?.length])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getServiceMapData();
|
getServiceMapData();
|
||||||
return () => setGraphData({ nodes: [], edges: [] })
|
return () => setGraphData({ nodes: [], edges: [] })
|
||||||
@@ -180,7 +189,7 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onOpen
|
|||||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<img src={refresh} className="custom" alt="refresh" style={{ marginRight: "8%" }}></img>}
|
startIcon={<img src={refresh} className="custom" alt="refresh" style={{ marginRight: "8%" }}/>}
|
||||||
size="medium"
|
size="medium"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||||
@@ -189,20 +198,20 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onOpen
|
|||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<img src={close} alt="close" onClick={() => onClose()} style={{ cursor: "pointer" }}></img>
|
<img src={close} alt="close" onClick={() => onClose()} style={{ cursor: "pointer" }}/>
|
||||||
</div>
|
</div>
|
||||||
<Graph
|
<Graph
|
||||||
graph={graphData}
|
graph={graphData}
|
||||||
options={ServiceMapOptions}
|
options={graphOptions}
|
||||||
/>
|
/>
|
||||||
<div className='legend-scale'>
|
<div className='legend-scale'>
|
||||||
<ul className='legend-labels'>
|
<ul className='legend-labels'>
|
||||||
<li><span style={{ background: '#205cf5' }}></span>HTTP</li>
|
<li><span style={{ background: '#205cf5' }}/>HTTP</li>
|
||||||
<li><span style={{ background: '#244c5a' }}></span>HTTP/2</li>
|
<li><span style={{ background: '#244c5a' }}/>HTTP/2</li>
|
||||||
<li><span style={{ background: '#244c5a' }}></span>gRPC</li>
|
<li><span style={{ background: '#244c5a' }}/>gRPC</li>
|
||||||
<li><span style={{ background: '#ff6600' }}></span>AMQP</li>
|
<li><span style={{ background: '#ff6600' }}/>AMQP</li>
|
||||||
<li><span style={{ background: '#000000' }}></span>KAFKA</li>
|
<li><span style={{ background: '#000000' }}/>KAFKA</li>
|
||||||
<li><span style={{ background: '#a41e11' }}></span>REDIS</li>
|
<li><span style={{ background: '#a41e11' }}/>REDIS</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
@@ -211,4 +220,4 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onOpen
|
|||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ const ServiceMapOptions = {
|
|||||||
springLength: 180,
|
springLength: 180,
|
||||||
springConstant: 0.04,
|
springConstant: 0.04,
|
||||||
damping: 0.2,
|
damping: 0.2,
|
||||||
avoidOverlap: 1
|
avoidOverlap: 0
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
@@ -171,4 +171,4 @@ const ServiceMapOptions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ServiceMapOptions
|
export default ServiceMapOptions
|
||||||
|
|||||||
Reference in New Issue
Block a user