mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-03-03 18:20:32 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aaf3f1423 | ||
|
|
a2463b739a |
@@ -12,7 +12,7 @@ Copyright (C) UP9 Inc.
|
|||||||
#include "include/common.h"
|
#include "include/common.h"
|
||||||
|
|
||||||
|
|
||||||
static __always_inline int add_address_to_chunk(struct pt_regs *ctx, struct tls_chunk* chunk, __u64 id, __u32 fd) {
|
static __always_inline int add_address_to_chunk(struct pt_regs *ctx, struct tls_chunk* chunk, __u64 id, __u32 fd, struct ssl_info* info) {
|
||||||
__u32 pid = id >> 32;
|
__u32 pid = id >> 32;
|
||||||
__u64 key = (__u64) pid << 32 | fd;
|
__u64 key = (__u64) pid << 32 | fd;
|
||||||
|
|
||||||
@@ -22,14 +22,29 @@ static __always_inline int add_address_to_chunk(struct pt_regs *ctx, struct tls_
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = bpf_probe_read(chunk->address, sizeof(chunk->address), fdinfo->ipv4_addr);
|
int err;
|
||||||
chunk->flags |= (fdinfo->flags & FLAGS_IS_CLIENT_BIT);
|
|
||||||
|
|
||||||
if (err != 0) {
|
switch (info->address_info.mode) {
|
||||||
log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l);
|
case ADDRESS_INFO_MODE_UNDEFINED:
|
||||||
return 0;
|
chunk->address_info.mode = ADDRESS_INFO_MODE_SINGLE;
|
||||||
|
err = bpf_probe_read(&chunk->address_info.sport, sizeof(chunk->address_info.sport), &fdinfo->ipv4_addr[2]);
|
||||||
|
if (err != 0) {
|
||||||
|
log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bpf_probe_read(&chunk->address_info.saddr, sizeof(chunk->address_info.saddr), &fdinfo->ipv4_addr[4]);
|
||||||
|
if (err != 0) {
|
||||||
|
log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
bpf_probe_read(&chunk->address_info, sizeof(chunk->address_info), &info->address_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chunk->flags |= (fdinfo->flags & FLAGS_IS_CLIENT_BIT);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +119,7 @@ static __always_inline void output_ssl_chunk(struct pt_regs *ctx, struct ssl_inf
|
|||||||
chunk->len = count_bytes;
|
chunk->len = count_bytes;
|
||||||
chunk->fd = info->fd;
|
chunk->fd = info->fd;
|
||||||
|
|
||||||
if (!add_address_to_chunk(ctx, chunk, id, chunk->fd)) {
|
if (!add_address_to_chunk(ctx, chunk, id, chunk->fd, info)) {
|
||||||
// Without an address, we drop the chunk because there is not much to do with it in Go
|
// Without an address, we drop the chunk because there is not much to do with it in Go
|
||||||
//
|
//
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ Copyright (C) UP9 Inc.
|
|||||||
#ifndef __COMMON__
|
#ifndef __COMMON__
|
||||||
#define __COMMON__
|
#define __COMMON__
|
||||||
|
|
||||||
|
#define AF_INET 2 /* Internet IP Protocol */
|
||||||
|
|
||||||
const __s32 invalid_fd = -1;
|
const __s32 invalid_fd = -1;
|
||||||
|
|
||||||
static int add_address_to_chunk(struct pt_regs *ctx, struct tls_chunk* chunk, __u64 id, __u32 fd);
|
static int add_address_to_chunk(struct pt_regs *ctx, struct tls_chunk* chunk, __u64 id, __u32 fd, struct ssl_info* info);
|
||||||
static void send_chunk_part(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tls_chunk* chunk, int start, int end);
|
static void send_chunk_part(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tls_chunk* chunk, int start, int end);
|
||||||
static void send_chunk(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tls_chunk* chunk);
|
static void send_chunk(struct pt_regs *ctx, __u8* buffer, __u64 id, struct tls_chunk* chunk);
|
||||||
static void output_ssl_chunk(struct pt_regs *ctx, struct ssl_info* info, int count_bytes, __u64 id, __u32 flags);
|
static void output_ssl_chunk(struct pt_regs *ctx, struct ssl_info* info, int count_bytes, __u64 id, __u32 flags);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Copyright (C) UP9 Inc.
|
|||||||
|
|
||||||
#include "legacy_kernel.h"
|
#include "legacy_kernel.h"
|
||||||
|
|
||||||
|
#include <bpf/bpf_endian.h>
|
||||||
#include <bpf/bpf_helpers.h>
|
#include <bpf/bpf_helpers.h>
|
||||||
#include <bpf/bpf_tracing.h>
|
#include <bpf/bpf_tracing.h>
|
||||||
#include <bpf/bpf_core_read.h>
|
#include <bpf/bpf_core_read.h>
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ Copyright (C) UP9 Inc.
|
|||||||
#define LOG_ERROR_PUTTING_CONNECT_INFO (14)
|
#define LOG_ERROR_PUTTING_CONNECT_INFO (14)
|
||||||
#define LOG_ERROR_GETTING_CONNECT_INFO (15)
|
#define LOG_ERROR_GETTING_CONNECT_INFO (15)
|
||||||
#define LOG_ERROR_READING_CONNECT_INFO (16)
|
#define LOG_ERROR_READING_CONNECT_INFO (16)
|
||||||
|
#define LOG_ERROR_READING_SOCKET_FAMILY (17)
|
||||||
|
#define LOG_ERROR_READING_SOCKET_DADDR (18)
|
||||||
|
#define LOG_ERROR_READING_SOCKET_SADDR (19)
|
||||||
|
#define LOG_ERROR_READING_SOCKET_DPORT (20)
|
||||||
|
#define LOG_ERROR_READING_SOCKET_SPORT (21)
|
||||||
|
|
||||||
// Sometimes we have the same error, happening from different locations.
|
// Sometimes we have the same error, happening from different locations.
|
||||||
// in order to be able to distinct between them in the log, we add an
|
// in order to be able to distinct between them in the log, we add an
|
||||||
|
|||||||
@@ -24,6 +24,21 @@ Copyright (C) UP9 Inc.
|
|||||||
//
|
//
|
||||||
// Be careful when editing, alignment and padding should be exactly the same in go/c.
|
// Be careful when editing, alignment and padding should be exactly the same in go/c.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ADDRESS_INFO_MODE_UNDEFINED,
|
||||||
|
ADDRESS_INFO_MODE_SINGLE,
|
||||||
|
ADDRESS_INFO_MODE_PAIR,
|
||||||
|
} address_info_mode;
|
||||||
|
|
||||||
|
struct address_info {
|
||||||
|
address_info_mode mode;
|
||||||
|
__be32 saddr;
|
||||||
|
__be32 daddr;
|
||||||
|
__be16 sport;
|
||||||
|
__be16 dport;
|
||||||
|
};
|
||||||
|
|
||||||
struct tls_chunk {
|
struct tls_chunk {
|
||||||
__u32 pid;
|
__u32 pid;
|
||||||
__u32 tgid;
|
__u32 tgid;
|
||||||
@@ -32,7 +47,7 @@ struct tls_chunk {
|
|||||||
__u32 recorded;
|
__u32 recorded;
|
||||||
__u32 fd;
|
__u32 fd;
|
||||||
__u32 flags;
|
__u32 flags;
|
||||||
__u8 address[16];
|
struct address_info address_info;
|
||||||
__u8 data[CHUNK_SIZE]; // Must be N^2
|
__u8 data[CHUNK_SIZE]; // Must be N^2
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,6 +56,7 @@ struct ssl_info {
|
|||||||
__u32 buffer_len;
|
__u32 buffer_len;
|
||||||
__u32 fd;
|
__u32 fd;
|
||||||
__u64 created_at_nano;
|
__u64 created_at_nano;
|
||||||
|
struct address_info address_info;
|
||||||
|
|
||||||
// for ssl_write and ssl_read must be zero
|
// for ssl_write and ssl_read must be zero
|
||||||
// for ssl_write_ex and ssl_read_ex save the *written/*readbytes pointer.
|
// for ssl_write_ex and ssl_read_ex save the *written/*readbytes pointer.
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ static __always_inline int get_count_bytes(struct pt_regs *ctx, struct ssl_info*
|
|||||||
}
|
}
|
||||||
|
|
||||||
static __always_inline void ssl_uprobe(struct pt_regs *ctx, void* ssl, void* buffer, int num, struct bpf_map_def* map_fd, size_t *count_ptr) {
|
static __always_inline void ssl_uprobe(struct pt_regs *ctx, void* ssl, void* buffer, int num, struct bpf_map_def* map_fd, size_t *count_ptr) {
|
||||||
|
long err;
|
||||||
|
|
||||||
__u64 id = bpf_get_current_pid_tgid();
|
__u64 id = bpf_get_current_pid_tgid();
|
||||||
|
|
||||||
if (!should_tap(id >> 32)) {
|
if (!should_tap(id >> 32)) {
|
||||||
@@ -53,7 +55,7 @@ static __always_inline void ssl_uprobe(struct pt_regs *ctx, void* ssl, void* buf
|
|||||||
info.count_ptr = count_ptr;
|
info.count_ptr = count_ptr;
|
||||||
info.buffer = buffer;
|
info.buffer = buffer;
|
||||||
|
|
||||||
long err = bpf_map_update_elem(map_fd, &id, &info, BPF_ANY);
|
err = bpf_map_update_elem(map_fd, &id, &info, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
log_error(ctx, LOG_ERROR_PUTTING_SSL_CONTEXT, id, err, 0l);
|
log_error(ctx, LOG_ERROR_PUTTING_SSL_CONTEXT, id, err, 0l);
|
||||||
@@ -66,7 +68,7 @@ static __always_inline void ssl_uretprobe(struct pt_regs *ctx, struct bpf_map_de
|
|||||||
if (!should_tap(id >> 32)) {
|
if (!should_tap(id >> 32)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ssl_info *infoPtr = bpf_map_lookup_elem(map_fd, &id);
|
struct ssl_info *infoPtr = bpf_map_lookup_elem(map_fd, &id);
|
||||||
|
|
||||||
if (infoPtr == NULL) {
|
if (infoPtr == NULL) {
|
||||||
@@ -99,10 +101,10 @@ static __always_inline void ssl_uretprobe(struct pt_regs *ctx, struct bpf_map_de
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count_bytes = get_count_bytes(ctx, &info, id);
|
int count_bytes = get_count_bytes(ctx, &info, id);
|
||||||
if (count_bytes <= 0) {
|
if (count_bytes <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
output_ssl_chunk(ctx, &info, count_bytes, id, flags);
|
output_ssl_chunk(ctx, &info, count_bytes, id, flags);
|
||||||
}
|
}
|
||||||
|
|||||||
79
tap/tlstapper/bpf/tcp_kprobes.c
Normal file
79
tap/tlstapper/bpf/tcp_kprobes.c
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include "include/headers.h"
|
||||||
|
#include "include/maps.h"
|
||||||
|
#include "include/log.h"
|
||||||
|
#include "include/logger_messages.h"
|
||||||
|
#include "include/pids.h"
|
||||||
|
#include "include/common.h"
|
||||||
|
|
||||||
|
static __always_inline void tcp_kprobe(struct pt_regs *ctx, struct bpf_map_def *map_fd, _Bool is_send) {
|
||||||
|
long err;
|
||||||
|
|
||||||
|
__u64 id = bpf_get_current_pid_tgid();
|
||||||
|
__u32 pid = id >> 32;
|
||||||
|
|
||||||
|
if (!should_tap(id >> 32)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ssl_info *info_ptr = bpf_map_lookup_elem(map_fd, &id);
|
||||||
|
// Happens when the connection is not tls
|
||||||
|
if (info_ptr == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sock *sk = (struct sock *) PT_REGS_PARM1(ctx);
|
||||||
|
|
||||||
|
short unsigned int family;
|
||||||
|
err = bpf_probe_read(&family, sizeof(family), (void *)&sk->__sk_common.skc_family);
|
||||||
|
if (err != 0) {
|
||||||
|
log_error(ctx, LOG_ERROR_READING_SOCKET_FAMILY, id, err, 0l);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (family != AF_INET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// daddr, saddr and dport are in network byte order (big endian)
|
||||||
|
// sport is in host byte order
|
||||||
|
__be32 saddr;
|
||||||
|
__be32 daddr;
|
||||||
|
__be16 dport;
|
||||||
|
__u16 sport;
|
||||||
|
|
||||||
|
err = bpf_probe_read(&saddr, sizeof(saddr), (void *)&sk->__sk_common.skc_rcv_saddr);
|
||||||
|
if (err != 0) {
|
||||||
|
log_error(ctx, LOG_ERROR_READING_SOCKET_SADDR, id, err, 0l);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = bpf_probe_read(&daddr, sizeof(daddr), (void *)&sk->__sk_common.skc_daddr);
|
||||||
|
if (err != 0) {
|
||||||
|
log_error(ctx, LOG_ERROR_READING_SOCKET_DADDR, id, err, 0l);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = bpf_probe_read(&dport, sizeof(dport), (void *)&sk->__sk_common.skc_dport);
|
||||||
|
if (err != 0) {
|
||||||
|
log_error(ctx, LOG_ERROR_READING_SOCKET_DPORT, id, err, 0l);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = bpf_probe_read(&sport, sizeof(sport), (void *)&sk->__sk_common.skc_num);
|
||||||
|
if (err != 0) {
|
||||||
|
log_error(ctx, LOG_ERROR_READING_SOCKET_SPORT, id, err, 0l);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info_ptr->address_info.mode = ADDRESS_INFO_MODE_PAIR;
|
||||||
|
info_ptr->address_info.daddr = daddr;
|
||||||
|
info_ptr->address_info.saddr = saddr;
|
||||||
|
info_ptr->address_info.dport = dport;
|
||||||
|
info_ptr->address_info.sport = bpf_htons(sport);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("kprobe/tcp_sendmsg")
|
||||||
|
void BPF_KPROBE(tcp_sendmsg) {
|
||||||
|
tcp_kprobe(ctx, &openssl_write_context, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("kprobe/tcp_recvmsg")
|
||||||
|
void BPF_KPROBE(tcp_recvmsg) {
|
||||||
|
tcp_kprobe(ctx, &openssl_read_context, false);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ Copyright (C) UP9 Inc.
|
|||||||
//
|
//
|
||||||
#include "common.c"
|
#include "common.c"
|
||||||
#include "openssl_uprobes.c"
|
#include "openssl_uprobes.c"
|
||||||
|
#include "tcp_kprobes.c"
|
||||||
#include "go_uprobes.c"
|
#include "go_uprobes.c"
|
||||||
#include "fd_tracepoints.c"
|
#include "fd_tracepoints.c"
|
||||||
#include "fd_to_address_tracepoints.c"
|
#include "fd_to_address_tracepoints.c"
|
||||||
|
|||||||
@@ -20,4 +20,9 @@ var bpfLogMessages = []string{
|
|||||||
/*0014*/ "[%d] Unable to put connect info [err: %d]",
|
/*0014*/ "[%d] Unable to put connect info [err: %d]",
|
||||||
/*0015*/ "[%d] Unable to get connect info",
|
/*0015*/ "[%d] Unable to get connect info",
|
||||||
/*0016*/ "[%d] Unable to read connect info [err: %d]",
|
/*0016*/ "[%d] Unable to read connect info [err: %d]",
|
||||||
|
/*0017*/ "[%d] Unable to read socket family [err: %d]",
|
||||||
|
/*0018*/ "[%d] Unable to read socket daddr [err: %d]",
|
||||||
|
/*0019*/ "[%d] Unable to read socket saddr [err: %d]",
|
||||||
|
/*0019*/ "[%d] Unable to read socket dport [err: %d]",
|
||||||
|
/*0021*/ "[%d] Unable to read socket sport [err: %d]",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,33 @@
|
|||||||
package tlstapper
|
package tlstapper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
|
||||||
"github.com/up9inc/mizu/tap/api"
|
"github.com/up9inc/mizu/tap/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FlagsIsClientBit uint32 = 1 << 0
|
const FlagsIsClientBit uint32 = 1 << 0
|
||||||
const FlagsIsReadBit uint32 = 1 << 1
|
const FlagsIsReadBit uint32 = 1 << 1
|
||||||
|
const (
|
||||||
|
addressInfoModeUndefined = iota
|
||||||
|
addressInfoModeSingle
|
||||||
|
addressInfoModePair
|
||||||
|
)
|
||||||
|
|
||||||
func (c *tlsTapperTlsChunk) getAddress() (net.IP, uint16, error) {
|
func (c *tlsTapperTlsChunk) getSrcAddress() (net.IP, uint16) {
|
||||||
address := bytes.NewReader(c.Address[:])
|
ip := intToIP(c.AddressInfo.Saddr)
|
||||||
var family uint16
|
port := ntohs(c.AddressInfo.Sport)
|
||||||
var port uint16
|
|
||||||
var ip32 uint32
|
|
||||||
|
|
||||||
if err := binary.Read(address, binary.BigEndian, &family); err != nil {
|
return ip, port
|
||||||
return nil, 0, errors.Wrap(err, 0)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := binary.Read(address, binary.BigEndian, &port); err != nil {
|
func (c *tlsTapperTlsChunk) getDstAddress() (net.IP, uint16) {
|
||||||
return nil, 0, errors.Wrap(err, 0)
|
ip := intToIP(c.AddressInfo.Daddr)
|
||||||
}
|
port := ntohs(c.AddressInfo.Dport)
|
||||||
|
|
||||||
if err := binary.Read(address, binary.BigEndian, &ip32); err != nil {
|
return ip, port
|
||||||
return nil, 0, errors.Wrap(err, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.IP{uint8(ip32 >> 24), uint8(ip32 >> 16), uint8(ip32 >> 8), uint8(ip32)}
|
|
||||||
|
|
||||||
return ip, port, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tlsTapperTlsChunk) isClient() bool {
|
func (c *tlsTapperTlsChunk) isClient() bool {
|
||||||
@@ -59,26 +54,54 @@ func (c *tlsTapperTlsChunk) isRequest() bool {
|
|||||||
return (c.isClient() && c.isWrite()) || (c.isServer() && c.isRead())
|
return (c.isClient() && c.isWrite()) || (c.isServer() && c.isRead())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tlsTapperTlsChunk) getAddressPair() (addressPair, error) {
|
func (c *tlsTapperTlsChunk) getAddressPair() (addressPair, bool) {
|
||||||
ip, port, err := c.getAddress()
|
var (
|
||||||
|
srcIp, dstIp net.IP
|
||||||
|
srcPort, dstPort uint16
|
||||||
|
full bool
|
||||||
|
)
|
||||||
|
|
||||||
if err != nil {
|
switch c.AddressInfo.Mode {
|
||||||
return addressPair{}, err
|
case addressInfoModeSingle:
|
||||||
|
if c.isRequest() {
|
||||||
|
srcIp, srcPort = api.UnknownIp, api.UnknownPort
|
||||||
|
dstIp, dstPort = c.getSrcAddress()
|
||||||
|
} else {
|
||||||
|
srcIp, srcPort = c.getSrcAddress()
|
||||||
|
dstIp, dstPort = api.UnknownIp, api.UnknownPort
|
||||||
|
}
|
||||||
|
full = false
|
||||||
|
case addressInfoModePair:
|
||||||
|
if c.isRequest() {
|
||||||
|
srcIp, srcPort = c.getSrcAddress()
|
||||||
|
dstIp, dstPort = c.getDstAddress()
|
||||||
|
} else {
|
||||||
|
srcIp, srcPort = c.getDstAddress()
|
||||||
|
dstIp, dstPort = c.getSrcAddress()
|
||||||
|
}
|
||||||
|
full = true
|
||||||
|
case addressInfoModeUndefined:
|
||||||
|
srcIp, srcPort = api.UnknownIp, api.UnknownPort
|
||||||
|
dstIp, dstPort = api.UnknownIp, api.UnknownPort
|
||||||
|
full = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.isRequest() {
|
return addressPair{
|
||||||
return addressPair{
|
srcIp: srcIp,
|
||||||
srcIp: api.UnknownIp,
|
srcPort: srcPort,
|
||||||
srcPort: api.UnknownPort,
|
dstIp: dstIp,
|
||||||
dstIp: ip,
|
dstPort: dstPort,
|
||||||
dstPort: port,
|
}, full
|
||||||
}, nil
|
}
|
||||||
} else {
|
|
||||||
return addressPair{
|
// intToIP converts IPv4 number to net.IP
|
||||||
srcIp: ip,
|
func intToIP(ip32be uint32) net.IP {
|
||||||
srcPort: port,
|
return net.IPv4(uint8(ip32be), uint8(ip32be>>8), uint8(ip32be>>16), uint8(ip32be>>24))
|
||||||
dstIp: api.UnknownIp,
|
}
|
||||||
dstPort: api.UnknownPort,
|
|
||||||
}, nil
|
// ntohs converts big endian (network byte order) to little endian (assuming that's the host byte order)
|
||||||
}
|
func ntohs(i16be uint16) uint16 {
|
||||||
|
b := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(b, i16be)
|
||||||
|
return *(*uint16)(unsafe.Pointer(&b[0]))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ type sslHooks struct {
|
|||||||
sslWriteExRetProbe link.Link
|
sslWriteExRetProbe link.Link
|
||||||
sslReadExProbe link.Link
|
sslReadExProbe link.Link
|
||||||
sslReadExRetProbe link.Link
|
sslReadExRetProbe link.Link
|
||||||
|
tcpSendmsg link.Link
|
||||||
|
tcpRecvmsg link.Link
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sslHooks) installUprobes(bpfObjects *tlsTapperObjects, sslLibraryPath string) error {
|
func (s *sslHooks) installUprobes(bpfObjects *tlsTapperObjects, sslLibraryPath string) error {
|
||||||
@@ -103,6 +105,16 @@ func (s *sslHooks) installSslHooks(bpfObjects *tlsTapperObjects, sslLibrary *lin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.tcpSendmsg, err = link.Kprobe("tcp_sendmsg", bpfObjects.TcpSendmsg, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.tcpRecvmsg, err = link.Kprobe("tcp_recvmsg", bpfObjects.TcpRecvmsg, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,5 +161,17 @@ func (s *sslHooks) close() []error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.tcpSendmsg != nil {
|
||||||
|
if err := s.tcpSendmsg.Close(); err != nil {
|
||||||
|
returnValue = append(returnValue, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.tcpRecvmsg != nil {
|
||||||
|
if err := s.tcpRecvmsg.Close(); err != nil {
|
||||||
|
returnValue = append(returnValue, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return returnValue
|
return returnValue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,14 +134,9 @@ func (p *tlsPoller) pollChunksPerfBuffer(chunks chan<- *tlsTapperTlsChunk) {
|
|||||||
|
|
||||||
func (p *tlsPoller) handleTlsChunk(chunk *tlsTapperTlsChunk, extension *api.Extension, emitter api.Emitter,
|
func (p *tlsPoller) handleTlsChunk(chunk *tlsTapperTlsChunk, extension *api.Extension, emitter api.Emitter,
|
||||||
options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) error {
|
options *api.TrafficFilteringOptions, streamsMap api.TcpStreamMap) error {
|
||||||
address, err := p.getSockfdAddressPair(chunk)
|
address, err := p.getAddressPair(chunk)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
address, err = chunk.getAddressPair()
|
return err
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key := buildTlsKey(address)
|
key := buildTlsKey(address)
|
||||||
@@ -161,6 +156,22 @@ func (p *tlsPoller) handleTlsChunk(chunk *tlsTapperTlsChunk, extension *api.Exte
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *tlsPoller) getAddressPair(chunk *tlsTapperTlsChunk) (addressPair, error) {
|
||||||
|
addrPairFromChunk, full := chunk.getAddressPair()
|
||||||
|
if full {
|
||||||
|
return addrPairFromChunk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addrPairFromSockfd, err := p.getSockfdAddressPair(chunk)
|
||||||
|
if err == nil {
|
||||||
|
return addrPairFromSockfd, nil
|
||||||
|
} else {
|
||||||
|
logger.Log.Error("failed to get address from sock fd:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrPairFromChunk, err
|
||||||
|
}
|
||||||
|
|
||||||
func (p *tlsPoller) startNewTlsReader(chunk *tlsTapperTlsChunk, address *addressPair, key string,
|
func (p *tlsPoller) startNewTlsReader(chunk *tlsTapperTlsChunk, 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 {
|
||||||
|
|||||||
@@ -19,15 +19,21 @@ type tlsTapper46GoidOffsets struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tlsTapper46TlsChunk struct {
|
type tlsTapper46TlsChunk struct {
|
||||||
Pid uint32
|
Pid uint32
|
||||||
Tgid uint32
|
Tgid uint32
|
||||||
Len uint32
|
Len uint32
|
||||||
Start uint32
|
Start uint32
|
||||||
Recorded uint32
|
Recorded uint32
|
||||||
Fd uint32
|
Fd uint32
|
||||||
Flags uint32
|
Flags uint32
|
||||||
Address [16]uint8
|
AddressInfo struct {
|
||||||
Data [4096]uint8
|
Mode int32
|
||||||
|
Saddr uint32
|
||||||
|
Daddr uint32
|
||||||
|
Sport uint16
|
||||||
|
Dport uint16
|
||||||
|
}
|
||||||
|
Data [4096]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTlsTapper46 returns the embedded CollectionSpec for tlsTapper46.
|
// loadTlsTapper46 returns the embedded CollectionSpec for tlsTapper46.
|
||||||
@@ -93,6 +99,8 @@ type tlsTapper46ProgramSpecs struct {
|
|||||||
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.ProgramSpec `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.ProgramSpec `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsTapper46MapSpecs contains maps before they are loaded into the kernel.
|
// tlsTapper46MapSpecs contains maps before they are loaded into the kernel.
|
||||||
@@ -189,6 +197,8 @@ type tlsTapper46Programs struct {
|
|||||||
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.Program `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.Program `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tlsTapper46Programs) Close() error {
|
func (p *tlsTapper46Programs) Close() error {
|
||||||
@@ -215,6 +225,8 @@ func (p *tlsTapper46Programs) Close() error {
|
|||||||
p.SysEnterWrite,
|
p.SysEnterWrite,
|
||||||
p.SysExitAccept4,
|
p.SysExitAccept4,
|
||||||
p.SysExitConnect,
|
p.SysExitConnect,
|
||||||
|
p.TcpRecvmsg,
|
||||||
|
p.TcpSendmsg,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -19,15 +19,21 @@ type tlsTapper46GoidOffsets struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tlsTapper46TlsChunk struct {
|
type tlsTapper46TlsChunk struct {
|
||||||
Pid uint32
|
Pid uint32
|
||||||
Tgid uint32
|
Tgid uint32
|
||||||
Len uint32
|
Len uint32
|
||||||
Start uint32
|
Start uint32
|
||||||
Recorded uint32
|
Recorded uint32
|
||||||
Fd uint32
|
Fd uint32
|
||||||
Flags uint32
|
Flags uint32
|
||||||
Address [16]uint8
|
AddressInfo struct {
|
||||||
Data [4096]uint8
|
Mode int32
|
||||||
|
Saddr uint32
|
||||||
|
Daddr uint32
|
||||||
|
Sport uint16
|
||||||
|
Dport uint16
|
||||||
|
}
|
||||||
|
Data [4096]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTlsTapper46 returns the embedded CollectionSpec for tlsTapper46.
|
// loadTlsTapper46 returns the embedded CollectionSpec for tlsTapper46.
|
||||||
@@ -93,6 +99,8 @@ type tlsTapper46ProgramSpecs struct {
|
|||||||
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.ProgramSpec `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.ProgramSpec `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsTapper46MapSpecs contains maps before they are loaded into the kernel.
|
// tlsTapper46MapSpecs contains maps before they are loaded into the kernel.
|
||||||
@@ -189,6 +197,8 @@ type tlsTapper46Programs struct {
|
|||||||
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.Program `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.Program `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tlsTapper46Programs) Close() error {
|
func (p *tlsTapper46Programs) Close() error {
|
||||||
@@ -215,6 +225,8 @@ func (p *tlsTapper46Programs) Close() error {
|
|||||||
p.SysEnterWrite,
|
p.SysEnterWrite,
|
||||||
p.SysExitAccept4,
|
p.SysExitAccept4,
|
||||||
p.SysExitConnect,
|
p.SysExitConnect,
|
||||||
|
p.TcpRecvmsg,
|
||||||
|
p.TcpSendmsg,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -19,15 +19,21 @@ type tlsTapperGoidOffsets struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tlsTapperTlsChunk struct {
|
type tlsTapperTlsChunk struct {
|
||||||
Pid uint32
|
Pid uint32
|
||||||
Tgid uint32
|
Tgid uint32
|
||||||
Len uint32
|
Len uint32
|
||||||
Start uint32
|
Start uint32
|
||||||
Recorded uint32
|
Recorded uint32
|
||||||
Fd uint32
|
Fd uint32
|
||||||
Flags uint32
|
Flags uint32
|
||||||
Address [16]uint8
|
AddressInfo struct {
|
||||||
Data [4096]uint8
|
Mode int32
|
||||||
|
Saddr uint32
|
||||||
|
Daddr uint32
|
||||||
|
Sport uint16
|
||||||
|
Dport uint16
|
||||||
|
}
|
||||||
|
Data [4096]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTlsTapper returns the embedded CollectionSpec for tlsTapper.
|
// loadTlsTapper returns the embedded CollectionSpec for tlsTapper.
|
||||||
@@ -93,6 +99,8 @@ type tlsTapperProgramSpecs struct {
|
|||||||
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.ProgramSpec `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.ProgramSpec `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsTapperMapSpecs contains maps before they are loaded into the kernel.
|
// tlsTapperMapSpecs contains maps before they are loaded into the kernel.
|
||||||
@@ -189,6 +197,8 @@ type tlsTapperPrograms struct {
|
|||||||
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.Program `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.Program `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tlsTapperPrograms) Close() error {
|
func (p *tlsTapperPrograms) Close() error {
|
||||||
@@ -215,6 +225,8 @@ func (p *tlsTapperPrograms) Close() error {
|
|||||||
p.SysEnterWrite,
|
p.SysEnterWrite,
|
||||||
p.SysExitAccept4,
|
p.SysExitAccept4,
|
||||||
p.SysExitConnect,
|
p.SysExitConnect,
|
||||||
|
p.TcpRecvmsg,
|
||||||
|
p.TcpSendmsg,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -19,15 +19,21 @@ type tlsTapperGoidOffsets struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tlsTapperTlsChunk struct {
|
type tlsTapperTlsChunk struct {
|
||||||
Pid uint32
|
Pid uint32
|
||||||
Tgid uint32
|
Tgid uint32
|
||||||
Len uint32
|
Len uint32
|
||||||
Start uint32
|
Start uint32
|
||||||
Recorded uint32
|
Recorded uint32
|
||||||
Fd uint32
|
Fd uint32
|
||||||
Flags uint32
|
Flags uint32
|
||||||
Address [16]uint8
|
AddressInfo struct {
|
||||||
Data [4096]uint8
|
Mode int32
|
||||||
|
Saddr uint32
|
||||||
|
Daddr uint32
|
||||||
|
Sport uint16
|
||||||
|
Dport uint16
|
||||||
|
}
|
||||||
|
Data [4096]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTlsTapper returns the embedded CollectionSpec for tlsTapper.
|
// loadTlsTapper returns the embedded CollectionSpec for tlsTapper.
|
||||||
@@ -93,6 +99,8 @@ type tlsTapperProgramSpecs struct {
|
|||||||
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.ProgramSpec `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.ProgramSpec `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.ProgramSpec `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.ProgramSpec `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.ProgramSpec `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsTapperMapSpecs contains maps before they are loaded into the kernel.
|
// tlsTapperMapSpecs contains maps before they are loaded into the kernel.
|
||||||
@@ -189,6 +197,8 @@ type tlsTapperPrograms struct {
|
|||||||
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
SysEnterWrite *ebpf.Program `ebpf:"sys_enter_write"`
|
||||||
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
SysExitAccept4 *ebpf.Program `ebpf:"sys_exit_accept4"`
|
||||||
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
SysExitConnect *ebpf.Program `ebpf:"sys_exit_connect"`
|
||||||
|
TcpRecvmsg *ebpf.Program `ebpf:"tcp_recvmsg"`
|
||||||
|
TcpSendmsg *ebpf.Program `ebpf:"tcp_sendmsg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *tlsTapperPrograms) Close() error {
|
func (p *tlsTapperPrograms) Close() error {
|
||||||
@@ -215,6 +225,8 @@ func (p *tlsTapperPrograms) Close() error {
|
|||||||
p.SysEnterWrite,
|
p.SysEnterWrite,
|
||||||
p.SysExitAccept4,
|
p.SysExitAccept4,
|
||||||
p.SysExitConnect,
|
p.SysExitConnect,
|
||||||
|
p.TcpRecvmsg,
|
||||||
|
p.TcpSendmsg,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -65,6 +65,7 @@
|
|||||||
"recharts": "^2.1.10",
|
"recharts": "^2.1.10",
|
||||||
"redoc": "^2.0.0-rc.71",
|
"redoc": "^2.0.0-rc.71",
|
||||||
"styled-components": "^5.3.5",
|
"styled-components": "^5.3.5",
|
||||||
|
"use-file-picker": "^1.4.2",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"xml-formatter": "^2.6.1"
|
"xml-formatter": "^2.6.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,6 +17,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
width: -moz-available;
|
width: -moz-available;
|
||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
width: strech;
|
width: stretch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import styles from './EntriesList.module.sass';
|
import styles from './EntriesList.module.sass';
|
||||||
import ScrollableFeedVirtualized from "react-scrollable-feed-virtualized";
|
import ScrollableFeedVirtualized from "react-scrollable-feed-virtualized";
|
||||||
import Moment from 'moment';
|
import { EntryItem } from "../EntryListItem/EntryListItem";
|
||||||
import {EntryItem} from "../EntryListItem/EntryListItem";
|
|
||||||
import down from "assets/downImg.svg";
|
import down from "assets/downImg.svg";
|
||||||
import spinner from 'assets/spinner.svg';
|
import spinner from 'assets/spinner.svg';
|
||||||
import {RecoilState, useRecoilState, useRecoilValue, useSetRecoilState} from "recoil";
|
import { RecoilState, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
|
||||||
import entriesAtom from "../../recoil/entries";
|
import entriesAtom from "../../recoil/entries";
|
||||||
import queryAtom from "../../recoil/query";
|
import queryAtom from "../../recoil/query";
|
||||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
|
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
|
||||||
import TrafficViewerApi from "../TrafficViewer/TrafficViewerApi";
|
import TrafficViewerApi from "../TrafficViewer/TrafficViewerApi";
|
||||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||||
import {toast} from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import {MAX_ENTRIES, TOAST_CONTAINER_ID} from "../../configs/Consts";
|
import { MAX_ENTRIES, TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||||
import tappingStatusAtom from "../../recoil/tappingStatus";
|
import tappingStatusAtom from "../../recoil/tappingStatus";
|
||||||
import leftOffTopAtom from "../../recoil/leftOffTop";
|
import leftOffTopAtom from "../../recoil/leftOffTop";
|
||||||
|
import Moment from "moment";
|
||||||
|
|
||||||
interface EntriesListProps {
|
interface EntriesListProps {
|
||||||
listEntryREF: any;
|
listEntryREF: any;
|
||||||
|
|||||||
@@ -37,11 +37,6 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
|||||||
theme="github"
|
theme="github"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
editorProps={{ $blockScrolling: true }}
|
editorProps={{ $blockScrolling: true }}
|
||||||
setOptions={{
|
|
||||||
enableBasicAutocompletion: true,
|
|
||||||
enableLiveAutocompletion: true,
|
|
||||||
enableSnippets: true
|
|
||||||
}}
|
|
||||||
showPrintMargin={false}
|
showPrintMargin={false}
|
||||||
value={code}
|
value={code}
|
||||||
width="100%"
|
width="100%"
|
||||||
|
|||||||
33
ui-common/src/components/UI/FilePicker/FilePicker.tsx
Normal file
33
ui-common/src/components/UI/FilePicker/FilePicker.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFilePicker } from 'use-file-picker';
|
||||||
|
import { FileContent } from 'use-file-picker/dist/interfaces';
|
||||||
|
|
||||||
|
interface IFilePickerProps {
|
||||||
|
onLoadingComplete: (file: FileContent) => void;
|
||||||
|
elem: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilePicker = ({ elem, onLoadingComplete }: IFilePickerProps) => {
|
||||||
|
const [openFileSelector, { filesContent }] = useFilePicker({
|
||||||
|
accept: ['.json'],
|
||||||
|
limitFilesConfig: { max: 1 },
|
||||||
|
maxFileSize: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const onFileSelectorClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
openFileSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
filesContent.length && onLoadingComplete(filesContent[0])
|
||||||
|
}, [filesContent, onLoadingComplete]);
|
||||||
|
|
||||||
|
return (<React.Fragment>
|
||||||
|
{React.cloneElement(elem, { onClick: onFileSelectorClick })}
|
||||||
|
</React.Fragment>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilePicker;
|
||||||
@@ -75,5 +75,6 @@ const KeyValueTable: React.FC<KeyValueTableProps> = ({ data, onDataChange, keyPl
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
export const convertParamsToArr = (paramsObj) => Object.entries(paramsObj).map(([key, value]) => { return { key, value } })
|
||||||
|
export const convertArrToKeyValueObject = (arr) => arr.reduce((acc, curr) => { acc[curr.key] = curr.value; return acc }, {})
|
||||||
export default KeyValueTable
|
export default KeyValueTable
|
||||||
|
|||||||
@@ -79,5 +79,12 @@
|
|||||||
overflow: hidden
|
overflow: hidden
|
||||||
|
|
||||||
b::after
|
b::after
|
||||||
content: '\b'
|
content: '\b'
|
||||||
display: inline
|
display: inline
|
||||||
|
|
||||||
|
.icon
|
||||||
|
width: 24px
|
||||||
|
height: 26px
|
||||||
|
stroke-width: 0px
|
||||||
|
fill: $blue-color
|
||||||
|
stroke: $blue-color
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
import { Accordion, AccordionDetails, AccordionSummary, Backdrop, Box, Button, Fade, Modal } from "@mui/material";
|
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import DownloadIcon from '@mui/icons-material/FileDownloadOutlined';
|
||||||
|
import UploadIcon from '@mui/icons-material/UploadFile';
|
||||||
|
import closeIcon from "assets/close.svg";
|
||||||
|
import refreshImg from "assets/refresh.svg";
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary, Backdrop, Box, Button, Fade, Modal } from "@mui/material";
|
||||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||||
import { useCommonStyles } from "../../../helpers/commonStyle";
|
|
||||||
import { Tabs } from "../../UI";
|
|
||||||
import KeyValueTable from "../../UI/KeyValueTable/KeyValueTable";
|
|
||||||
import CodeEditor from "../../UI/CodeEditor/CodeEditor";
|
|
||||||
import { useRecoilValue, RecoilState, useRecoilState } from "recoil";
|
|
||||||
import TrafficViewerApiAtom from "../../../recoil/TrafficViewerApi/atom";
|
|
||||||
import TrafficViewerApi from "../../TrafficViewer/TrafficViewerApi";
|
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import { RecoilState, useRecoilState, useRecoilValue } from "recoil";
|
||||||
|
import { FileContent } from "use-file-picker/dist/interfaces";
|
||||||
import { TOAST_CONTAINER_ID } from "../../../configs/Consts";
|
import { TOAST_CONTAINER_ID } from "../../../configs/Consts";
|
||||||
import styles from './ReplayRequestModal.module.sass'
|
import { useCommonStyles } from "../../../helpers/commonStyle";
|
||||||
import closeIcon from "assets/close.svg"
|
|
||||||
import refreshImg from "assets/refresh.svg"
|
|
||||||
import { formatRequestWithOutError } from "../../EntryDetailed/EntrySections/EntrySections";
|
|
||||||
import entryDataAtom from "../../../recoil/entryData";
|
|
||||||
import { AutoRepresentation, TabsEnum } from "../../EntryDetailed/EntryViewer/AutoRepresentation";
|
|
||||||
import useDebounce from "../../../hooks/useDebounce"
|
|
||||||
import replayRequestModalOpenAtom from "../../../recoil/replayRequestModalOpen";
|
|
||||||
import { Utils } from "../../../helpers/Utils";
|
import { Utils } from "../../../helpers/Utils";
|
||||||
|
import useDebounce from "../../../hooks/useDebounce";
|
||||||
|
import entryDataAtom from "../../../recoil/entryData";
|
||||||
|
import replayRequestModalOpenAtom from "../../../recoil/replayRequestModalOpen";
|
||||||
|
import TrafficViewerApiAtom from "../../../recoil/TrafficViewerApi/atom";
|
||||||
|
import { formatRequestWithOutError } from "../../EntryDetailed/EntrySections/EntrySections";
|
||||||
|
import { AutoRepresentation, TabsEnum } from "../../EntryDetailed/EntryViewer/AutoRepresentation";
|
||||||
|
import TrafficViewerApi from "../../TrafficViewer/TrafficViewerApi";
|
||||||
|
import { Tabs } from "../../UI";
|
||||||
|
import CodeEditor from "../../UI/CodeEditor/CodeEditor";
|
||||||
|
import FilePicker from '../../UI/FilePicker/FilePicker';
|
||||||
|
import KeyValueTable, { convertArrToKeyValueObject, convertParamsToArr } from "../../UI/KeyValueTable/KeyValueTable";
|
||||||
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
||||||
|
import { IReplayRequestData, KeyValuePair } from './interfaces';
|
||||||
|
import styles from './ReplayRequestModal.module.sass';
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -37,11 +42,6 @@ const modalStyle = {
|
|||||||
paddingBottom: "15px"
|
paddingBottom: "15px"
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ReplayRequestModalProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RequestTabs {
|
enum RequestTabs {
|
||||||
Params = "params",
|
Params = "params",
|
||||||
Headers = "headers",
|
Headers = "headers",
|
||||||
@@ -51,8 +51,6 @@ enum RequestTabs {
|
|||||||
const HTTP_METHODS = ["get", "post", "put", "head", "options", "delete"]
|
const HTTP_METHODS = ["get", "post", "put", "head", "options", "delete"]
|
||||||
const TABS = [{ tab: RequestTabs.Headers }, { tab: RequestTabs.Params }, { tab: RequestTabs.Body }];
|
const TABS = [{ tab: RequestTabs.Headers }, { tab: RequestTabs.Params }, { tab: RequestTabs.Body }];
|
||||||
|
|
||||||
const convertParamsToArr = (paramsObj) => Object.entries(paramsObj).map(([key, value]) => { return { key, value } })
|
|
||||||
|
|
||||||
const getQueryStringParams = (link: String) => {
|
const getQueryStringParams = (link: String) => {
|
||||||
|
|
||||||
if (link) {
|
if (link) {
|
||||||
@@ -69,43 +67,61 @@ const decodeQueryParam = (p) => {
|
|||||||
return decodeURIComponent(p.replace(/\+/g, ' '));
|
return decodeURIComponent(p.replace(/\+/g, ' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ReplayRequestModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose }) => {
|
const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose }) => {
|
||||||
const entryData = useRecoilValue(entryDataAtom)
|
const entryData = useRecoilValue(entryDataAtom)
|
||||||
const request = entryData.data.request
|
const request = entryData.data.request
|
||||||
const [method, setMethod] = useState(request?.method?.toLowerCase() as string)
|
|
||||||
const getHostUrl = useCallback(() => {
|
const getHostUrl = useCallback(() => {
|
||||||
return entryData.data.dst.name ? entryData.data?.dst?.name : entryData.data.dst.ip
|
return entryData.data.dst.name ? entryData.data?.dst?.name : entryData.data.dst.ip
|
||||||
}, [entryData.data.dst.ip, entryData.data.dst.name])
|
}, [entryData.data.dst.ip, entryData.data.dst.name])
|
||||||
const [hostPortInput, setHostPortInput] = useState(`${entryData.base.proto.name}://${getHostUrl()}:${entryData.data.dst.port}`)
|
const getHostPortVal = useCallback(() => {
|
||||||
|
return `${entryData.base.proto.name}://${getHostUrl()}:${entryData.data.dst.port}`
|
||||||
|
}, [entryData.base.proto.name, entryData.data.dst.port, getHostUrl])
|
||||||
|
const [hostPortInput, setHostPortInput] = useState(getHostPortVal())
|
||||||
const [pathInput, setPathInput] = useState(request.path);
|
const [pathInput, setPathInput] = useState(request.path);
|
||||||
const commonClasses = useCommonStyles();
|
const commonClasses = useCommonStyles();
|
||||||
const [currentTab, setCurrentTab] = useState(TABS[0].tab);
|
const [currentTab, setCurrentTab] = useState(TABS[0].tab);
|
||||||
const [response, setResponse] = useState(null);
|
const [response, setResponse] = useState(null);
|
||||||
const [postData, setPostData] = useState(request?.postData?.text || JSON.stringify(request?.postData?.params));
|
|
||||||
const [params, setParams] = useState(convertParamsToArr(request?.queryString || {}))
|
|
||||||
const [headers, setHeaders] = useState(convertParamsToArr(request?.headers || {}))
|
|
||||||
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [requestExpanded, setRequestExpanded] = useState(true)
|
const [requestExpanded, setRequestExpanded] = useState(true)
|
||||||
const [responseExpanded, setResponseExpanded] = useState(false)
|
const [responseExpanded, setResponseExpanded] = useState(false)
|
||||||
|
|
||||||
|
const getInitialRequestData = useCallback((): IReplayRequestData => {
|
||||||
|
return {
|
||||||
|
method: request?.method?.toLowerCase() as string,
|
||||||
|
hostPort: `${entryData.base.proto.name}://${getHostUrl()}:${entryData.data.dst.port}`,
|
||||||
|
path: request.path,
|
||||||
|
postData: request.postData?.text || JSON.stringify(request.postData?.params),
|
||||||
|
headers: convertParamsToArr(request.headers || {}),
|
||||||
|
params: convertParamsToArr(request.queryString || {})
|
||||||
|
}
|
||||||
|
}, [entryData.base.proto.name, entryData.data.dst.port, getHostUrl, request.headers, request?.method, request.path, request.postData?.params, request.postData?.text, request.queryString])
|
||||||
|
|
||||||
|
const [requestDataModel, setRequestData] = useState<IReplayRequestData>(getInitialRequestData())
|
||||||
|
|
||||||
const debouncedPath = useDebounce(pathInput, 500);
|
const debouncedPath = useDebounce(pathInput, 500);
|
||||||
|
|
||||||
|
const addParamsToUrl = useCallback((url: string, params: KeyValuePair[]) => {
|
||||||
|
const urlParams = new URLSearchParams("");
|
||||||
|
params.forEach(param => urlParams.append(param.key, param.value as string))
|
||||||
|
return `${url}?${urlParams.toString()}`
|
||||||
|
}, [])
|
||||||
|
|
||||||
const onParamsChange = useCallback((newParams) => {
|
const onParamsChange = useCallback((newParams) => {
|
||||||
setParams(newParams);
|
|
||||||
let newUrl = `${debouncedPath ? debouncedPath.split('?')[0] : ""}`
|
let newUrl = `${debouncedPath ? debouncedPath.split('?')[0] : ""}`
|
||||||
newParams.forEach(({ key, value }, index) => {
|
newUrl = addParamsToUrl(newUrl, newParams)
|
||||||
newUrl += index > 0 ? '&' : '?'
|
|
||||||
newUrl += `${key}` + (value ? `=${value}` : "")
|
|
||||||
})
|
|
||||||
|
|
||||||
setPathInput(newUrl)
|
setPathInput(newUrl)
|
||||||
|
}, [addParamsToUrl, debouncedPath])
|
||||||
}, [debouncedPath])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newParams = getQueryStringParams(debouncedPath);
|
const params = convertParamsToArr(getQueryStringParams(debouncedPath));
|
||||||
setParams(convertParamsToArr(newParams))
|
setRequestData({ ...requestDataModel, params })
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [debouncedPath])
|
}, [debouncedPath])
|
||||||
|
|
||||||
const onModalClose = () => {
|
const onModalClose = () => {
|
||||||
@@ -114,33 +130,28 @@ const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose
|
|||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetModel = useCallback(() => {
|
const resetModal = useCallback((requestDataModel: IReplayRequestData, hostPortInputVal, pathVal) => {
|
||||||
setMethod(request?.method?.toLowerCase() as string)
|
setRequestData(requestDataModel)
|
||||||
setHostPortInput(`${entryData.base.proto.name}://${getHostUrl()}:${entryData.data.dst.port}`)
|
setHostPortInput(hostPortInputVal)
|
||||||
setPathInput(request.path);
|
setPathInput(addParamsToUrl(pathVal, requestDataModel.params));
|
||||||
setResponse(null);
|
setResponse(null);
|
||||||
setPostData(request?.postData?.text || JSON.stringify(request?.postData?.params));
|
setRequestExpanded(true);
|
||||||
setParams(convertParamsToArr(request?.queryString || {}))
|
}, [addParamsToUrl])
|
||||||
setHeaders(convertParamsToArr(request?.headers || {}))
|
|
||||||
setRequestExpanded(true)
|
|
||||||
}, [entryData.base.proto.name, entryData.data.dst.port, getHostUrl, request?.headers, request?.method, request.path, request?.postData?.params, request?.postData?.text, request?.queryString])
|
|
||||||
|
|
||||||
const onRefreshRequest = useCallback((event) => {
|
const onRefreshRequest = useCallback((event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
resetModel()
|
const hostPortInputVal = getHostPortVal();
|
||||||
}, [resetModel])
|
resetModal(getInitialRequestData(), hostPortInputVal, request.path);
|
||||||
|
}, [getHostPortVal, getInitialRequestData, request.path, resetModal])
|
||||||
|
|
||||||
|
|
||||||
const sendRequest = useCallback(async () => {
|
const sendRequest = useCallback(async () => {
|
||||||
setResponse(null)
|
setResponse(null)
|
||||||
const headersData = headers.reduce((prev, corrent) => {
|
const headersData = convertArrToKeyValueObject(requestDataModel.headers)
|
||||||
prev[corrent.key] = corrent.value
|
|
||||||
return prev
|
|
||||||
}, {})
|
|
||||||
const buildUrl = `${hostPortInput}${pathInput}`
|
|
||||||
const requestData = { url: buildUrl, headers: headersData, data: postData, method }
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
const requestData = { url: `${hostPortInput}${pathInput}`, headers: headersData, data: requestDataModel.postData, method: requestDataModel.method }
|
||||||
const response = await trafficViewerApi.replayRequest(requestData)
|
const response = await trafficViewerApi.replayRequest(requestData)
|
||||||
setResponse(response?.data?.representation)
|
setResponse(response?.data?.representation)
|
||||||
if (response.errorMessage) {
|
if (response.errorMessage) {
|
||||||
@@ -150,7 +161,6 @@ const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose
|
|||||||
setRequestExpanded(false)
|
setRequestExpanded(false)
|
||||||
setResponseExpanded(true)
|
setResponseExpanded(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setRequestExpanded(true)
|
setRequestExpanded(true)
|
||||||
toast.error("Error occurred while fetching response", { containerId: TOAST_CONTAINER_ID });
|
toast.error("Error occurred while fetching response", { containerId: TOAST_CONTAINER_ID });
|
||||||
@@ -159,27 +169,37 @@ const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose
|
|||||||
finally {
|
finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
}, [hostPortInput, pathInput, requestDataModel.headers, requestDataModel.method, requestDataModel.postData, trafficViewerApi])
|
||||||
|
|
||||||
}, [headers, hostPortInput, method, pathInput, postData, trafficViewerApi])
|
const onDownloadRequest = useCallback((e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
const date = Utils.getNow()
|
||||||
|
Utils.exportToJson(requestDataModel, `${getHostUrl()} - ${date}`)
|
||||||
|
}, [getHostUrl, requestDataModel])
|
||||||
|
|
||||||
|
const onLoadingComplete = useCallback((fileContent: FileContent) => {
|
||||||
|
const requestData = JSON.parse(fileContent.content) as IReplayRequestData
|
||||||
|
resetModal(requestData, requestData.hostPort, requestData.path)
|
||||||
|
}, [resetModal])
|
||||||
|
|
||||||
let innerComponent
|
let innerComponent
|
||||||
switch (currentTab) {
|
switch (currentTab) {
|
||||||
case RequestTabs.Params:
|
case RequestTabs.Params:
|
||||||
innerComponent = <div className={styles.keyValueContainer}><KeyValueTable data={params} onDataChange={onParamsChange} key={"params"} valuePlaceholder="New Param Value" keyPlaceholder="New param Key" /></div>
|
innerComponent = <div className={styles.keyValueContainer}><KeyValueTable data={requestDataModel.params} onDataChange={onParamsChange} key={"params"} valuePlaceholder="New Param Value" keyPlaceholder="New param Key" /></div>
|
||||||
break;
|
break;
|
||||||
case RequestTabs.Headers:
|
case RequestTabs.Headers:
|
||||||
innerComponent = <Fragment>
|
innerComponent = <Fragment>
|
||||||
<div className={styles.keyValueContainer}><KeyValueTable data={headers} onDataChange={(heaedrs) => setHeaders(heaedrs)} key={"Header"} valuePlaceholder="New Headers Value" keyPlaceholder="New Headers Key" />
|
<div className={styles.keyValueContainer}><KeyValueTable data={requestDataModel.headers} onDataChange={(headers) => setRequestData({ ...requestDataModel, headers: headers })} key={"Header"} valuePlaceholder="New Headers Value" keyPlaceholder="New Headers Key" />
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.note}><b>* </b> X-Mizu Header added to reuqests</span>
|
<span className={styles.note}><b>* </b> X-Mizu Header added to requests</span>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
break;
|
break;
|
||||||
case RequestTabs.Body:
|
case RequestTabs.Body:
|
||||||
const formatedCode = formatRequestWithOutError(postData || "", request?.postData?.mimeType)
|
const formattedCode = formatRequestWithOutError(requestDataModel.postData || "", request?.postData?.mimeType)
|
||||||
innerComponent = <div className={styles.codeEditor}>
|
innerComponent = <div className={styles.codeEditor}>
|
||||||
<CodeEditor language={request?.postData?.mimeType.split("/")[1]}
|
<CodeEditor language={request?.postData?.mimeType.split("/")[1]}
|
||||||
code={Utils.isJson(formatedCode) ? JSON.stringify(JSON.parse(formatedCode || "{}"), null, 2) : formatedCode}
|
code={Utils.isJson(formattedCode) ? JSON.stringify(JSON.parse(formattedCode || "{}"), null, 2) : formattedCode}
|
||||||
onChange={setPostData} />
|
onChange={(postData) => setRequestData({ ...requestDataModel, postData })} />
|
||||||
</div>
|
</div>
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -204,17 +224,43 @@ const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose
|
|||||||
<div className={styles.headerContainer}>
|
<div className={styles.headerContainer}>
|
||||||
<div className={styles.headerSection}>
|
<div className={styles.headerSection}>
|
||||||
<span className={styles.title}>Replay Request</span>
|
<span className={styles.title}>Replay Request</span>
|
||||||
|
<Button style={{ marginLeft: "2%", textTransform: 'unset' }}
|
||||||
|
startIcon={<img src={refreshImg} className="custom" alt="Refresh Request"></img>}
|
||||||
|
size="medium"
|
||||||
|
variant="contained"
|
||||||
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||||
|
onClick={onRefreshRequest}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
<Button style={{ marginLeft: "2%", textTransform: 'unset' }}
|
||||||
|
startIcon={<DownloadIcon className={`custom ${styles.icon}`} />}
|
||||||
|
size="medium"
|
||||||
|
variant="contained"
|
||||||
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||||
|
onClick={onDownloadRequest}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
<FilePicker onLoadingComplete={onLoadingComplete}
|
||||||
|
elem={<Button style={{ marginLeft: "2%", textTransform: 'unset' }}
|
||||||
|
startIcon={<UploadIcon className={`custom ${styles.icon}`} />}
|
||||||
|
size="medium"
|
||||||
|
variant="contained"
|
||||||
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}>
|
||||||
|
Upload
|
||||||
|
</Button>}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.modalContainer}>
|
<div className={styles.modalContainer}>
|
||||||
<Accordion TransitionProps={{ unmountOnExit: true }} expanded={requestExpanded} onChange={() => setRequestExpanded(!requestExpanded)}>
|
<Accordion TransitionProps={{ unmountOnExit: true }} expanded={requestExpanded} onChange={() => setRequestExpanded(!requestExpanded)}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="response-content">
|
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="response-content">
|
||||||
<span className={styles.sectionHeader}>REQUEST</span>
|
<span className={styles.sectionHeader}>REQUEST</span>
|
||||||
<img src={refreshImg} style={{ marginLeft: "10px" }} title="Refresh Reuqest" alt="Refresh Reuqest" onClick={onRefreshRequest} />
|
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<div className={styles.path}>
|
<div className={styles.path}>
|
||||||
<select className={styles.select} value={method} onChange={(e) => setMethod(e.target.value)}>
|
<select className={styles.select} value={requestDataModel.method} onChange={(e) => setRequestData({ ...requestDataModel, method: e.target.value })}>
|
||||||
{HTTP_METHODS.map(method => <option value={method} key={method}>{method}</option>)}
|
{HTTP_METHODS.map(method => <option value={method} key={method}>{method}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<input placeholder="Host:Port" value={hostPortInput} onChange={(event) => setHostPortInput(event.target.value)} className={`${commonClasses.textField} ${styles.hostPort}`} />
|
<input placeholder="Host:Port" value={hostPortInput} onChange={(event) => setHostPortInput(event.target.value)} className={`${commonClasses.textField} ${styles.hostPort}`} />
|
||||||
@@ -246,7 +292,7 @@ const ReplayRequestModal: React.FC<ReplayRequestModalProps> = ({ isOpen, onClose
|
|||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
</Fade>
|
</Fade>
|
||||||
</Modal>
|
</Modal >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export interface KeyValuePair {
|
||||||
|
key: string;
|
||||||
|
value: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReplayRequestData {
|
||||||
|
method: string;
|
||||||
|
hostPort: string;
|
||||||
|
path: string;
|
||||||
|
postData: string;
|
||||||
|
headers: KeyValuePair[]
|
||||||
|
params: KeyValuePair[]
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ $modalMargin-from-edge : 35px
|
|||||||
color: $blue-gray
|
color: $blue-gray
|
||||||
font-weight: 600
|
font-weight: 600
|
||||||
margin-right: 35px
|
margin-right: 35px
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
.graphSection
|
.graphSection
|
||||||
flex: 85%
|
flex: 85%
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
|||||||
})
|
})
|
||||||
protocolsBarsData.push(newProtocolObj);
|
protocolsBarsData.push(newProtocolObj);
|
||||||
})
|
})
|
||||||
const uniqueObjArray = Utils.creatUniqueObjArrayByProp(prtcNames, "name")
|
const uniqueObjArray = Utils.createUniqueObjArrayByProp(prtcNames, "name")
|
||||||
setProtocolStats(protocolsBarsData);
|
setProtocolStats(protocolsBarsData);
|
||||||
setProtocolsNamesAndColors(uniqueObjArray);
|
setProtocolsNamesAndColors(uniqueObjArray);
|
||||||
}, [data, timeLineBarChartMode])
|
}, [data, timeLineBarChartMode])
|
||||||
@@ -57,7 +57,7 @@ export const TimelineBarChart: React.FC<TimelineBarChartProps> = ({ timeLineBarC
|
|||||||
})
|
})
|
||||||
protocolsMethods.push(newMethodObj);
|
protocolsMethods.push(newMethodObj);
|
||||||
})
|
})
|
||||||
const uniqueObjArray = Utils.creatUniqueObjArrayByProp(protocolsMethodsNamesAndColors, "name")
|
const uniqueObjArray = Utils.createUniqueObjArrayByProp(protocolsMethodsNamesAndColors, "name")
|
||||||
setMethodsNamesAndColors(uniqueObjArray);
|
setMethodsNamesAndColors(uniqueObjArray);
|
||||||
setMethodsStats(protocolsMethods);
|
setMethodsStats(protocolsMethods);
|
||||||
}, [data, timeLineBarChartMode, selectedProtocol])
|
}, [data, timeLineBarChartMode, selectedProtocol])
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
|
import Moment from 'moment';
|
||||||
|
|
||||||
const IP_ADDRESS_REGEX = /([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})(:([0-9]{1,5}))?/
|
const IP_ADDRESS_REGEX = /([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})(:([0-9]{1,5}))?/
|
||||||
|
|
||||||
|
type JSONValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| Object
|
||||||
|
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
static isIpAddress = (address: string): boolean => IP_ADDRESS_REGEX.test(address)
|
static isIpAddress = (address: string): boolean => IP_ADDRESS_REGEX.test(address)
|
||||||
@@ -51,7 +59,7 @@ export class Utils {
|
|||||||
return [hoursAndMinutes, newDate].join(' ');
|
return [hoursAndMinutes, newDate].join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
static creatUniqueObjArrayByProp = (objArray, prop) => {
|
static createUniqueObjArrayByProp = (objArray, prop) => {
|
||||||
const map = new Map(objArray.map((item) => [item[prop], item])).values()
|
const map = new Map(objArray.map((item) => [item[prop], item])).values()
|
||||||
return Array.from(map);
|
return Array.from(map);
|
||||||
}
|
}
|
||||||
@@ -65,4 +73,24 @@ export class Utils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static downloadFile = (data: string, filename: string, fileType: string) => {
|
||||||
|
const blob = new Blob([data], { type: fileType })
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = window.URL.createObjectURL(blob);
|
||||||
|
a.download = filename;
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
static exportToJson = (data: JSONValue, name) => {
|
||||||
|
Utils.downloadFile(JSON.stringify(data), `${name}.json`, 'text/json')
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTimeFormatted = (time: Moment.MomentInput) => {
|
||||||
|
return Moment(time).utc().format('MM/DD/YYYY, h:mm:ss.SSS A')
|
||||||
|
}
|
||||||
|
|
||||||
|
static getNow = (format: string = 'MM/DD/YYYY, HH:mm:ss.SSS') => {
|
||||||
|
return Moment().format(format)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user