Merge branch 'develop' into fix/spawn-only-two-goroutines

This commit is contained in:
M. Mert Yıldıran 2022-05-03 05:28:17 -07:00 committed by GitHub
commit 05495eebde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 202 additions and 43919 deletions

View File

@ -76,6 +76,7 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect

View File

@ -405,6 +405,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=

View File

@ -18,6 +18,7 @@ require (
github.com/google/go-cmp v0.5.7 // indirect github.com/google/go-cmp v0.5.7 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/martian v2.1.0+incompatible // indirect github.com/google/martian v2.1.0+incompatible // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect

View File

@ -71,6 +71,8 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=

View File

@ -48,14 +48,14 @@ static __always_inline int get_count_bytes(struct pt_regs *ctx, struct ssl_info*
return countBytes; return countBytes;
} }
static __always_inline void add_address_to_chunk(struct pt_regs *ctx, struct tlsChunk* chunk, __u64 id, __u32 fd) { static __always_inline int add_address_to_chunk(struct pt_regs *ctx, struct tlsChunk* chunk, __u64 id, __u32 fd) {
__u32 pid = id >> 32; __u32 pid = id >> 32;
__u64 key = (__u64) pid << 32 | fd; __u64 key = (__u64) pid << 32 | fd;
struct fd_info *fdinfo = bpf_map_lookup_elem(&file_descriptor_to_ipv4, &key); struct fd_info *fdinfo = bpf_map_lookup_elem(&file_descriptor_to_ipv4, &key);
if (fdinfo == NULL) { if (fdinfo == NULL) {
return; return 0;
} }
int err = bpf_probe_read(chunk->address, sizeof(chunk->address), fdinfo->ipv4_addr); int err = bpf_probe_read(chunk->address, sizeof(chunk->address), fdinfo->ipv4_addr);
@ -63,7 +63,10 @@ static __always_inline void add_address_to_chunk(struct pt_regs *ctx, struct tls
if (err != 0) { if (err != 0) {
log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l); log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l);
return 0;
} }
return 1;
} }
static __always_inline void send_chunk_part(struct pt_regs *ctx, __u8* buffer, __u64 id, static __always_inline void send_chunk_part(struct pt_regs *ctx, __u8* buffer, __u64 id,
@ -143,7 +146,12 @@ static __always_inline void output_ssl_chunk(struct pt_regs *ctx, struct ssl_inf
chunk->len = countBytes; chunk->len = countBytes;
chunk->fd = info->fd; chunk->fd = info->fd;
add_address_to_chunk(ctx, chunk, id, chunk->fd); if (!add_address_to_chunk(ctx, chunk, id, chunk->fd)) {
// Without an address, we drop the chunk because there is not much to do with it in Go
//
return;
}
send_chunk(ctx, info->buffer, id, chunk); send_chunk(ctx, info->buffer, id, chunk);
} }

View File

@ -6,6 +6,7 @@ import (
"net" "net"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/up9inc/mizu/tap/api"
) )
const FLAGS_IS_CLIENT_BIT uint32 = (1 << 0) const FLAGS_IS_CLIENT_BIT uint32 = (1 << 0)
@ -73,3 +74,27 @@ func (c *tlsChunk) getRecordedData() []byte {
func (c *tlsChunk) isRequest() bool { func (c *tlsChunk) isRequest() bool {
return (c.isClient() && c.isWrite()) || (c.isServer() && c.isRead()) return (c.isClient() && c.isWrite()) || (c.isServer() && c.isRead())
} }
func (c *tlsChunk) getAddressPair() (addressPair, error) {
ip, port, err := c.getAddress()
if err != nil {
return addressPair{}, err
}
if c.isRequest() {
return addressPair{
srcIp: api.UnknownIp,
srcPort: api.UnknownPort,
dstIp: ip,
dstPort: port,
}, nil
} else {
return addressPair{
srcIp: ip,
srcPort: port,
dstIp: api.UnknownIp,
dstPort: api.UnknownPort,
}, nil
}
}

View File

@ -20,6 +20,13 @@ const (
INODE_FILED_INDEX = 9 INODE_FILED_INDEX = 9
) )
type addressPair struct {
srcIp net.IP
srcPort uint16
dstIp net.IP
dstPort uint16
}
// This file helps to extract Ip and Port out of a Socket file descriptor. // This file helps to extract Ip and Port out of a Socket file descriptor.
// //
// The equivalent bash commands are: // The equivalent bash commands are:
@ -31,18 +38,18 @@ const (
// The 1st and 2nd fields are the source and dest ip and ports in a Hex format // The 1st and 2nd fields are the source and dest ip and ports in a Hex format
// 0100007F:50 is 127.0.0.1:80 // 0100007F:50 is 127.0.0.1:80
func getAddressBySockfd(procfs string, pid uint32, fd uint32, src bool) (net.IP, uint16, error) { func getAddressBySockfd(procfs string, pid uint32, fd uint32) (addressPair, error) {
inode, err := getSocketInode(procfs, pid, fd) inode, err := getSocketInode(procfs, pid, fd)
if err != nil { if err != nil {
return nil, 0, err return addressPair{}, err
} }
tcppath := fmt.Sprintf("%s/%d/net/tcp", procfs, pid) tcppath := fmt.Sprintf("%s/%d/net/tcp", procfs, pid)
tcp, err := ioutil.ReadFile(tcppath) tcp, err := ioutil.ReadFile(tcppath)
if err != nil { if err != nil {
return nil, 0, errors.Wrap(err, 0) return addressPair{}, errors.Wrap(err, 0)
} }
for _, line := range strings.Split(string(tcp), "\n") { for _, line := range strings.Split(string(tcp), "\n") {
@ -53,15 +60,28 @@ func getAddressBySockfd(procfs string, pid uint32, fd uint32, src bool) (net.IP,
} }
if inode == parts[INODE_FILED_INDEX] { if inode == parts[INODE_FILED_INDEX] {
if src { srcIp, srcPort, srcErr := parseHexAddress(parts[SRC_ADDRESS_FILED_INDEX])
return parseHexAddress(parts[SRC_ADDRESS_FILED_INDEX])
} else { if srcErr != nil {
return parseHexAddress(parts[DST_ADDRESS_FILED_INDEX]) return addressPair{}, srcErr
} }
dstIp, dstPort, dstErr := parseHexAddress(parts[DST_ADDRESS_FILED_INDEX])
if dstErr != nil {
return addressPair{}, dstErr
}
return addressPair{
srcIp: srcIp,
srcPort: srcPort,
dstIp: dstIp,
dstPort: dstPort,
}, nil
} }
} }
return nil, 0, errors.Errorf("address not found [pid: %d] [sockfd: %d] [inode: %s]", pid, fd, inode) return addressPair{}, errors.Errorf("address not found [pid: %d] [sockfd: %d] [inode: %s]", pid, fd, inode)
} }
func getSocketInode(procfs string, pid uint32, fd uint32) (string, error) { func getSocketInode(procfs string, pid uint32, fd uint32) (string, error) {

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"net"
"sync" "sync"
"time" "time"
@ -16,10 +15,14 @@ import (
"github.com/cilium/ebpf/perf" "github.com/cilium/ebpf/perf"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/hashicorp/golang-lru/simplelru"
"github.com/up9inc/mizu/logger" "github.com/up9inc/mizu/logger"
"github.com/up9inc/mizu/tap/api" "github.com/up9inc/mizu/tap/api"
) )
const fdCachedItemAvgSize = 40
const fdCacheMaxItems = 500000 / fdCachedItemAvgSize
type tlsPoller struct { type tlsPoller struct {
tls *TlsTapper tls *TlsTapper
readers map[string]*tlsReader readers map[string]*tlsReader
@ -29,10 +32,12 @@ type tlsPoller struct {
extension *api.Extension extension *api.Extension
procfs string procfs string
pidToNamespace sync.Map pidToNamespace sync.Map
fdCache *simplelru.LRU // Actual typs is map[string]addressPair
evictedCounter int
} }
func newTlsPoller(tls *TlsTapper, extension *api.Extension, procfs string) *tlsPoller { func newTlsPoller(tls *TlsTapper, extension *api.Extension, procfs string) (*tlsPoller, error) {
return &tlsPoller{ poller := &tlsPoller{
tls: tls, tls: tls,
readers: make(map[string]*tlsReader), readers: make(map[string]*tlsReader),
closedReaders: make(chan string, 100), closedReaders: make(chan string, 100),
@ -41,6 +46,15 @@ func newTlsPoller(tls *TlsTapper, extension *api.Extension, procfs string) *tlsP
chunksReader: nil, chunksReader: nil,
procfs: procfs, procfs: procfs,
} }
fdCache, err := simplelru.NewLRU(fdCacheMaxItems, poller.fdCacheEvictCallback)
if err != nil {
return nil, errors.Wrap(err, 0)
}
poller.fdCache = fdCache
return poller, nil
} }
func (p *tlsPoller) init(bpfObjects *tlsTapperObjects, bufferSize int) error { func (p *tlsPoller) init(bpfObjects *tlsTapperObjects, bufferSize int) error {
@ -117,35 +131,38 @@ func (p *tlsPoller) pollChunksPerfBuffer(chunks chan<- *tlsChunk) {
func (p *tlsPoller) handleTlsChunk(chunk *tlsChunk, extension *api.Extension, emitter api.Emitter, func (p *tlsPoller) handleTlsChunk(chunk *tlsChunk, extension *api.Extension, emitter api.Emitter,
options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) error { options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) error {
ip, port, err := chunk.getAddress() address, err := p.getSockfdAddressPair(chunk)
if err != nil {
address, err = chunk.getAddressPair()
if err != nil { if err != nil {
return err return err
} }
}
key := buildTlsKey(chunk, ip, port) key := buildTlsKey(address)
reader, exists := p.readers[key] reader, exists := p.readers[key]
if !exists { if !exists {
reader = p.startNewTlsReader(chunk, ip, port, key, emitter, extension, options, streamsMap) reader = p.startNewTlsReader(chunk, &address, key, emitter, extension, options, streamsMap)
p.readers[key] = reader p.readers[key] = reader
} }
reader.captureTime = time.Now() reader.newChunk(chunk)
reader.chunks <- chunk
if os.Getenv("MIZU_VERBOSE_TLS_TAPPER") == "true" { if os.Getenv("MIZU_VERBOSE_TLS_TAPPER") == "true" {
p.logTls(chunk, ip, port) p.logTls(chunk, key, reader)
} }
return nil return nil
} }
func (p *tlsPoller) startNewTlsReader(chunk *tlsChunk, ip net.IP, port uint16, key string, func (p *tlsPoller) startNewTlsReader(chunk *tlsChunk, address *addressPair, key string,
emitter api.Emitter, extension *api.Extension, options *api.TrafficFilteringOptions, emitter api.Emitter, extension *api.Extension, options *api.TrafficFilteringOptions,
streamsMap api.TcpStreamMap) *tlsReader { streamsMap api.TcpStreamMap) *tlsReader {
tcpid := p.buildTcpId(chunk, ip, port) tcpid := p.buildTcpId(chunk, address)
doneHandler := func(r *tlsReader) { doneHandler := func(r *tlsReader) {
p.closeReader(key, r) p.closeReader(key, r)
@ -196,37 +213,53 @@ func (p *tlsPoller) closeReader(key string, r *tlsReader) {
p.closedReaders <- key p.closedReaders <- key
} }
func buildTlsKey(chunk *tlsChunk, ip net.IP, port uint16) string { func (p *tlsPoller) getSockfdAddressPair(chunk *tlsChunk) (addressPair, error) {
return fmt.Sprintf("%v:%v-%v:%v", chunk.isClient(), chunk.isRead(), ip, port) address, err := getAddressBySockfd(p.procfs, chunk.Pid, chunk.Fd)
} fdCacheKey := fmt.Sprintf("%d:%d", chunk.Pid, chunk.Fd)
func (p *tlsPoller) buildTcpId(chunk *tlsChunk, ip net.IP, port uint16) api.TcpID { if err == nil {
myIp, myPort, err := getAddressBySockfd(p.procfs, chunk.Pid, chunk.Fd, chunk.isClient()) if !chunk.isRequest() {
switchedAddress := addressPair{
if err != nil { srcIp: address.dstIp,
// May happen if the socket already closed, very likely to happen for localhost srcPort: address.dstPort,
// dstIp: address.srcIp,
myIp = api.UnknownIp dstPort: address.srcPort,
myPort = api.UnknownPort
}
if chunk.isRequest() {
return api.TcpID{
SrcIP: myIp.String(),
DstIP: ip.String(),
SrcPort: strconv.FormatUint(uint64(myPort), 10),
DstPort: strconv.FormatUint(uint64(port), 10),
Ident: "",
} }
p.fdCache.Add(fdCacheKey, switchedAddress)
return switchedAddress, nil
} else { } else {
return api.TcpID{ p.fdCache.Add(fdCacheKey, address)
SrcIP: ip.String(), return address, nil
DstIP: myIp.String(),
SrcPort: strconv.FormatUint(uint64(port), 10),
DstPort: strconv.FormatUint(uint64(myPort), 10),
Ident: "",
} }
} }
fromCacheIfc, ok := p.fdCache.Get(fdCacheKey)
if !ok {
return addressPair{}, err
}
fromCache, ok := fromCacheIfc.(addressPair)
if !ok {
return address, errors.Errorf("Unable to cast %T to addressPair", fromCacheIfc)
}
return fromCache, nil
}
func buildTlsKey(address addressPair) string {
return fmt.Sprintf("%s:%d>%s:%d", address.srcIp, address.srcPort, address.dstIp, address.dstPort)
}
func (p *tlsPoller) buildTcpId(chunk *tlsChunk, address *addressPair) api.TcpID {
return api.TcpID{
SrcIP: address.srcIp.String(),
DstIP: address.dstIp.String(),
SrcPort: strconv.FormatUint(uint64(address.srcPort), 10),
DstPort: strconv.FormatUint(uint64(address.dstPort), 10),
Ident: "",
}
} }
func (p *tlsPoller) addPid(pid uint32, namespace string) { func (p *tlsPoller) addPid(pid uint32, namespace string) {
@ -256,7 +289,7 @@ func (p *tlsPoller) clearPids() {
}) })
} }
func (p *tlsPoller) logTls(chunk *tlsChunk, ip net.IP, port uint16) { func (p *tlsPoller) logTls(chunk *tlsChunk, key string, reader *tlsReader) {
var flagsStr string var flagsStr string
if chunk.isClient() { if chunk.isClient() {
@ -271,13 +304,18 @@ func (p *tlsPoller) logTls(chunk *tlsChunk, ip net.IP, port uint16) {
flagsStr += "W" flagsStr += "W"
} }
srcIp, srcPort, _ := getAddressBySockfd(p.procfs, chunk.Pid, chunk.Fd, true)
dstIp, dstPort, _ := getAddressBySockfd(p.procfs, chunk.Pid, chunk.Fd, false)
str := strings.ReplaceAll(strings.ReplaceAll(string(chunk.Data[0:chunk.Recorded]), "\n", " "), "\r", "") str := strings.ReplaceAll(strings.ReplaceAll(string(chunk.Data[0:chunk.Recorded]), "\n", " "), "\r", "")
logger.Log.Infof("PID: %v (tid: %v) (fd: %v) (client: %v) (addr: %v:%v) (fdaddr %v:%v>%v:%v) (recorded %v out of %v starting at %v) - %v - %v", logger.Log.Infof("[%-44s] %s #%-4d (fd: %d) (recorded %d/%d:%d) - %s - %s",
chunk.Pid, chunk.Tgid, chunk.Fd, flagsStr, ip, port, key, flagsStr, reader.seenChunks, chunk.Fd,
srcIp, srcPort, dstIp, dstPort, chunk.Recorded, chunk.Len, chunk.Start,
chunk.Recorded, chunk.Len, chunk.Start, str, hex.EncodeToString(chunk.Data[0:chunk.Recorded])) str, hex.EncodeToString(chunk.Data[0:chunk.Recorded]))
}
func (p *tlsPoller) fdCacheEvictCallback(key interface{}, value interface{}) {
p.evictedCounter = p.evictedCounter + 1
if p.evictedCounter%1000000 == 0 {
logger.Log.Infof("Tls fdCache evicted %d items", p.evictedCounter)
}
} }

View File

@ -10,6 +10,7 @@ import (
type tlsReader struct { type tlsReader struct {
key string key string
chunks chan *tlsChunk chunks chan *tlsChunk
seenChunks int
data []byte data []byte
doneHandler func(r *tlsReader) doneHandler func(r *tlsReader)
progress *api.ReadProgress progress *api.ReadProgress
@ -23,6 +24,12 @@ type tlsReader struct {
reqResMatcher api.RequestResponseMatcher reqResMatcher api.RequestResponseMatcher
} }
func (r *tlsReader) newChunk(chunk *tlsChunk) {
r.captureTime = time.Now()
r.seenChunks = r.seenChunks + 1
r.chunks <- chunk
}
func (r *tlsReader) Read(p []byte) (int, error) { func (r *tlsReader) Read(p []byte) (int, error) {
var chunk *tlsChunk var chunk *tlsChunk

View File

@ -46,7 +46,13 @@ func (t *TlsTapper) Init(chunksBufferSize int, logBufferSize int, procfs string,
return err return err
} }
t.poller = newTlsPoller(t, extension, procfs) var err error
t.poller, err = newTlsPoller(t, extension, procfs)
if err != nil {
return err
}
return t.poller.init(&t.bpfObjects, chunksBufferSize) return t.poller.init(&t.bpfObjects, chunksBufferSize)
} }

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +0,0 @@
This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
It is linked to the liraz-test package in the parent directory for development purposes.
You can run `npm install` and then `npm start` to test your package.

View File

@ -1,13 +0,0 @@
module.exports = {
webpack: {
configure: (webpackConfig) => {
const instanceOfMiniCssExtractPlugin = webpackConfig.plugins.find(
(plugin) => plugin.options && plugin.options.ignoreOrder != null,
);
if(instanceOfMiniCssExtractPlugin)
instanceOfMiniCssExtractPlugin.options.ignoreOrder = true;
return webpackConfig;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
{
"name": "@up9/mizu-common-example",
"homepage": ".",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "craco start",
"comment-start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
"build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
"test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
"eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
},
"dependencies": {
"@testing-library/jest-dom": "file:../node_modules/@testing-library/jest-dom",
"@testing-library/react": "file:../node_modules/@testing-library/react",
"@testing-library/user-event": "file:../node_modules/@testing-library/user-event",
"@types/jest": "file:../node_modules/@types/jest",
"@types/node": "file:../node_modules/@types/node",
"@types/react": "file:../node_modules/@types/react",
"@types/react-dom": "file:../node_modules/@types/react-dom",
"@up9/mizu-common": "file:..",
"react": "file:../node_modules/react",
"react-dom": "file:../node_modules/react-dom"
},
"devDependencies": {
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@craco/craco": "^6.4.3",
"eslint": "^7.11.0",
"node-sass": "^6.0.0",
"recoil": "^0.5.2",
"react-scripts": "^4.0.3"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>@up9/mizu-common</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -1,15 +0,0 @@
{
"short_name": "@up9/mizu-common",
"name": "@up9/mizu-common",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -1,9 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
it('renders without crashing', () => {
const div = document.createElement('div')
ReactDOM.render(<App />, div)
ReactDOM.unmountComponentAtNode(div)
})

View File

@ -1,24 +0,0 @@
import TrafficViewer,{useWS, DEFAULT_QUERY, OasModal} from '@up9/mizu-common';
import "@up9/mizu-common/dist/index.css"
import {useEffect} from 'react';
import Api, {getWebsocketUrl} from "./api";
const api = Api.getInstance()
const App = () => {
const {message,error,isOpen, openSocket, closeSocket, sendQueryWhenWsOpen} = useWS(getWebsocketUrl())
const trafficViewerApi = {...api, webSocket:{open : openSocket, close: closeSocket, sendQueryWhenWsOpen: sendQueryWhenWsOpen}}
sendQueryWhenWsOpen(DEFAULT_QUERY);
useEffect(() => {
return () =>{
closeSocket()
}
},[])
return <>
</>
}
export default App

View File

@ -1,119 +0,0 @@
import * as axios from "axios";
export const MizuWebsocketURL = process.env.REACT_APP_OVERRIDE_WS_URL ? process.env.REACT_APP_OVERRIDE_WS_URL :
window.location.protocol === 'https:' ? `wss://${window.location.host}/ws` : `ws://${window.location.host}/ws`;
export const FormValidationErrorType = "formError";
const CancelToken = axios.CancelToken;
const apiURL = process.env.REACT_APP_OVERRIDE_API_URL ? process.env.REACT_APP_OVERRIDE_API_URL : `${window.location.origin}/`;
let token = null
let client = null
let source = null
export default class Api {
static instance;
static getInstance() {
if (!Api.instance) {
Api.instance = new Api();
}
return Api.instance;
}
constructor() {
token = localStorage.getItem("token");
client = this.getAxiosClient();
source = null;
}
tapStatus = async () => {
const response = await client.get("/status/tap");
return response.data;
}
analyzeStatus = async () => {
const response = await client.get("/status/analyze");
return response.data;
}
getEntry = async (id, query) => {
const response = await client.get(`/entries/${id}?query=${query}`);
return response.data;
}
fetchEntries = async (leftOff, direction, query, limit, timeoutMs) => {
const response = await client.get(`/entries/?leftOff=${leftOff}&direction=${direction}&query=${query}&limit=${limit}&timeoutMs=${timeoutMs}`).catch(function (thrown) {
console.error(thrown.message);
return {};
});
return response.data;
}
getOasServices = async () => {
const response = await client.get("/oas");
return response.data;
}
getOasByService = async (selectedService) => {
const response = await client.get(`/oas/${selectedService}`);
return response.data;
}
validateQuery = async (query) => {
if (source) {
source.cancel();
}
source = CancelToken.source();
const form = new FormData();
form.append('query', query)
const response = await client.post(`/query/validate`, form, {
cancelToken: source.token
}).catch(function (thrown) {
if (!axios.isCancel(thrown)) {
console.error('Validate error', thrown.message);
}
});
if (!response) {
return null;
}
return response.data;
}
persistToken = (tk) => {
token = tk;
client = this.getAxiosClient();
localStorage.setItem('token', token);
}
getAxiosClient = () => {
const headers = {
Accept: "application/json"
}
if (token) {
headers['x-session-token'] = `${token}`; // we use `x-session-token` instead of `Authorization` because the latter is reserved by kubectl proxy, making mizu view not work
}
return axios.create({
baseURL: apiURL,
timeout: 31000,
headers
});
}
}
export function getWebsocketUrl(){
let websocketUrl = MizuWebsocketURL;
if (token) {
websocketUrl += `/${token}`;
}
return websocketUrl;
}

View File

@ -1,14 +0,0 @@
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1,7 +0,0 @@
import './index.css'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@ -1,41 +0,0 @@
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": [
"dom",
"esnext"
],
"moduleResolution": "node",
"jsx": "react-jsx",
"sourceMap": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": false,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"allowSyntheticDefaultImports": true,
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
],
"exclude": [
"node_modules",
"build",
"exmaple"
]
}

View File

@ -1,5 +1,12 @@
@import '../../variables.module.scss' @import '../../variables.module.scss'
.closeIcon
cursor: pointer
user-select: none
margin-top: -12px
margin-right: -12px
float: right
.boxContainer .boxContainer
display: flex display: flex
justifyContent: space-between justifyContent: space-between
@ -37,4 +44,3 @@
.root .root
width: 100% width: 100%

View File

@ -1,5 +1,5 @@
import { Box, Fade, FormControl, MenuItem, Modal, Backdrop } from "@material-ui/core"; import { Box, Fade, FormControl, Modal, Backdrop } from "@material-ui/core";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { RedocStandalone } from "redoc"; import { RedocStandalone } from "redoc";
import closeIcon from "assets/closeIcon.svg"; import closeIcon from "assets/closeIcon.svg";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@ -33,7 +33,7 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
const classes = { root: style.root } const classes = { root: style.root }
const onSelectedOASService = async (selectedService) => { const onSelectedOASService = useCallback (async (selectedService) => {
if (oasServices.length === 0) { if (oasServices.length === 0) {
setSelectedServiceSpec(null); setSelectedServiceSpec(null);
setSelectedServiceName(""); setSelectedServiceName("");
@ -49,7 +49,8 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
toast.error("Error occurred while fetching service OAS spec", { containerId: TOAST_CONTAINER_ID }); toast.error("Error occurred while fetching service OAS spec", { containerId: TOAST_CONTAINER_ID });
console.error(e); console.error(e);
} }
}; // eslint-disable-next-line
},[oasServices]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@ -60,11 +61,13 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
console.error(e); console.error(e);
} }
})(); })();
// eslint-disable-next-line
}, [openModal]); }, [openModal]);
useEffect(() => { useEffect(() => {
onSelectedOASService(null); onSelectedOASService(null);
}, [oasServices]) }, [oasServices, onSelectedOASService])
return ( return (
<Modal <Modal
@ -80,15 +83,14 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
> >
<Fade in={openModal}> <Fade in={openModal}>
<Box sx={modalStyle}> <Box sx={modalStyle}>
<img src={closeIcon} alt="close" onClick={handleCloseModal} className={style.closeIcon} />
<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}>Service Catalog</div> <div className={style.title}>Service Catalog</div>
</div> </div>
<div style={{ cursor: "pointer" }}>
<img src={closeIcon} alt="close" onClick={handleCloseModal} />
</div>
</div> </div>
<div className={style.selectContainer} > <div className={style.selectContainer} >
<FormControl classes={classes}> <FormControl classes={classes}>
<SearchableDropdown <SearchableDropdown

View File

@ -1,4 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.5 11C20.5 16.2467 16.2467 20.5 11 20.5C5.75329 20.5 1.5 16.2467 1.5 11C1.5 5.75329 5.75329 1.5 11 1.5C16.2467 1.5 20.5 5.75329 20.5 11Z" stroke="#2B3560"/> <path d="M18.591 9.99997C18.591 14.7446 14.7447 18.5909 10.0001 18.5909C5.25546 18.5909 1.40918 14.7446 1.40918 9.99997C1.40918 5.25534 5.25546 1.40906 10.0001 1.40906C14.7447 1.40906 18.591 5.25534 18.591 9.99997Z" fill="#E9EBF8" stroke="#BCCEFD"/>
<path d="M14.4762 9.05338L13.1448 7.7219L11.1528 9.71382L9.16091 7.7219L7.82943 9.05338L9.82135 11.0453L7.83226 13.0344L9.16374 14.3659L11.1528 12.3768L13.1419 14.3659L14.4734 13.0344L12.4843 11.0453L14.4762 9.05338Z" fill="#627EF7"/> <path d="M13.1604 8.23038L11.95 7.01994L10.1392 8.83078L8.32832 7.01994L7.11789 8.23038L8.92872 10.0412L7.12046 11.8495L8.33089 13.0599L10.1392 11.2517L11.9474 13.0599L13.1579 11.8495L11.3496 10.0412L13.1604 8.23038Z" fill="#205CF5"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 588 B

View File

@ -56,3 +56,9 @@
.separtorLine .separtorLine
margin-top: 10px margin-top: 10px
border: 1px solid #E9EBF8 border: 1px solid #E9EBF8
.closeIcon
cursor: pointer
user-select: none
margin-top: -15px
margin-right: 5px

View File

@ -203,7 +203,7 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
> >
Refresh Refresh
</Button> </Button>
<img src={closeIcon} alt="close" onClick={() => onClose()} style={{ cursor: "pointer", userSelect: "none" }}></img> <img src={closeIcon} alt="close" onClick={() => onClose()} className={styles.closeIcon}></img>
</div> </div>
{isLoading && <div className={spinnerStyle.spinnerContainer}> {isLoading && <div className={spinnerStyle.spinnerContainer}>
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} /> <img alt="spinner" src={spinnerImg} style={{ height: 50 }} />