mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-19 20:40:17 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9771d689ca | ||
|
|
5a044875d3 | ||
|
|
c49c344c2a | ||
|
|
e3e9681110 | ||
|
|
adf2274213 | ||
|
|
cb5344090a | ||
|
|
2110afc514 | ||
|
|
2c4a5d06ab | ||
|
|
14650aa3f4 |
@@ -41,16 +41,10 @@ RUN go build -ldflags="-s -w \
|
||||
-X 'mizuserver/pkg/version.BuildTimestamp=${BUILD_TIMESTAMP}' \
|
||||
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
|
||||
|
||||
# Download Basenine executable, verify the sha1sum and move it to a directory in $PATH
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64 ./basenine_linux_amd64
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
|
||||
RUN shasum -a 256 -c basenine_linux_amd64.sha256
|
||||
RUN chmod +x ./basenine_linux_amd64
|
||||
|
||||
COPY devops/build_extensions.sh ..
|
||||
RUN cd .. && /bin/bash build_extensions.sh
|
||||
|
||||
FROM alpine:3.14
|
||||
FROM alpine:3.15
|
||||
|
||||
RUN apk add bash libpcap-dev
|
||||
|
||||
@@ -58,7 +52,6 @@ WORKDIR /app
|
||||
|
||||
# Copy binary and config files from /build to root folder of scratch container.
|
||||
COPY --from=builder ["/app/agent-build/mizuagent", "."]
|
||||
COPY --from=builder ["/app/agent-build/basenine_linux_amd64", "/usr/local/bin/basenine"]
|
||||
COPY --from=builder ["/app/agent/build/extensions", "extensions"]
|
||||
COPY --from=site-build ["/app/ui-build/build", "site"]
|
||||
RUN mkdir /app/data/
|
||||
|
||||
@@ -81,11 +81,16 @@ func TestLogs(t *testing.T) {
|
||||
logsFileNames = append(logsFileNames, file.Name)
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
|
||||
t.Errorf("api server logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
|
||||
t.Errorf("basenine logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||
t.Errorf("cli logs not found")
|
||||
return
|
||||
@@ -174,11 +179,16 @@ func TestLogsPath(t *testing.T) {
|
||||
logsFileNames = append(logsFileNames, file.Name)
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
|
||||
t.Errorf("api server logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
|
||||
t.Errorf("basenine logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||
t.Errorf("cli logs not found")
|
||||
return
|
||||
|
||||
@@ -862,11 +862,16 @@ func TestTapDumpLogs(t *testing.T) {
|
||||
logsFileNames = append(logsFileNames, file.Name)
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
|
||||
t.Errorf("api server logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
|
||||
t.Errorf("basenine logs not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||
t.Errorf("cli logs not found")
|
||||
return
|
||||
|
||||
@@ -92,7 +92,7 @@ func getDefaultCommandArgs() []string {
|
||||
setFlag := "--set"
|
||||
telemetry := "telemetry=false"
|
||||
agentImage := "agent-image=gcr.io/up9-docker-hub/mizu/ci:0.0.0"
|
||||
imagePullPolicy := "image-pull-policy=Never"
|
||||
imagePullPolicy := "image-pull-policy=IfNotPresent"
|
||||
headless := "headless=true"
|
||||
|
||||
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy, setFlag, headless}
|
||||
|
||||
@@ -17,7 +17,7 @@ require (
|
||||
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231
|
||||
github.com/ory/kratos-client-go v0.8.2-alpha.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211215185650-10083bb9a1b3
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220107003657-7c0578359920
|
||||
github.com/up9inc/mizu/shared v0.0.0
|
||||
github.com/up9inc/mizu/tap v0.0.0
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
|
||||
@@ -472,8 +472,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211215185650-10083bb9a1b3 h1:FeDCVOBFVpZA5/O5hfPdGTn0rdR2jTEYo3iB2htELI4=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211215185650-10083bb9a1b3/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220107003657-7c0578359920 h1:QQpgRleNNpxxAG/rKmk4dwJh0jHyRaQz4QOVlPmqv1c=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220107003657-7c0578359920/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"mizuserver/pkg/utils"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -115,7 +114,7 @@ func main() {
|
||||
|
||||
go pipeTapChannelToSocket(socketConnection, filteredOutputItemsChannel)
|
||||
} else if *apiServerMode {
|
||||
startBasenineServer(shared.BasenineHost, shared.BaseninePort)
|
||||
configureBasenineServer(shared.BasenineHost, shared.BaseninePort)
|
||||
startTime = time.Now().UnixNano() / int64(time.Millisecond)
|
||||
api.StartResolving(*namespace)
|
||||
|
||||
@@ -149,16 +148,7 @@ func main() {
|
||||
logger.Log.Info("Exiting")
|
||||
}
|
||||
|
||||
func startBasenineServer(host string, port string) {
|
||||
cmd := exec.Command("basenine", "-addr", host, "-port", port, "-persistent")
|
||||
cmd.Dir = config.Config.AgentDatabasePath
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
logger.Log.Panicf("Failed starting Basenine: %v", err)
|
||||
}
|
||||
|
||||
func configureBasenineServer(host string, port string) {
|
||||
if !wait.New(
|
||||
wait.WithProto("tcp"),
|
||||
wait.WithWait(200*time.Millisecond),
|
||||
@@ -166,25 +156,16 @@ func startBasenineServer(host string, port string) {
|
||||
wait.WithDeadline(5*time.Second),
|
||||
wait.WithDebug(true),
|
||||
).Do([]string{fmt.Sprintf("%s:%s", host, port)}) {
|
||||
logger.Log.Panicf("Basenine is not available: %v", err)
|
||||
logger.Log.Panicf("Basenine is not available!")
|
||||
}
|
||||
|
||||
// Make a channel to gracefully exit Basenine.
|
||||
channel := make(chan os.Signal)
|
||||
signal.Notify(channel, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Handle the channel.
|
||||
go func() {
|
||||
<-channel
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}()
|
||||
|
||||
// Limit the database size to default 200MB
|
||||
err = basenine.Limit(host, port, config.Config.MaxDBSizeBytes)
|
||||
err := basenine.Limit(host, port, config.Config.MaxDBSizeBytes)
|
||||
if err != nil {
|
||||
logger.Log.Panicf("Error while limiting database size: %v", err)
|
||||
}
|
||||
|
||||
// Define the macros
|
||||
for _, extension := range extensions {
|
||||
macros := extension.Dissector.Macros()
|
||||
for macro, expanded := range macros {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var globalTapConfig = &models.TapConfig{}
|
||||
var globalTapConfig = &models.TapConfig{TappedNamespaces: make(map[string]bool)}
|
||||
var cancelTapperSyncer context.CancelFunc
|
||||
|
||||
func PostTapConfig(c *gin.Context) {
|
||||
@@ -45,7 +45,7 @@ func PostTapConfig(c *gin.Context) {
|
||||
|
||||
podRegex, _ := regexp.Compile(".*")
|
||||
|
||||
kubernetesProvider, err := kubernetes.NewProviderInCluster()
|
||||
kubernetesProvider, err := providers.GetKubernetesProvider()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
@@ -66,7 +66,7 @@ func PostTapConfig(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetTapConfig(c *gin.Context) {
|
||||
kubernetesProvider, err := kubernetes.NewProviderInCluster()
|
||||
kubernetesProvider, err := providers.GetKubernetesProvider()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
@@ -94,7 +94,7 @@ func GetTapConfig(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, tapConfig)
|
||||
}
|
||||
|
||||
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, istio bool) (*kubernetes.MizuTapperSyncer, error) {
|
||||
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, serviceMesh bool) (*kubernetes.MizuTapperSyncer, error) {
|
||||
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
|
||||
TargetNamespaces: targetNamespaces,
|
||||
PodFilterRegex: podFilterRegex,
|
||||
@@ -106,7 +106,7 @@ func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, t
|
||||
IgnoredUserAgents: ignoredUserAgents,
|
||||
MizuApiFilteringOptions: mizuApiFilteringOptions,
|
||||
MizuServiceAccountExists: true, //assume service account exists since install mode will not function without it anyway
|
||||
Istio: istio,
|
||||
ServiceMesh: serviceMesh,
|
||||
}, time.Now())
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -18,7 +18,7 @@ func Login(c *gin.Context) {
|
||||
func Logout(c *gin.Context) {
|
||||
token := c.GetHeader("x-session-token")
|
||||
if err := providers.Logout(token, c.Request.Context()); err != nil {
|
||||
c.AbortWithStatusJSON(401, gin.H{"error": "error occured while logging out, the session might still be valid"})
|
||||
c.AbortWithStatusJSON(500, gin.H{"error": "error occured while logging out, the session might still be valid"})
|
||||
} else {
|
||||
c.JSON(200, "")
|
||||
}
|
||||
|
||||
27
agent/pkg/providers/kubernetes_provider.go
Normal file
27
agent/pkg/providers/kubernetes_provider.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/shared/kubernetes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var lock = &sync.Mutex{}
|
||||
|
||||
var kubernetesProvider *kubernetes.Provider
|
||||
|
||||
func GetKubernetesProvider() (*kubernetes.Provider, error) {
|
||||
if kubernetesProvider == nil {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if kubernetesProvider == nil {
|
||||
var err error
|
||||
kubernetesProvider, err = kubernetes.NewProviderInCluster()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return kubernetesProvider, nil
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func AnyUserExists(ctx context.Context) (bool, error) {
|
||||
|
||||
func Logout(token string, ctx context.Context) error {
|
||||
logoutRequest := client.V0alpha2Api.SubmitSelfServiceLogoutFlowWithoutBrowser(ctx)
|
||||
logoutRequest.SubmitSelfServiceLogoutFlowWithoutBrowserBody(ory.SubmitSelfServiceLogoutFlowWithoutBrowserBody{
|
||||
logoutRequest = logoutRequest.SubmitSelfServiceLogoutFlowWithoutBrowserBody(ory.SubmitSelfServiceLogoutFlowWithoutBrowserBody{
|
||||
SessionToken: token,
|
||||
})
|
||||
if response, err := logoutRequest.Execute(); err != nil {
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/up9inc/mizu/shared/kubernetes"
|
||||
core "k8s.io/api/core/v1"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
@@ -58,7 +62,21 @@ func runMizuInstall() {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Infof(uiUtils.Magenta, "Created Mizu Agent components, run `mizu view` to connect to the mizu daemon instance")
|
||||
logger.Log.Infof("Waiting for Mizu server to start...")
|
||||
readyChan := make(chan string)
|
||||
readyErrorChan := make(chan error)
|
||||
go watchApiServerPodReady(ctx, kubernetesProvider, readyChan, readyErrorChan)
|
||||
|
||||
select {
|
||||
case readyMessage := <-readyChan:
|
||||
logger.Log.Infof(readyMessage)
|
||||
case err := <-readyErrorChan:
|
||||
defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("%v", errormessage.FormatError(err)))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Infof(uiUtils.Magenta, "Installation completed, run `mizu view` to connect to the mizu daemon instance")
|
||||
}
|
||||
|
||||
func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Resources) *shared.MizuAgentConfig {
|
||||
@@ -75,3 +93,58 @@ func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Reso
|
||||
|
||||
return &mizuAgentConfig
|
||||
}
|
||||
|
||||
func watchApiServerPodReady(ctx context.Context, kubernetesProvider *kubernetes.Provider, readyChan chan string, readyErrorChan chan error) {
|
||||
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", kubernetes.ApiServerPodName))
|
||||
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
|
||||
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.MizuResourcesNamespace}, podWatchHelper)
|
||||
|
||||
timeAfter := time.After(30 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case wEvent, ok := <-eventChan:
|
||||
if !ok {
|
||||
eventChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
switch wEvent.Type {
|
||||
case kubernetes.EventAdded:
|
||||
logger.Log.Debugf("Watching API Server pod ready loop, added")
|
||||
case kubernetes.EventDeleted:
|
||||
logger.Log.Debugf("Watching API Server pod ready loop, %s removed", kubernetes.ApiServerPodName)
|
||||
case kubernetes.EventModified:
|
||||
modifiedPod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
readyErrorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Watching API Server pod ready loop, modified: %v", modifiedPod.Status.Phase)
|
||||
|
||||
if modifiedPod.Status.Phase == core.PodRunning {
|
||||
readyChan <- fmt.Sprintf("%v pod is running", modifiedPod.Name)
|
||||
return
|
||||
}
|
||||
case kubernetes.EventBookmark:
|
||||
break
|
||||
case kubernetes.EventError:
|
||||
break
|
||||
}
|
||||
case err, ok := <-errorChan:
|
||||
if !ok {
|
||||
errorChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
readyErrorChan <- fmt.Errorf("[ERROR] Agent creation, watching %v namespace, error: %v", config.Config.MizuResourcesNamespace, err)
|
||||
return
|
||||
case <-timeAfter:
|
||||
readyErrorChan <- fmt.Errorf("mizu API server was not ready in time")
|
||||
return
|
||||
case <-ctx.Done():
|
||||
logger.Log.Debugf("Watching API Server pod ready loop, ctx done")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,5 +119,5 @@ func init() {
|
||||
tapCmd.Flags().StringP(configStructs.WorkspaceTapName, "w", defaultTapConfig.Workspace, "Uploads traffic to your UP9 workspace for further analysis (requires auth)")
|
||||
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules")
|
||||
tapCmd.Flags().String(configStructs.ContractFile, defaultTapConfig.ContractFile, "OAS/Swagger file to validate to monitor the contracts")
|
||||
tapCmd.Flags().Bool(configStructs.IstioName, defaultTapConfig.Istio, "Record decrypted traffic if the cluster configured with istio and mtls")
|
||||
tapCmd.Flags().Bool(configStructs.ServiceMeshName, defaultTapConfig.ServiceMesh, "Record decrypted traffic if the cluster is configured with a service mesh and with mtls")
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider
|
||||
IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents,
|
||||
MizuApiFilteringOptions: mizuApiFilteringOptions,
|
||||
MizuServiceAccountExists: state.mizuServiceAccountExists,
|
||||
Istio: config.Config.Tap.Istio,
|
||||
ServiceMesh: config.Config.Tap.ServiceMesh,
|
||||
}, startTime)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
WorkspaceTapName = "workspace"
|
||||
EnforcePolicyFile = "traffic-validation-file"
|
||||
ContractFile = "contract"
|
||||
IstioName = "istio"
|
||||
ServiceMeshName = "service-mesh"
|
||||
)
|
||||
|
||||
type TapConfig struct {
|
||||
@@ -44,7 +44,7 @@ type TapConfig struct {
|
||||
AskUploadConfirmation bool `yaml:"ask-upload-confirmation" default:"true"`
|
||||
ApiServerResources shared.Resources `yaml:"api-server-resources"`
|
||||
TapperResources shared.Resources `yaml:"tapper-resources"`
|
||||
Istio bool `yaml:"istio" default:"false"`
|
||||
ServiceMesh bool `yaml:"service-mesh" default:"false"`
|
||||
}
|
||||
|
||||
func (config *TapConfig) PodRegex() *regexp.Regexp {
|
||||
|
||||
@@ -38,18 +38,20 @@ func DumpLogs(ctx context.Context, provider *kubernetes.Provider, filePath strin
|
||||
defer zipWriter.Close()
|
||||
|
||||
for _, pod := range pods {
|
||||
logs, err := provider.GetPodLogs(ctx, pod.Namespace, pod.Name)
|
||||
if err != nil {
|
||||
logger.Log.Errorf("Failed to get logs, %v", err)
|
||||
continue
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully read log length %d for pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||
}
|
||||
for _, container := range pod.Spec.Containers {
|
||||
logs, err := provider.GetPodLogs(ctx, pod.Namespace, pod.Name, container.Name)
|
||||
if err != nil {
|
||||
logger.Log.Errorf("Failed to get logs, %v", err)
|
||||
continue
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully read log length %d for pod: %s.%s.%s", len(logs), pod.Namespace, pod.Name, container.Name)
|
||||
}
|
||||
|
||||
if err := AddStrToZip(zipWriter, logs, fmt.Sprintf("%s.%s.log", pod.Namespace, pod.Name)); err != nil {
|
||||
logger.Log.Errorf("Failed write logs, %v", err)
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully added log length %d from pod: %s.%s", len(logs), pod.Namespace, pod.Name)
|
||||
if err := AddStrToZip(zipWriter, logs, fmt.Sprintf("%s.%s.%s.log", pod.Namespace, pod.Name, container.Name)); err != nil {
|
||||
logger.Log.Errorf("Failed write logs, %v", err)
|
||||
} else {
|
||||
logger.Log.Debugf("Successfully added log length %d from pod: %s.%s.%s", len(logs), pod.Namespace, pod.Name, container.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,6 @@ COPY tap ../tap
|
||||
COPY agent .
|
||||
RUN go build -gcflags="all=-N -l" -o mizuagent .
|
||||
|
||||
# Download Basenine executable, verify the sha1sum and move it to a directory in $PATH
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64 ./basenine_linux_amd64
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
|
||||
RUN shasum -a 256 -c basenine_linux_amd64.sha256
|
||||
RUN chmod +x ./basenine_linux_amd64
|
||||
|
||||
COPY devops/build_extensions_debug.sh ..
|
||||
RUN cd .. && /bin/bash build_extensions_debug.sh
|
||||
|
||||
@@ -54,7 +48,6 @@ WORKDIR /app
|
||||
|
||||
# Copy binary and config files from /build to root folder of scratch container.
|
||||
COPY --from=builder ["/app/agent-build/mizuagent", "."]
|
||||
COPY --from=builder ["/app/agent-build/basenine_linux_amd64", "/usr/local/bin/basenine"]
|
||||
COPY --from=builder ["/app/agent/build/extensions", "extensions"]
|
||||
COPY --from=site-build ["/app/ui-build/build", "site"]
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Please make sure to use full option name (`tap.dry-run` as opposed to `dry-run`
|
||||
|
||||
* `dump-logs` - if set to `true`, saves log files for all Mizu components (tapper, api-server, CLI) in a zip file under `$HOME/.mizu`. Default value is `false`
|
||||
|
||||
* `image-pull-policy` - container image pull policy for Kubernetes, default value `Always`. Other accepted values are `Never` or `IfNotExist`. Please mind the implications when changing this.
|
||||
* `image-pull-policy` - container image pull policy for Kubernetes, default value `Always`. Other accepted values are `Never` or `IfNotPresent`. Please mind the implications when changing this.
|
||||
|
||||
* `kube-config-path` - path to alternative kubeconfig file to use for all interactions with Kubernetes cluster. By default - `$HOME/.kubeconfig`
|
||||
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||

|
||||
# Istio mutual tls (mtls) with Mizu
|
||||
# Service mesh mutual tls (mtls) with Mizu
|
||||
This document describe how Mizu tapper handles workloads configured with mtls, making the internal traffic between services in a cluster to be encrypted.
|
||||
|
||||
Besides Istio there are other service meshes that implement mtls. However, as of now Istio is the most used one, and this is why we are focusing on it.
|
||||
The list of service meshes supported by Mizu include:
|
||||
|
||||
In order to create an Istio setup for development, follow those steps:
|
||||
- Istio
|
||||
- Linkerd
|
||||
|
||||
In order to create a service mesh setup for development, follow those steps:
|
||||
1. Deploy a sample application to a Kubernetes cluster, the sample application needs to make internal service to service calls
|
||||
2. SSH to one of the nodes, and run `tcpdump`
|
||||
3. Make sure you see the internal service to service calls in a plain text
|
||||
4. Deploy Istio to the cluster - make sure it is attached to all pods of the sample application, and that it is configured with mtls (default)
|
||||
4. Deploy a service mesh (Istio, Linkerd) to the cluster - make sure it is attached to all pods of the sample application, and that it is configured with mtls (default)
|
||||
5. Run `tcpdump` again, make sure you don't see the internal service to service calls in a plain text
|
||||
|
||||
## The connection between Istio and Envoy
|
||||
In order to implement its service mesh capabilities, [Istio](https://istio.io) use an [Envoy](https://www.envoyproxy.io) sidecar in front of every pod in the cluster. The Envoy is responsible for the mtls communication, and that's why we are focusing on Envoy proxy.
|
||||
## Implementation
|
||||
|
||||
### Istio support
|
||||
|
||||
#### The connection between Istio and Envoy
|
||||
In order to implement its service mesh capabilities, [Istio](https://istio.io) uses an [Envoy](https://www.envoyproxy.io) sidecar in front of every pod in the cluster. The Envoy is responsible for the mtls communication, and that's why we are focusing on Envoy proxy.
|
||||
|
||||
In the future we might see more players in that field, then we'll have to either add support for each of them or go with a unified eBPF solution.
|
||||
|
||||
## Network namespaces
|
||||
#### Network namespaces
|
||||
A [linux network namespace](https://man7.org/linux/man-pages/man7/network_namespaces.7.html) is an isolation that limit the process view of the network. In the container world it used to isolate one container from another. In the Kubernetes world it used to isolate a pod from another. That means that two containers running on the same pod share the same network namespace. A container can reach a container in the same pod by accessing `localhost`.
|
||||
|
||||
An Envoy proxy configured with mtls receives the inbound traffic directed to the pod, decrypts it and sends it via `localhost` to the target container.
|
||||
|
||||
## Tapping mtls traffic
|
||||
#### Tapping mtls traffic
|
||||
In order for Mizu to be able to see the decrypted traffic it needs to listen on the same network namespace of the target pod. Multiple threads of the same process can have different network namespaces.
|
||||
|
||||
[gopacket](https://github.com/google/gopacket) uses [libpacp](https://github.com/the-tcpdump-group/libpcap) by default for capturing the traffic. Libpacap doesn't support network namespaces and we can't ask it to listen to traffic on a different namespace. However, we can change the network namespace of the calling thread and then start libpcap to see the traffic on a different namespace.
|
||||
|
||||
## Finding the network namespace of a running process
|
||||
#### Finding the network namespace of a running process
|
||||
The network namespace of a running process can be found in `/proc/PID/ns/net` link. Once we have this link, we can ask Linux to change the network namespace of a thread to this one.
|
||||
|
||||
This mean that Mizu needs to have access to the `/proc` (procfs) of the running node.
|
||||
|
||||
## Finding the network namespace of a running pod
|
||||
#### Finding the network namespace of a running pod
|
||||
In order for Mizu to be able to listen to mtls traffic, it needs to get the PIDs of the the running pods, filter them according to the user filters and then start listen to their internal network namespace traffic.
|
||||
|
||||
There is no official way in Kubernetes to get from pod to PID. The CRI implementation purposefully doesn't force a pod to be a processes on the host. It can be a Virtual Machine as well like [Kata containers](https://katacontainers.io)
|
||||
@@ -42,5 +49,5 @@ Once Mizu detects an Envoy process, it need to check whether this specific Envoy
|
||||
|
||||
Istio sends an `INSTANCE_IP` environment variable to every Envoy proxy process. By examining the Envoy process's environment variables we can see whether it's relevant or not. Examining a process environment variables is done by reading the `/proc/PID/envion` file.
|
||||
|
||||
## Edge cases
|
||||
#### Edge cases
|
||||
The method we use to find Envoy processes and correlate them to the cluster IPs may be inaccurate in certain situations. If, for example, a user runs an Envoy process manually, and set its `INSTANCE_IP` environment variable to one of the `CLUSTER_IPS` the tapper gets, then Mizu will capture traffic for it.
|
||||
@@ -14,6 +14,8 @@ const (
|
||||
GoGCEnvVar = "GOGC"
|
||||
DefaultApiServerPort = 8899
|
||||
LogLevelEnvVar = "LOG_LEVEL"
|
||||
BasenineHost = "localhost"
|
||||
BasenineHost = "127.0.0.1"
|
||||
BaseninePort = "9099"
|
||||
BasenineImageRepo = "ghcr.io/up9inc/basenine"
|
||||
BasenineImageTag = "v0.2.26"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ type TapperSyncerConfig struct {
|
||||
IgnoredUserAgents []string
|
||||
MizuApiFilteringOptions api.TrafficFilteringOptions
|
||||
MizuServiceAccountExists bool
|
||||
Istio bool
|
||||
ServiceMesh bool
|
||||
}
|
||||
|
||||
func CreateAndStartMizuTapperSyncer(ctx context.Context, kubernetesProvider *Provider, config TapperSyncerConfig, startTime time.Time) (*MizuTapperSyncer, error) {
|
||||
@@ -316,7 +316,7 @@ func (tapperSyncer *MizuTapperSyncer) updateMizuTappers() error {
|
||||
tapperSyncer.config.ImagePullPolicy,
|
||||
tapperSyncer.config.MizuApiFilteringOptions,
|
||||
tapperSyncer.config.LogLevel,
|
||||
tapperSyncer.config.Istio,
|
||||
tapperSyncer.config.ServiceMesh,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -278,6 +278,36 @@ func (provider *Provider) GetMizuApiServerPodObject(opts *ApiServerOptions, moun
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "basenine",
|
||||
Image: fmt.Sprintf("%s:%s", shared.BasenineImageRepo, shared.BasenineImageTag),
|
||||
ImagePullPolicy: opts.ImagePullPolicy,
|
||||
VolumeMounts: volumeMounts,
|
||||
ReadinessProbe: &core.Probe{
|
||||
FailureThreshold: 3,
|
||||
Handler: core.Handler{
|
||||
TCPSocket: &core.TCPSocketAction{
|
||||
Port: intstr.Parse(shared.BaseninePort),
|
||||
},
|
||||
},
|
||||
PeriodSeconds: 1,
|
||||
SuccessThreshold: 1,
|
||||
TimeoutSeconds: 1,
|
||||
},
|
||||
Resources: core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
"cpu": cpuLimit,
|
||||
"memory": memLimit,
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
"cpu": cpuRequests,
|
||||
"memory": memRequests,
|
||||
},
|
||||
},
|
||||
Command: []string{"/basenine"},
|
||||
Args: []string{"-addr", "0.0.0.0", "-port", shared.BaseninePort, "-persistent"},
|
||||
WorkingDir: shared.DataDirPath,
|
||||
},
|
||||
}
|
||||
|
||||
if createAuthContainer {
|
||||
@@ -690,7 +720,7 @@ func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodMap map[string][]core.Pod, serviceAccountName string, resources shared.Resources, imagePullPolicy core.PullPolicy, mizuApiFilteringOptions api.TrafficFilteringOptions, logLevel logging.Level, istio bool) error {
|
||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodMap map[string][]core.Pod, serviceAccountName string, resources shared.Resources, imagePullPolicy core.PullPolicy, mizuApiFilteringOptions api.TrafficFilteringOptions, logLevel logging.Level, serviceMesh bool) error {
|
||||
logger.Log.Debugf("Applying %d tapper daemon sets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodMap), namespace, daemonSetName, podImage, tapperPodName)
|
||||
|
||||
if len(nodeToTappedPodMap) == 0 {
|
||||
@@ -715,8 +745,8 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
||||
"--nodefrag",
|
||||
}
|
||||
|
||||
if istio {
|
||||
mizuCmd = append(mizuCmd, "--procfs", procfsMountPath, "--istio")
|
||||
if serviceMesh {
|
||||
mizuCmd = append(mizuCmd, "--procfs", procfsMountPath, "--servicemesh")
|
||||
}
|
||||
|
||||
agentContainer := applyconfcore.Container()
|
||||
@@ -726,7 +756,7 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
||||
|
||||
caps := applyconfcore.Capabilities().WithDrop("ALL").WithAdd("NET_RAW").WithAdd("NET_ADMIN")
|
||||
|
||||
if istio {
|
||||
if serviceMesh {
|
||||
caps = caps.WithAdd("SYS_ADMIN") // for reading /proc/PID/net/ns
|
||||
caps = caps.WithAdd("SYS_PTRACE") // for setting netns to other process
|
||||
caps = caps.WithAdd("DAC_OVERRIDE") // for reading /proc/PID/environ
|
||||
@@ -913,8 +943,8 @@ func (provider *Provider) ListAllNamespaces(ctx context.Context) ([]core.Namespa
|
||||
return namespaces.Items, err
|
||||
}
|
||||
|
||||
func (provider *Provider) GetPodLogs(ctx context.Context, namespace string, podName string) (string, error) {
|
||||
podLogOpts := core.PodLogOptions{}
|
||||
func (provider *Provider) GetPodLogs(ctx context.Context, namespace string, podName string, containerName string) (string, error) {
|
||||
podLogOpts := core.PodLogOptions{Container: containerName}
|
||||
req := provider.clientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
|
||||
podLogs, err := req.Stream(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -52,7 +52,7 @@ var tstype = flag.String("timestamp_type", "", "Type of timestamps to use")
|
||||
var promisc = flag.Bool("promisc", true, "Set promiscuous mode")
|
||||
var staleTimeoutSeconds = flag.Int("staletimout", 120, "Max time in seconds to keep connections which don't transmit data")
|
||||
var pids = flag.String("pids", "", "A comma separated list of PIDs to capture their network namespaces")
|
||||
var istio = flag.Bool("istio", false, "Record decrypted traffic if the cluster configured with istio and mtls")
|
||||
var servicemesh = flag.Bool("servicemesh", false, "Record decrypted traffic if the cluster is configured with a service mesh and with mtls")
|
||||
|
||||
var memprofile = flag.String("memprofile", "", "Write memory profile")
|
||||
|
||||
@@ -179,7 +179,7 @@ func initializePacketSources() error {
|
||||
}
|
||||
|
||||
var err error
|
||||
if packetSourceManager, err = source.NewPacketSourceManager(*procfs, *pids, *fname, *iface, *istio, tapTargets, behaviour); err != nil {
|
||||
if packetSourceManager, err = source.NewPacketSourceManager(*procfs, *pids, *fname, *iface, *servicemesh, tapTargets, behaviour); err != nil {
|
||||
return err
|
||||
} else {
|
||||
packetSourceManager.ReadPackets(!*nodefrag, mainPacketInputChan)
|
||||
|
||||
405
ui/package-lock.json
generated
405
ui/package-lock.json
generated
@@ -1161,11 +1161,51 @@
|
||||
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
|
||||
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
||||
},
|
||||
"@emotion/cache": {
|
||||
"version": "11.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
|
||||
"integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
|
||||
"requires": {
|
||||
"@emotion/memoize": "^0.7.4",
|
||||
"@emotion/sheet": "^1.1.0",
|
||||
"@emotion/utils": "^1.0.0",
|
||||
"@emotion/weak-memoize": "^0.2.5",
|
||||
"stylis": "4.0.13"
|
||||
}
|
||||
},
|
||||
"@emotion/hash": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
|
||||
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
|
||||
},
|
||||
"@emotion/is-prop-valid": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz",
|
||||
"integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==",
|
||||
"requires": {
|
||||
"@emotion/memoize": "^0.7.4"
|
||||
}
|
||||
},
|
||||
"@emotion/memoize": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
|
||||
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
|
||||
},
|
||||
"@emotion/sheet": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
|
||||
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
|
||||
},
|
||||
"@emotion/utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
|
||||
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
|
||||
},
|
||||
"@emotion/weak-memoize": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
|
||||
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz",
|
||||
@@ -1898,6 +1938,335 @@
|
||||
"react-is": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"@mui/base": {
|
||||
"version": "5.0.0-alpha.62",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.62.tgz",
|
||||
"integrity": "sha512-ItmdSZwHKQbLbAsS3sWguR7OHqYqh2cYWahoVmHb13Kc6bMdmVUTY4x57IlDSU712B0yuA0Q/gPTq7xADKnFow==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@emotion/is-prop-valid": "^1.1.1",
|
||||
"@mui/utils": "^5.2.3",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"clsx": "^1.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/icons-material": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.2.5.tgz",
|
||||
"integrity": "sha512-uQiUz+l0xy+2jExyKyU19MkMAR2F7bQFcsQ5hdqAtsB14Jw2zlmIAD55mV6f0NxKCut7Rx6cA3ZpfzlzAfoK8Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/material": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.2.6.tgz",
|
||||
"integrity": "sha512-yF2bRqyJMo6bYXT7TPA9IU/XLaXHi47Xvmj8duQa5ha3bCpFMXLfGoZcAUl6ZDjjGEz1nCFS+c1qx219xD/aeQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@mui/base": "5.0.0-alpha.62",
|
||||
"@mui/system": "^5.2.6",
|
||||
"@mui/types": "^7.1.0",
|
||||
"@mui/utils": "^5.2.3",
|
||||
"@types/react-transition-group": "^4.4.4",
|
||||
"clsx": "^1.1.1",
|
||||
"csstype": "^3.0.10",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-transition-group": "^4.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
|
||||
"integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
|
||||
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
||||
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/private-theming": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.2.3.tgz",
|
||||
"integrity": "sha512-Lc1Cmu8lSsYZiXADi9PBb17Ho82ZbseHQujUFAcp6bCJ5x/d+87JYCIpCBMagPu/isRlFCwbziuXPmz7WOzJPQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@mui/utils": "^5.2.3",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/styled-engine": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.2.6.tgz",
|
||||
"integrity": "sha512-bqAhli8eGS6v2qxivy2/4K0Ag8o//jsu1G2G6QcieFiT6y7oIF/nd/6Tvw6OSm3roOTifVQWNKwkt1yFWhGS+w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@emotion/cache": "^11.7.1",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/styles": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.2.3.tgz",
|
||||
"integrity": "sha512-Art4qjlEI9H2h34mLL8s+CE9nWZWZbuJLbNpievaIM6DGuayz3DYkJHcH5mXJYFPhTNoe9IQYbpyKofjE0YVag==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@emotion/hash": "^0.8.0",
|
||||
"@mui/private-theming": "^5.2.3",
|
||||
"@mui/types": "^7.1.0",
|
||||
"@mui/utils": "^5.2.3",
|
||||
"clsx": "^1.1.1",
|
||||
"csstype": "^3.0.10",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"jss": "^10.8.2",
|
||||
"jss-plugin-camel-case": "^10.8.2",
|
||||
"jss-plugin-default-unit": "^10.8.2",
|
||||
"jss-plugin-global": "^10.8.2",
|
||||
"jss-plugin-nested": "^10.8.2",
|
||||
"jss-plugin-props-sort": "^10.8.2",
|
||||
"jss-plugin-rule-value-function": "^10.8.2",
|
||||
"jss-plugin-vendor-prefixer": "^10.8.2",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
|
||||
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
|
||||
},
|
||||
"jss": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz",
|
||||
"integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"csstype": "^3.0.2",
|
||||
"is-in-browser": "^1.1.3",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"jss-plugin-camel-case": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz",
|
||||
"integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"hyphenate-style-name": "^1.0.3",
|
||||
"jss": "10.9.0"
|
||||
}
|
||||
},
|
||||
"jss-plugin-default-unit": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz",
|
||||
"integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.9.0"
|
||||
}
|
||||
},
|
||||
"jss-plugin-global": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz",
|
||||
"integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.9.0"
|
||||
}
|
||||
},
|
||||
"jss-plugin-nested": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz",
|
||||
"integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.9.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"jss-plugin-props-sort": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz",
|
||||
"integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.9.0"
|
||||
}
|
||||
},
|
||||
"jss-plugin-rule-value-function": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz",
|
||||
"integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.9.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"jss-plugin-vendor-prefixer": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz",
|
||||
"integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"css-vendor": "^2.0.8",
|
||||
"jss": "10.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/system": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.2.6.tgz",
|
||||
"integrity": "sha512-PZ7bmpWOLikWgqn2zWv9/Xa7lxnRBOmfjoMH7c/IVYJs78W3971brXJ3xV9MEWWQcoqiYQeXzUJaNf4rFbKCBA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@mui/private-theming": "^5.2.3",
|
||||
"@mui/styled-engine": "^5.2.6",
|
||||
"@mui/types": "^7.1.0",
|
||||
"@mui/utils": "^5.2.3",
|
||||
"clsx": "^1.1.1",
|
||||
"csstype": "^3.0.10",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
|
||||
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/types": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.0.tgz",
|
||||
"integrity": "sha512-Hh7ALdq/GjfIwLvqH3XftuY3bcKhupktTm+S6qRIDGOtPtRuq2L21VWzOK4p7kblirK0XgGVH5BLwa6u8z/6QQ=="
|
||||
},
|
||||
"@mui/utils": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.2.3.tgz",
|
||||
"integrity": "sha512-sQujlajIS0zQKcGIS6tZR0L1R+ib26B6UtuEn+cZqwKHsPo3feuS+SkdscYBdcCdMbrZs4gj8WIJHl2z6tbSzQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@types/prop-types": "^15.7.4",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
|
||||
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
|
||||
"integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ=="
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
|
||||
@@ -1957,6 +2326,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
|
||||
"integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ=="
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz",
|
||||
@@ -2479,6 +2853,14 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-is": {
|
||||
"version": "17.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz",
|
||||
"integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz",
|
||||
@@ -4355,6 +4737,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"clean-css": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
|
||||
@@ -10689,6 +11076,19 @@
|
||||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"material-ui-popup-state": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/material-ui-popup-state/-/material-ui-popup-state-2.0.0.tgz",
|
||||
"integrity": "sha512-1sbb9xpMs7OxG0SOGfGO0ZnwiLtqZSoXda0/AqqJkpouT4e0nADXutQtDJFMa9GUMNAODVDlYnNmfqM+MhFjsg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@mui/icons-material": "^5.0.0",
|
||||
"@mui/material": "^5.0.0",
|
||||
"@mui/styles": "^5.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -15733,6 +16133,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"stylis": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
|
||||
"integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"highlight.js": "^11.3.1",
|
||||
"json-beautify": "^1.1.1",
|
||||
"jsonpath": "^1.1.1",
|
||||
"material-ui-popup-state": "^2.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"numeral": "^2.0.6",
|
||||
|
||||
@@ -7,25 +7,6 @@ body
|
||||
color: $font-color
|
||||
width: 100%
|
||||
|
||||
.header
|
||||
height: 60px
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 5px 24px
|
||||
justify-content: space-between
|
||||
|
||||
.title
|
||||
letter-spacing: 2px
|
||||
|
||||
img
|
||||
height: 45px
|
||||
|
||||
.description
|
||||
margin-left: 10px
|
||||
font-size: 11px
|
||||
font-weight: bold
|
||||
color: $light-blue-color
|
||||
|
||||
.centeredForm
|
||||
max-width: 500px
|
||||
text-align: center
|
||||
|
||||
110
ui/src/App.tsx
110
ui/src/App.tsx
@@ -1,71 +1,16 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import './App.sass';
|
||||
import {TrafficPage} from "./components/TrafficPage";
|
||||
import {TLSWarning} from "./components/TLSWarning/TLSWarning";
|
||||
import {Header} from "./components/Header/Header";
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import Api from "./helpers/api";
|
||||
import LoadingOverlay from './components/LoadingOverlay';
|
||||
import LoginPage from './components/LoginPage';
|
||||
import InstallPage from './components/InstallPage';
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
// TODO: move to state management
|
||||
export enum Page {
|
||||
Traffic,
|
||||
Setup,
|
||||
Login
|
||||
}
|
||||
|
||||
// TODO: move to state management
|
||||
export interface MizuContextModel {
|
||||
page: Page;
|
||||
setPage: (page: Page) => void;
|
||||
}
|
||||
|
||||
// TODO: move to state management
|
||||
export const MizuContext = React.createContext<MizuContextModel>(null);
|
||||
import {TrafficPage} from "./components/TrafficPage";
|
||||
|
||||
const App = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [analyzeStatus, setAnalyzeStatus] = useState(null);
|
||||
const [showTLSWarning, setShowTLSWarning] = useState(false);
|
||||
const [userDismissedTLSWarning, setUserDismissedTLSWarning] = useState(false);
|
||||
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set<string>());
|
||||
|
||||
const [page, setPage] = useState(Page.Traffic); // TODO: move to state management
|
||||
|
||||
const determinePage = async () => { // TODO: move to state management
|
||||
if (window['isEnt'] !== true) {
|
||||
setPage(Page.Traffic);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const isInstallNeeded = await api.isInstallNeeded();
|
||||
if (isInstallNeeded) {
|
||||
setPage(Page.Setup);
|
||||
} else {
|
||||
const isAuthNeeded = await api.isAuthenticationNeeded();
|
||||
if(isAuthNeeded) {
|
||||
setPage(Page.Login);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error("Error occured while checking Mizu API status, see console for mode details");
|
||||
console.error(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
determinePage();
|
||||
}, []);
|
||||
|
||||
const onTLSDetected = (destAddress: string) => {
|
||||
addressesWithTLS.add(destAddress);
|
||||
setAddressesWithTLS(new Set(addressesWithTLS));
|
||||
@@ -75,49 +20,16 @@ const App = () => {
|
||||
}
|
||||
};
|
||||
|
||||
let pageComponent: any;
|
||||
|
||||
switch (page) { // TODO: move to state management / proper routing
|
||||
case Page.Traffic:
|
||||
pageComponent = <TrafficPage setAnalyzeStatus={setAnalyzeStatus} onTLSDetected={onTLSDetected}/>;
|
||||
break;
|
||||
case Page.Setup:
|
||||
pageComponent = <InstallPage/>;
|
||||
break;
|
||||
case Page.Login:
|
||||
pageComponent = <LoginPage/>;
|
||||
break;
|
||||
default:
|
||||
pageComponent = <div>Unknown Error</div>;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingOverlay/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mizuApp">
|
||||
<MizuContext.Provider value={{page, setPage}}>
|
||||
<Header analyzeStatus={analyzeStatus}/>
|
||||
{pageComponent}
|
||||
<TLSWarning showTLSWarning={showTLSWarning}
|
||||
setShowTLSWarning={setShowTLSWarning}
|
||||
addressesWithTLS={addressesWithTLS}
|
||||
setAddressesWithTLS={setAddressesWithTLS}
|
||||
userDismissedTLSWarning={userDismissedTLSWarning}
|
||||
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>
|
||||
</MizuContext.Provider>
|
||||
<ToastContainer
|
||||
position="bottom-right"
|
||||
autoClose={5000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
/>
|
||||
<Header analyzeStatus={analyzeStatus}/>
|
||||
<TrafficPage setAnalyzeStatus={setAnalyzeStatus} onTLSDetected={onTLSDetected}/>
|
||||
<TLSWarning showTLSWarning={showTLSWarning}
|
||||
setShowTLSWarning={setShowTLSWarning}
|
||||
addressesWithTLS={addressesWithTLS}
|
||||
setAddressesWithTLS={setAddressesWithTLS}
|
||||
userDismissedTLSWarning={userDismissedTLSWarning}
|
||||
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
107
ui/src/EntApp.tsx
Normal file
107
ui/src/EntApp.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import './App.sass';
|
||||
import {TrafficPage} from "./components/TrafficPage";
|
||||
import {TLSWarning} from "./components/TLSWarning/TLSWarning";
|
||||
import {EntHeader} from "./components/Header/EntHeader";
|
||||
import Api from "./helpers/api";
|
||||
import {toast} from "react-toastify";
|
||||
import InstallPage from "./components/InstallPage";
|
||||
import LoginPage from "./components/LoginPage";
|
||||
import LoadingOverlay from "./components/LoadingOverlay";
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
// TODO: move to state management
|
||||
export enum Page {
|
||||
Traffic,
|
||||
Setup,
|
||||
Login
|
||||
}
|
||||
|
||||
// TODO: move to state management
|
||||
export interface MizuContextModel {
|
||||
page: Page;
|
||||
setPage: (page: Page) => void;
|
||||
}
|
||||
|
||||
// TODO: move to state management
|
||||
export const MizuContext = React.createContext<MizuContextModel>(null);
|
||||
|
||||
const EntApp = () => {
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showTLSWarning, setShowTLSWarning] = useState(false);
|
||||
const [userDismissedTLSWarning, setUserDismissedTLSWarning] = useState(false);
|
||||
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set<string>());
|
||||
const [page, setPage] = useState(Page.Traffic); // TODO: move to state management
|
||||
const [isFirstLogin, setIsFirstLogin] = useState(false);
|
||||
|
||||
const determinePage = async () => { // TODO: move to state management
|
||||
try {
|
||||
const isInstallNeeded = await api.isInstallNeeded();
|
||||
if (isInstallNeeded) {
|
||||
setPage(Page.Setup);
|
||||
} else {
|
||||
const isAuthNeeded = await api.isAuthenticationNeeded();
|
||||
if(isAuthNeeded) {
|
||||
setPage(Page.Login);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error("Error occured while checking Mizu API status, see console for mode details");
|
||||
console.error(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
determinePage();
|
||||
}, []);
|
||||
|
||||
const onTLSDetected = (destAddress: string) => {
|
||||
addressesWithTLS.add(destAddress);
|
||||
setAddressesWithTLS(new Set(addressesWithTLS));
|
||||
|
||||
if (!userDismissedTLSWarning) {
|
||||
setShowTLSWarning(true);
|
||||
}
|
||||
};
|
||||
|
||||
let pageComponent: any;
|
||||
|
||||
switch (page) { // TODO: move to state management / proper routing
|
||||
case Page.Traffic:
|
||||
pageComponent = <TrafficPage onTLSDetected={onTLSDetected}/>;
|
||||
break;
|
||||
case Page.Setup:
|
||||
pageComponent = <InstallPage onFirstLogin={() => setIsFirstLogin(true)}/>;
|
||||
break;
|
||||
case Page.Login:
|
||||
pageComponent = <LoginPage/>;
|
||||
break;
|
||||
default:
|
||||
pageComponent = <div>Unknown Error</div>;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingOverlay/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mizuApp">
|
||||
<MizuContext.Provider value={{page, setPage}}>
|
||||
{page === Page.Traffic && <EntHeader isFirstLogin={isFirstLogin} setIsFirstLogin={setIsFirstLogin}/>}
|
||||
{pageComponent}
|
||||
{page === Page.Traffic && <TLSWarning showTLSWarning={showTLSWarning}
|
||||
setShowTLSWarning={setShowTLSWarning}
|
||||
addressesWithTLS={addressesWithTLS}
|
||||
setAddressesWithTLS={setAddressesWithTLS}
|
||||
userDismissedTLSWarning={userDismissedTLSWarning}
|
||||
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>}
|
||||
</MizuContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EntApp;
|
||||
@@ -36,7 +36,7 @@ interface QueryFormProps {
|
||||
openWebSocket: (query: string, resetEntries: boolean) => void;
|
||||
}
|
||||
|
||||
const style = {
|
||||
export const modalStyle = {
|
||||
position: 'absolute',
|
||||
top: '10%',
|
||||
left: '50%',
|
||||
@@ -45,6 +45,7 @@ const style = {
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: '5px',
|
||||
boxShadow: 24,
|
||||
outline: "none",
|
||||
p: 4,
|
||||
color: '#000',
|
||||
};
|
||||
@@ -153,11 +154,11 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
|
||||
style={{overflow: 'auto'}}
|
||||
>
|
||||
<Fade in={openModal}>
|
||||
<Box sx={style}>
|
||||
<Box sx={modalStyle}>
|
||||
<Typography id="modal-modal-title" variant="h5" component="h2" style={{textAlign: 'center'}}>
|
||||
Filtering Guide (Cheatsheet)
|
||||
</Typography>
|
||||
<Typography id="modal-modal-description">
|
||||
<Typography component={'span'} id="modal-modal-description">
|
||||
<p>Mizu has a rich filtering syntax that let's you query the results both flexibly and efficiently.</p>
|
||||
<p>Here are some examples that you can try;</p>
|
||||
</Typography>
|
||||
|
||||
78
ui/src/components/Header/EntHeader.tsx
Normal file
78
ui/src/components/Header/EntHeader.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React, {useContext, useEffect, useState} from "react";
|
||||
import logo from '../assets/MizuEntLogo.svg';
|
||||
import './Header.sass';
|
||||
import userImg from '../assets/user-circle.svg';
|
||||
import settingImg from '../assets/settings.svg';
|
||||
import {Menu, MenuItem} from "@material-ui/core";
|
||||
import PopupState, {bindMenu, bindTrigger} from "material-ui-popup-state";
|
||||
import logoutIcon from '../assets/logout.png';
|
||||
import {SettingsModal} from "../SettingsModal/SettingModal";
|
||||
import Api from "../../helpers/api";
|
||||
import {toast} from "react-toastify";
|
||||
import {MizuContext, Page} from "../../EntApp";
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
interface EntHeaderProps {
|
||||
isFirstLogin: boolean;
|
||||
setIsFirstLogin: (flag: boolean) => void
|
||||
}
|
||||
|
||||
export const EntHeader: React.FC<EntHeaderProps> = ({isFirstLogin, setIsFirstLogin}) => {
|
||||
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(isFirstLogin) {
|
||||
setIsSettingsModalOpen(true)
|
||||
}
|
||||
}, [isFirstLogin])
|
||||
|
||||
const onSettingsModalClose = () => {
|
||||
setIsSettingsModalOpen(false);
|
||||
setIsFirstLogin(false);
|
||||
}
|
||||
|
||||
return <div className="header">
|
||||
<div>
|
||||
<div className="title">
|
||||
<img style={{height: 55}} src={logo} alt="logo"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{display: "flex", alignItems: "center"}}>
|
||||
<img className="headerIcon" alt="settings" src={settingImg} style={{marginRight: 25}} onClick={() => setIsSettingsModalOpen(true)}/>
|
||||
<ProfileButton/>
|
||||
</div>
|
||||
<SettingsModal isOpen={isSettingsModalOpen} onClose={onSettingsModalClose} isFirstLogin={isFirstLogin}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const ProfileButton = () => {
|
||||
|
||||
const {setPage} = useContext(MizuContext);
|
||||
|
||||
const logout = async (popupState) => {
|
||||
try {
|
||||
await api.logout();
|
||||
setPage(Page.Login);
|
||||
} catch (e) {
|
||||
toast.error("Something went wrong, please check the console");
|
||||
console.error(e);
|
||||
}
|
||||
popupState.close();
|
||||
}
|
||||
|
||||
return (<PopupState variant="popover" popupId="demo-popup-menu">
|
||||
{(popupState) => (
|
||||
<React.Fragment>
|
||||
<img className="headerIcon" alt="user" src={userImg} {...bindTrigger(popupState)}/>
|
||||
<Menu {...bindMenu(popupState)}>
|
||||
<MenuItem style={{fontSize: 12, fontWeight: 600}} onClick={() => logout(popupState)}>
|
||||
<img alt="logout" src={logoutIcon} style={{marginRight: 5, height: 16}}/>
|
||||
Log Out
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</PopupState>);
|
||||
}
|
||||
23
ui/src/components/Header/Header.sass
Normal file
23
ui/src/components/Header/Header.sass
Normal file
@@ -0,0 +1,23 @@
|
||||
@import '../../variables.module'
|
||||
|
||||
.header
|
||||
height: 60px
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 5px 24px
|
||||
justify-content: space-between
|
||||
|
||||
.title
|
||||
letter-spacing: 2px
|
||||
|
||||
img
|
||||
height: 45px
|
||||
|
||||
.description
|
||||
margin-left: 10px
|
||||
font-size: 11px
|
||||
font-weight: bold
|
||||
color: $light-blue-color
|
||||
|
||||
.headerIcon
|
||||
cursor: pointer
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import {AuthPresentation} from "../AuthPresentation/AuthPresentation";
|
||||
import {AnalyzeButton} from "../AnalyzeButton/AnalyzeButton";
|
||||
import logo from '../assets/Mizu-logo.svg';
|
||||
import './Header.sass';
|
||||
|
||||
interface HeaderProps {
|
||||
analyzeStatus: any
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button, TextField } from "@material-ui/core";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { MizuContext, Page } from "../App";
|
||||
import { MizuContext, Page } from "../EntApp";
|
||||
import { adminUsername } from "../consts";
|
||||
import Api, { FormValidationErrorType } from "../helpers/api";
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -8,7 +8,11 @@ import LoadingOverlay from "./LoadingOverlay";
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
export const InstallPage: React.FC = () => {
|
||||
interface InstallPageProps {
|
||||
onFirstLogin: () => void;
|
||||
}
|
||||
|
||||
export const InstallPage: React.FC<InstallPageProps> = ({onFirstLogin}) => {
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
@@ -31,6 +35,7 @@ export const InstallPage: React.FC = () => {
|
||||
if (!await api.isAuthenticationNeeded()) {
|
||||
toast.success("admin user created successfully");
|
||||
setPage(Page.Traffic);
|
||||
onFirstLogin();
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.type === FormValidationErrorType) {
|
||||
|
||||
@@ -11,11 +11,17 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({delay}) => {
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
// @ts-ignore
|
||||
useEffect(() => {
|
||||
let isRelevant = true;
|
||||
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
if(isRelevant)
|
||||
setIsVisible(true);
|
||||
}, delay ?? SpinnerShowDelayMs);
|
||||
}, []);
|
||||
|
||||
return () => isRelevant = false;
|
||||
}, [delay]);
|
||||
|
||||
return <div className="loading-overlay-container" hidden={!isVisible}>
|
||||
<div className="loading-overlay-spinner"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button, TextField } from "@material-ui/core";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { MizuContext, Page } from "../App";
|
||||
import { MizuContext, Page } from "../EntApp";
|
||||
import Api from "../helpers/api";
|
||||
import LoadingOverlay from "./LoadingOverlay";
|
||||
|
||||
|
||||
150
ui/src/components/SettingsModal/SettingModal.tsx
Normal file
150
ui/src/components/SettingsModal/SettingModal.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import React, {useEffect, useMemo, useState} from "react";
|
||||
import {Modal, Backdrop, Fade, Box, Button} from "@material-ui/core";
|
||||
import {modalStyle} from "../Filters";
|
||||
import Checkbox from "../UI/Checkbox";
|
||||
import './SettingsModal.sass';
|
||||
import Api from "../../helpers/api";
|
||||
import spinner from "../assets/spinner.svg";
|
||||
import {useCommonStyles} from "../../helpers/commonStyle";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
interface SettingsModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
isFirstLogin: boolean
|
||||
}
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
export const SettingsModal: React.FC<SettingsModalProps> = ({isOpen, onClose, isFirstLogin}) => {
|
||||
|
||||
const classes = useCommonStyles();
|
||||
const [namespaces, setNamespaces] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const tapConfig = await api.getTapConfig()
|
||||
if(isFirstLogin) {
|
||||
const namespacesObj = {...tapConfig?.tappedNamespaces}
|
||||
Object.keys(tapConfig?.tappedNamespaces ?? {}).forEach(namespace => {
|
||||
namespacesObj[namespace] = true;
|
||||
})
|
||||
setNamespaces(namespacesObj);
|
||||
} else {
|
||||
setNamespaces(tapConfig?.tappedNamespaces);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
})()
|
||||
}, [isFirstLogin])
|
||||
|
||||
const setAllNamespacesTappedValue = (isTap: boolean) => {
|
||||
const newNamespaces = {};
|
||||
Object.keys(namespaces).forEach(key => {
|
||||
newNamespaces[key] = isTap;
|
||||
})
|
||||
setNamespaces(newNamespaces);
|
||||
}
|
||||
|
||||
const updateTappingSettings = async () => {
|
||||
try {
|
||||
await api.setTapConfig(namespaces);
|
||||
onClose();
|
||||
toast.success("Saved successfully");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error("Something went wrong, changes may not have been saved.")
|
||||
}
|
||||
}
|
||||
|
||||
const toggleTapNamespace = (namespace) => {
|
||||
const newNamespaces = {...namespaces};
|
||||
newNamespaces[namespace] = !namespaces[namespace]
|
||||
setNamespaces(newNamespaces);
|
||||
}
|
||||
|
||||
const toggleAll = () => {
|
||||
const isChecked = Object.values(namespaces).every(tap => tap === true);
|
||||
setAllNamespacesTappedValue(!isChecked);
|
||||
}
|
||||
|
||||
const filteredNamespaces = useMemo(() => {
|
||||
return Object.keys(namespaces).filter((namespace) => namespace.includes(searchValue));
|
||||
},[namespaces, searchValue])
|
||||
|
||||
const buildNamespacesTable = () => {
|
||||
return <table cellPadding={5} style={{borderCollapse: "collapse"}}>
|
||||
<thead>
|
||||
<tr style={{borderBottomWidth: "2px"}}>
|
||||
<th style={{width: 50}}><Checkbox checked={Object.values(namespaces).every(tap => tap === true)}
|
||||
onToggle={toggleAll}/></th>
|
||||
<th>Namespace</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredNamespaces?.map(namespace => {
|
||||
return <tr key={namespace}>
|
||||
<td style={{width: 50}}>
|
||||
<Checkbox checked={namespaces[namespace]} onToggle={() => toggleTapNamespace(namespace)}/>
|
||||
</td>
|
||||
<td>{namespace}</td>
|
||||
</tr>
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
const onModalClose = (reason) => {
|
||||
if(reason === 'backdropClick' && isFirstLogin) return;
|
||||
onClose();
|
||||
}
|
||||
|
||||
return <Modal
|
||||
open={isOpen}
|
||||
onClose={(event, reason) => onModalClose(reason)}
|
||||
closeAfterTransition
|
||||
BackdropComponent={Backdrop}
|
||||
BackdropProps={{
|
||||
timeout: 500,
|
||||
}}
|
||||
style={{overflow: 'auto'}}
|
||||
>
|
||||
<Fade in={isOpen}>
|
||||
<Box sx={modalStyle} style={{width: "40vw", maxWidth: 600, height: "70vh", padding: 0, display: "flex", justifyContent: "space-between", flexDirection: "column"}}>
|
||||
<div style={{padding: 32, paddingBottom: 0}}>
|
||||
<div className="settingsTitle">Tapping Settings</div>
|
||||
<div className="settingsSubtitle" style={{marginTop: 20}}>
|
||||
Please choose from below the namespaces for tapping, traffic for namespaces selected will be displayed
|
||||
</div>
|
||||
{isLoading ? <div style={{textAlign: "center", padding: 20}}>
|
||||
<img alt="spinner" src={spinner} style={{height: 35}}/>
|
||||
</div> : <>
|
||||
<div className="namespacesSettingsContainer">
|
||||
<div style={{margin: "10px 0"}}>
|
||||
<input className="searchNamespace" placeholder="Search" value={searchValue}
|
||||
onChange={(event) => setSearchValue(event.target.value)}/></div>
|
||||
<div className="namespacesTable">
|
||||
{buildNamespacesTable()}
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
<div className="settingsActionsContainer">
|
||||
{!isFirstLogin &&
|
||||
<Button style={{width: 100}} className={classes.outlinedButton} size={"small"}
|
||||
onClick={onClose} variant='outlined'>Cancel</Button>}
|
||||
<Button style={{width: 100, marginLeft: 20}} className={classes.button} size={"small"}
|
||||
onClick={updateTappingSettings}>OK</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</Fade>
|
||||
</Modal>
|
||||
}
|
||||
61
ui/src/components/SettingsModal/SettingsModal.sass
Normal file
61
ui/src/components/SettingsModal/SettingsModal.sass
Normal file
@@ -0,0 +1,61 @@
|
||||
@import "../../variables.module"
|
||||
|
||||
.settingsTitle
|
||||
font-size: 28px
|
||||
color: $blue-gray
|
||||
font-weight: 600
|
||||
|
||||
.settingsSubtitle
|
||||
font-size: 14px
|
||||
color: $light-gray
|
||||
margin-top: 20px
|
||||
line-height: 20px
|
||||
|
||||
.namespacesSettingsContainer
|
||||
border-radius: 4px
|
||||
padding: 20px 0
|
||||
|
||||
.searchNamespace
|
||||
outline: 0
|
||||
background: white
|
||||
border-radius: 4px
|
||||
width: 300px
|
||||
max-width: calc(40vw - 85px)
|
||||
padding: 8px 10px
|
||||
border: 1px #9D9D9D solid
|
||||
font-size: 14px
|
||||
color: #494677
|
||||
|
||||
.settingsActionsContainer
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
padding: 20px
|
||||
border-top: 2px $data-background-color solid
|
||||
|
||||
.namespacesTable
|
||||
table
|
||||
width: 100%
|
||||
margin-top: 20px
|
||||
|
||||
tbody
|
||||
max-height: calc(70vh - 355px)
|
||||
overflow-y: auto
|
||||
display: block
|
||||
|
||||
th
|
||||
color: $blue-gray
|
||||
text-align: left
|
||||
padding: 10px
|
||||
|
||||
tr
|
||||
border-bottom-width: 1px
|
||||
border-bottom-color: $data-background-color
|
||||
border-bottom-style: solid
|
||||
display: table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
|
||||
td
|
||||
color: $light-gray
|
||||
padding: 10px
|
||||
font-size: 16px
|
||||
@@ -29,7 +29,7 @@ export const TLSWarning: React.FC<TLSWarningProps> = ({showTLSWarning, setShowT
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
}, [setShowTLSWarning, setAddressesWithTLS]);
|
||||
|
||||
return (<Snackbar open={showTLSWarning && !userDismissedTLSWarning}>
|
||||
<MuiAlert classes={{filledWarning: 'customWarningStyle'}} elevation={6} variant="filled"
|
||||
|
||||
@@ -11,7 +11,6 @@ import variables from '../variables.module.scss';
|
||||
import {StatusBar} from "./UI/StatusBar";
|
||||
import Api, {MizuWebsocketURL} from "../helpers/api";
|
||||
import { toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
const useLayoutStyles = makeStyles(() => ({
|
||||
@@ -40,13 +39,13 @@ enum ConnectionStatus {
|
||||
}
|
||||
|
||||
interface TrafficPageProps {
|
||||
setAnalyzeStatus: (status: any) => void;
|
||||
onTLSDetected: (destAddress: string) => void;
|
||||
setAnalyzeStatus?: (status: any) => void;
|
||||
}
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLSDetected}) => {
|
||||
export const TrafficPage: React.FC<TrafficPageProps> = ({onTLSDetected, setAnalyzeStatus}) => {
|
||||
|
||||
const classes = useLayoutStyles();
|
||||
|
||||
@@ -152,7 +151,8 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
setTappingStatus(message.tappingStatus);
|
||||
break
|
||||
case "analyzeStatus":
|
||||
setAnalyzeStatus(message.analyzeStatus);
|
||||
if(setAnalyzeStatus)
|
||||
setAnalyzeStatus(message.analyzeStatus);
|
||||
break
|
||||
case "outboundLink":
|
||||
onTLSDetected(message.Data.DstIP);
|
||||
@@ -193,8 +193,10 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
try{
|
||||
const tapStatusResponse = await api.tapStatus();
|
||||
setTappingStatus(tapStatusResponse);
|
||||
const analyzeStatusResponse = await api.analyzeStatus();
|
||||
setAnalyzeStatus(analyzeStatusResponse);
|
||||
if(setAnalyzeStatus) {
|
||||
const analyzeStatusResponse = await api.analyzeStatus();
|
||||
setAnalyzeStatus(analyzeStatusResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ export interface Props {
|
||||
const Checkbox: React.FC<Props> = ({checked, onToggle}) => {
|
||||
|
||||
return (
|
||||
<div className="checkboxWrapper">
|
||||
<input type="checkbox" className="checkbox" checked={checked} onChange={(event) => onToggle(event.target.checked)}/>
|
||||
<div>
|
||||
<input style={{cursor: "pointer"}} type="checkbox" checked={checked} onChange={(event) => onToggle(event.target.checked)}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
11
ui/src/components/assets/MizuEntLogo.svg
Normal file
11
ui/src/components/assets/MizuEntLogo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
BIN
ui/src/components/assets/logout.png
Normal file
BIN
ui/src/components/assets/logout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
3
ui/src/components/assets/settings.svg
Normal file
3
ui/src/components/assets/settings.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.8199 22H10.1799C9.95182 22 9.73059 21.9221 9.55289 21.7792C9.37519 21.6362 9.25169 21.4368 9.20288 21.214L8.79588 19.33C8.25294 19.0921 7.73812 18.7946 7.26088 18.443L5.42388 19.028C5.20645 19.0973 4.97183 19.0902 4.759 19.0078C4.54617 18.9254 4.36794 18.7727 4.25388 18.575L2.42988 15.424C2.31703 15.2261 2.27467 14.9958 2.30973 14.7708C2.34479 14.5457 2.45519 14.3392 2.62288 14.185L4.04788 12.885C3.98308 12.2961 3.98308 11.7019 4.04788 11.113L2.62288 9.816C2.45496 9.66177 2.3444 9.45507 2.30933 9.22978C2.27427 9.00449 2.31677 8.77397 2.42988 8.576L4.24988 5.423C4.36394 5.22532 4.54218 5.07259 4.755 4.99019C4.96783 4.90778 5.20245 4.90066 5.41988 4.97L7.25688 5.555C7.50088 5.375 7.75488 5.207 8.01688 5.055C8.26988 4.913 8.52988 4.784 8.79588 4.669L9.20388 2.787C9.25246 2.5642 9.37572 2.36469 9.55323 2.22155C9.73074 2.07841 9.95185 2.00024 10.1799 2H13.8199C14.0479 2.00024 14.269 2.07841 14.4465 2.22155C14.6241 2.36469 14.7473 2.5642 14.7959 2.787L15.2079 4.67C15.7505 4.9079 16.265 5.20539 16.7419 5.557L18.5799 4.972C18.7972 4.90292 19.0316 4.91017 19.2442 4.99256C19.4568 5.07495 19.6349 5.22753 19.7489 5.425L21.5689 8.578C21.8009 8.985 21.7209 9.5 21.3759 9.817L19.9509 11.117C20.0157 11.7059 20.0157 12.3001 19.9509 12.889L21.3759 14.189C21.7209 14.507 21.8009 15.021 21.5689 15.428L19.7489 18.581C19.6348 18.7787 19.4566 18.9314 19.2438 19.0138C19.0309 19.0962 18.7963 19.1033 18.5789 19.034L16.7419 18.449C16.265 18.8003 15.7505 19.0975 15.2079 19.335L14.7959 21.214C14.7471 21.4366 14.6238 21.6359 14.4463 21.7788C14.2688 21.9218 14.0478 21.9998 13.8199 22ZM7.61988 16.229L8.43988 16.829C8.62488 16.965 8.81688 17.09 9.01688 17.204C9.20488 17.313 9.39688 17.411 9.59588 17.5L10.5289 17.909L10.9859 20H13.0159L13.4729 17.908L14.4059 17.499C14.8129 17.319 15.1999 17.096 15.5589 16.833L16.3789 16.233L18.4209 16.883L19.4359 15.125L17.8529 13.682L17.9649 12.67C18.0149 12.227 18.0149 11.78 17.9649 11.338L17.8529 10.326L19.4369 8.88L18.4209 7.121L16.3799 7.771L15.5589 7.171C15.1997 6.90669 14.8131 6.68173 14.4059 6.5L13.4729 6.091L13.0159 4H10.9859L10.5259 6.092L9.59588 6.5C9.18807 6.67861 8.80136 6.90198 8.44288 7.166L7.62188 7.766L5.58188 7.116L4.56488 8.88L6.14788 10.321L6.03588 11.334C5.98588 11.777 5.98588 12.224 6.03588 12.666L6.14788 13.678L4.56488 15.121L5.57988 16.879L7.61988 16.229ZM11.9959 16C10.935 16 9.9176 15.5786 9.16746 14.8284C8.41731 14.0783 7.99588 13.0609 7.99588 12C7.99588 10.9391 8.41731 9.92172 9.16746 9.17157C9.9176 8.42143 10.935 8 11.9959 8C13.0568 8 14.0742 8.42143 14.8243 9.17157C15.5745 9.92172 15.9959 10.9391 15.9959 12C15.9959 13.0609 15.5745 14.0783 14.8243 14.8284C14.0742 15.5786 13.0568 16 11.9959 16ZM11.9959 10C11.6042 10.0004 11.2213 10.1158 10.8947 10.3318C10.568 10.5479 10.3119 10.855 10.1583 11.2153C10.0046 11.5755 9.9601 11.9729 10.0303 12.3583C10.1004 12.7436 10.2822 13.0998 10.5529 13.3828C10.8237 13.6657 11.1716 13.863 11.5534 13.95C11.9353 14.037 12.3343 14.01 12.7009 13.8724C13.0676 13.7347 13.3858 13.4924 13.616 13.1756C13.8462 12.8587 13.9783 12.4812 13.9959 12.09V12.49V12C13.9959 11.4696 13.7852 10.9609 13.4101 10.5858C13.035 10.2107 12.5263 10 11.9959 10Z" fill="#627EF7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
4
ui/src/components/assets/user-circle.svg
Normal file
4
ui/src/components/assets/user-circle.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C9.35831 2.03369 6.8343 3.09807 4.96618 4.96618C3.09807 6.8343 2.03369 9.35831 2 12C2.01235 13.5389 2.37972 15.0542 3.07351 16.4279C3.7673 17.8016 4.76879 18.9967 6 19.92V20H6.1C7.79318 21.2975 9.86685 22.0006 12 22.0006C14.1332 22.0006 16.2068 21.2975 17.9 20H18V19.92C19.2312 18.9967 20.2327 17.8016 20.9265 16.4279C21.6203 15.0542 21.9877 13.5389 22 12C21.9663 9.35831 20.9019 6.8343 19.0338 4.96618C17.1657 3.09807 14.6417 2.03369 12 2ZM8.07 18.93C8.21599 18.2614 8.58615 17.6629 9.11908 17.2336C9.65202 16.8044 10.3157 16.5702 11 16.57H13C13.6843 16.5702 14.348 16.8044 14.8809 17.2336C15.4138 17.6629 15.784 18.2614 15.93 18.93C14.7389 19.6308 13.382 20.0004 12 20.0004C10.618 20.0004 9.26113 19.6308 8.07 18.93ZM17.61 17.64C17.2296 16.7309 16.5891 15.9546 15.7689 15.4084C14.9487 14.8622 13.9854 14.5705 13 14.57H11C10.0146 14.5705 9.05127 14.8622 8.23108 15.4084C7.41088 15.9546 6.77037 16.7309 6.39 17.64C5.64066 16.903 5.0439 16.0255 4.63381 15.0578C4.22373 14.0901 4.00835 13.051 4 12C4.02594 9.88633 4.87712 7.86653 6.37183 6.37183C7.86653 4.87712 9.88633 4.02594 12 4C14.1137 4.02594 16.1335 4.87712 17.6282 6.37183C19.1229 7.86653 19.9741 9.88633 20 12C19.9916 13.051 19.7763 14.0901 19.3662 15.0578C18.9561 16.0255 18.3593 16.903 17.61 17.64Z" fill="#627EF7"/>
|
||||
<path d="M12 6.00002C11.4714 5.98771 10.9457 6.08275 10.4548 6.27941C9.96397 6.47607 9.51809 6.77026 9.14417 7.14418C8.77026 7.51809 8.47607 7.96397 8.27941 8.45484C8.08275 8.94571 7.98771 9.47137 8.00002 10C7.98771 10.5287 8.08275 11.0543 8.27941 11.5452C8.47607 12.0361 8.77026 12.482 9.14417 12.8559C9.51809 13.2298 9.96397 13.524 10.4548 13.7206C10.9457 13.9173 11.4714 14.0123 12 14C12.5287 14.0123 13.0543 13.9173 13.5452 13.7206C14.0361 13.524 14.482 13.2298 14.8559 12.8559C15.2298 12.482 15.524 12.0361 15.7206 11.5452C15.9173 11.0543 16.0123 10.5287 16 10C16.0123 9.47137 15.9173 8.94571 15.7206 8.45484C15.524 7.96397 15.2298 7.51809 14.8559 7.14418C14.482 6.77026 14.0361 6.47607 13.5452 6.27941C13.0543 6.08275 12.5287 5.98771 12 6.00002ZM12 12C11.734 12.0129 11.4682 11.97 11.2197 11.874C10.9712 11.778 10.7456 11.6312 10.5572 11.4428C10.3689 11.2545 10.222 11.0288 10.126 10.7803C10.0301 10.5319 9.98716 10.2661 10 10C9.98716 9.73397 10.0301 9.46817 10.126 9.2197C10.222 8.97122 10.3689 8.74557 10.5572 8.55722C10.7456 8.36888 10.9712 8.22201 11.2197 8.12605C11.4682 8.03009 11.734 7.98716 12 8.00002C12.2661 7.98716 12.5319 8.03009 12.7803 8.12605C13.0288 8.22201 13.2545 8.36888 13.4428 8.55722C13.6312 8.74557 13.778 8.97122 13.874 9.2197C13.97 9.46817 14.0129 9.73397 14 10C14.0129 10.2661 13.97 10.5319 13.874 10.7803C13.778 11.0288 13.6312 11.2545 13.4428 11.4428C13.2545 11.6312 13.0288 11.778 12.7803 11.874C12.5319 11.97 12.2661 12.0129 12 12Z" fill="#627EF7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -84,6 +84,16 @@ export default class Api {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getTapConfig = async () => {
|
||||
const response = await this.client.get("/config/tapConfig");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
setTapConfig = async (config) => {
|
||||
const response = await this.client.post("/config/tapConfig", {tappedNamespaces: config});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
isInstallNeeded = async () => {
|
||||
const response = await this.client.get("/install/isNeeded");
|
||||
return response.data;
|
||||
@@ -94,7 +104,7 @@ export default class Api {
|
||||
await this.client.get("/status/tap");
|
||||
return false;
|
||||
} catch (e) {
|
||||
if (e.response.status == 401) {
|
||||
if (e.response.status === 401) {
|
||||
return true;
|
||||
}
|
||||
throw e;
|
||||
@@ -112,10 +122,11 @@ export default class Api {
|
||||
return response;
|
||||
} catch (e) {
|
||||
if (e.response.status === 400) {
|
||||
throw {
|
||||
const error = {
|
||||
'type': FormValidationErrorType,
|
||||
'messages': e.response.data
|
||||
};
|
||||
throw error;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
30
ui/src/helpers/commonStyle.ts
Normal file
30
ui/src/helpers/commonStyle.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {makeStyles} from "@material-ui/core";
|
||||
|
||||
// @ts-ignore
|
||||
export const useCommonStyles = makeStyles(() => ({
|
||||
button: {
|
||||
backgroundColor: "#205cf5",
|
||||
color: "white",
|
||||
fontWeight: "600 !important",
|
||||
fontSize: 12,
|
||||
padding: "8px 12px",
|
||||
borderRadius: "6px ! important",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "#205cf5",
|
||||
},
|
||||
},
|
||||
outlinedButton: {
|
||||
backgroundColor: "transparent",
|
||||
color: "#205cf5",
|
||||
fontWeight: "600 !important",
|
||||
fontSize: 12,
|
||||
padding: "8px 12px",
|
||||
border: "1px #205cf5 solid",
|
||||
borderRadius: "6px ! important",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
}
|
||||
}));
|
||||
@@ -146,4 +146,3 @@ button
|
||||
|
||||
::-webkit-scrollbar-corner
|
||||
display: none
|
||||
|
||||
|
||||
@@ -2,10 +2,26 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.sass';
|
||||
import App from './App';
|
||||
import EntApp from "./EntApp";
|
||||
import {ToastContainer} from "react-toastify";
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<>
|
||||
{window["isEnt"] ? <EntApp/> : <App/>}
|
||||
<ToastContainer
|
||||
position="bottom-right"
|
||||
autoClose={5000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
/>
|
||||
</>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ $light-blue-color: #BCCEFD;
|
||||
$success-color: #27AE60;
|
||||
$failure-color: #EB5757;
|
||||
$blue-gray: #494677;
|
||||
$light-gray: #8F9BB2;
|
||||
|
||||
:export {
|
||||
mainBackgroundColor: $main-background-color;
|
||||
@@ -19,4 +20,5 @@ $blue-gray: #494677;
|
||||
successColor: $success-color;
|
||||
failureColor: $failure-color;
|
||||
blueGray: $blue-gray;
|
||||
lightGray: $light-gray;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user