Merge branch 'dev' into ender_refactoring
This commit is contained in:
commit
4ac3da241e
109 changed files with 2506 additions and 583 deletions
|
|
@ -23,15 +23,15 @@ function build_service() {
|
|||
image="$1"
|
||||
echo "BUILDING $image"
|
||||
case "$image" in
|
||||
http | db | ender | heuristics)
|
||||
http | db | sink | ender | heuristics)
|
||||
echo build http
|
||||
docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --build-arg SERVICE_NAME=$image -f ./cmd/Dockerfile .
|
||||
docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --platform linux/amd64 --build-arg SERVICE_NAME=$image -f ./cmd/Dockerfile .
|
||||
[[ $PUSH_IMAGE -eq 1 ]] && {
|
||||
docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1}
|
||||
}
|
||||
;;
|
||||
*)
|
||||
docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --build-arg SERVICE_NAME=$image .
|
||||
docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --platform linux/amd64 --build-arg SERVICE_NAME=$image .
|
||||
[[ $PUSH_IMAGE -eq 1 ]] && {
|
||||
docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ COPY cmd cmd
|
|||
ARG SERVICE_NAME
|
||||
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o service -tags musl openreplay/backend/cmd/$SERVICE_NAME
|
||||
|
||||
FROM alpine
|
||||
FROM alpine AS entrypoint
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
ENV TZ=UTC \
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ package main
|
|||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"openreplay/backend/internal/config"
|
||||
"openreplay/backend/internal/router"
|
||||
"openreplay/backend/internal/server"
|
||||
|
|
@ -10,9 +14,6 @@ import (
|
|||
"openreplay/backend/pkg/db/postgres"
|
||||
"openreplay/backend/pkg/pprof"
|
||||
"openreplay/backend/pkg/queue"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
|||
|
|
@ -9,42 +9,48 @@ import (
|
|||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"openreplay/backend/pkg/env"
|
||||
"openreplay/backend/internal/assetscache"
|
||||
"openreplay/backend/internal/config/sink"
|
||||
"openreplay/backend/internal/oswriter"
|
||||
. "openreplay/backend/pkg/messages"
|
||||
"openreplay/backend/pkg/queue"
|
||||
"openreplay/backend/pkg/queue/types"
|
||||
"openreplay/backend/pkg/url/assets"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile)
|
||||
|
||||
FS_DIR := env.String("FS_DIR")
|
||||
if _, err := os.Stat(FS_DIR); os.IsNotExist(err) {
|
||||
log.Fatalf("%v doesn't exist. %v", FS_DIR, err)
|
||||
cfg := sink.New()
|
||||
|
||||
if _, err := os.Stat(cfg.FsDir); os.IsNotExist(err) {
|
||||
log.Fatalf("%v doesn't exist. %v", cfg.FsDir, err)
|
||||
}
|
||||
|
||||
writer := NewWriter(env.Uint16("FS_ULIMIT"), FS_DIR)
|
||||
writer := oswriter.NewWriter(cfg.FsUlimit, cfg.FsDir)
|
||||
|
||||
producer := queue.NewProducer()
|
||||
defer producer.Close(cfg.ProducerCloseTimeout)
|
||||
rewriter := assets.NewRewriter(cfg.AssetsOrigin)
|
||||
assetMessageHandler := assetscache.New(cfg, rewriter, producer)
|
||||
|
||||
count := 0
|
||||
|
||||
consumer := queue.NewMessageConsumer(
|
||||
env.String("GROUP_SINK"),
|
||||
cfg.GroupSink,
|
||||
[]string{
|
||||
env.String("TOPIC_RAW_WEB"),
|
||||
env.String("TOPIC_RAW_IOS"),
|
||||
cfg.TopicRawIOS,
|
||||
cfg.TopicRawWeb,
|
||||
},
|
||||
func(sessionID uint64, message Message, _ *types.Meta) {
|
||||
//typeID, err := GetMessageTypeID(value)
|
||||
// if err != nil {
|
||||
// log.Printf("Message type decoding error: %v", err)
|
||||
// return
|
||||
// }
|
||||
typeID := message.Meta().TypeID
|
||||
count++
|
||||
|
||||
typeID := message.TypeID()
|
||||
if !IsReplayerType(typeID) {
|
||||
return
|
||||
}
|
||||
|
||||
count++
|
||||
message = assetMessageHandler.ParseAssets(sessionID, message)
|
||||
|
||||
value := message.Encode()
|
||||
var data []byte
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
package assetscache
|
||||
|
||||
import (
|
||||
"openreplay/backend/internal/config"
|
||||
"openreplay/backend/internal/config/sink"
|
||||
"openreplay/backend/pkg/messages"
|
||||
"openreplay/backend/pkg/queue/types"
|
||||
"openreplay/backend/pkg/url/assets"
|
||||
)
|
||||
|
||||
type AssetsCache struct {
|
||||
cfg *config.Config
|
||||
cfg *sink.Config
|
||||
rewriter *assets.Rewriter
|
||||
producer types.Producer
|
||||
}
|
||||
|
||||
func New(cfg *config.Config, rewriter *assets.Rewriter, producer types.Producer) *AssetsCache {
|
||||
func New(cfg *sink.Config, rewriter *assets.Rewriter, producer types.Producer) *AssetsCache {
|
||||
return &AssetsCache{
|
||||
cfg: cfg,
|
||||
rewriter: rewriter,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,9 @@ type Config struct {
|
|||
HTTPTimeout time.Duration
|
||||
TopicRawWeb string
|
||||
TopicRawIOS string
|
||||
TopicCache string
|
||||
CacheAssets bool
|
||||
BeaconSizeLimit int64
|
||||
JsonSizeLimit int64
|
||||
FileSizeLimit int64
|
||||
AssetsOrigin string
|
||||
AWSRegion string
|
||||
S3BucketIOSImages string
|
||||
Postgres string
|
||||
|
|
@ -33,12 +30,9 @@ func New() *Config {
|
|||
HTTPTimeout: time.Second * 60,
|
||||
TopicRawWeb: env.String("TOPIC_RAW_WEB"),
|
||||
TopicRawIOS: env.String("TOPIC_RAW_IOS"),
|
||||
TopicCache: env.String("TOPIC_CACHE"),
|
||||
CacheAssets: env.Bool("CACHE_ASSETS"),
|
||||
BeaconSizeLimit: int64(env.Uint64("BEACON_SIZE_LIMIT")),
|
||||
JsonSizeLimit: 1e3, // 1Kb
|
||||
FileSizeLimit: 1e7, // 10Mb
|
||||
AssetsOrigin: env.String("ASSETS_ORIGIN"),
|
||||
AWSRegion: env.String("AWS_REGION"),
|
||||
S3BucketIOSImages: env.String("S3_BUCKET_IOS_IMAGES"),
|
||||
Postgres: env.String("POSTGRES_STRING"),
|
||||
|
|
|
|||
31
backend/internal/config/sink/config.go
Normal file
31
backend/internal/config/sink/config.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package sink
|
||||
|
||||
import (
|
||||
"openreplay/backend/pkg/env"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
FsDir string
|
||||
FsUlimit uint16
|
||||
GroupSink string
|
||||
TopicRawWeb string
|
||||
TopicRawIOS string
|
||||
TopicCache string
|
||||
CacheAssets bool
|
||||
AssetsOrigin string
|
||||
ProducerCloseTimeout int
|
||||
}
|
||||
|
||||
func New() *Config {
|
||||
return &Config{
|
||||
FsDir: env.String("FS_DIR"),
|
||||
FsUlimit: env.Uint16("FS_ULIMIT"),
|
||||
GroupSink: env.String("GROUP_SINK"),
|
||||
TopicRawWeb: env.String("TOPIC_RAW_WEB"),
|
||||
TopicRawIOS: env.String("TOPIC_RAW_IOS"),
|
||||
TopicCache: env.String("TOPIC_CACHE"),
|
||||
CacheAssets: env.Bool("CACHE_ASSETS"),
|
||||
AssetsOrigin: env.String("ASSETS_ORIGIN"),
|
||||
ProducerCloseTimeout: 15000,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package oswriter
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
|
@ -64,14 +64,14 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request)
|
|||
ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized"))
|
||||
return
|
||||
}
|
||||
sessionID, err := e.services.Flaker.Compose(uint64(startTime.UnixNano() / 1e6))
|
||||
sessionID, err := e.services.Flaker.Compose(uint64(startTime.UnixMilli()))
|
||||
if err != nil {
|
||||
ResponseWithError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
// TODO: if EXPIRED => send message for two sessions association
|
||||
expTime := startTime.Add(time.Duration(p.MaxSessionDuration) * time.Millisecond)
|
||||
tokenData = &token.TokenData{ID: sessionID, ExpTime: expTime.UnixNano() / 1e6}
|
||||
tokenData = &token.TokenData{ID: sessionID, ExpTime: expTime.UnixMilli()}
|
||||
|
||||
e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(&SessionStart{
|
||||
Timestamp: req.Timestamp,
|
||||
|
|
@ -117,20 +117,15 @@ func (e *Router) pushMessagesHandlerWeb(w http.ResponseWriter, r *http.Request)
|
|||
body := http.MaxBytesReader(w, r.Body, e.cfg.BeaconSizeLimit)
|
||||
defer body.Close()
|
||||
|
||||
var handledMessages bytes.Buffer
|
||||
|
||||
// Process each message in request data
|
||||
err = ReadBatchReader(body, func(msg Message) {
|
||||
msg = e.services.Assets.ParseAssets(sessionData.ID, msg)
|
||||
handledMessages.Write(msg.Encode())
|
||||
})
|
||||
bytes, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
ResponseWithError(w, http.StatusForbidden, err)
|
||||
ResponseWithError(w, http.StatusInternalServerError, err) // TODO: Split environments; send error here only on staging
|
||||
return
|
||||
}
|
||||
|
||||
// Send processed messages to queue as array of bytes
|
||||
err = e.services.Producer.Produce(e.cfg.TopicRawWeb, sessionData.ID, handledMessages.Bytes())
|
||||
// TODO: check bytes for nonsense crap
|
||||
err = e.services.Producer.Produce(e.cfg.TopicRawWeb, sessionData.ID, bytes)
|
||||
if err != nil {
|
||||
log.Printf("can't send processed messages to queue: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"openreplay/backend/internal/assetscache"
|
||||
"openreplay/backend/internal/config"
|
||||
"openreplay/backend/internal/geoip"
|
||||
"openreplay/backend/internal/uaparser"
|
||||
|
|
@ -10,13 +9,11 @@ import (
|
|||
"openreplay/backend/pkg/queue/types"
|
||||
"openreplay/backend/pkg/storage"
|
||||
"openreplay/backend/pkg/token"
|
||||
"openreplay/backend/pkg/url/assets"
|
||||
)
|
||||
|
||||
type ServicesBuilder struct {
|
||||
Database *cache.PGCache
|
||||
Producer types.Producer
|
||||
Assets *assetscache.AssetsCache
|
||||
Flaker *flakeid.Flaker
|
||||
UaParser *uaparser.UAParser
|
||||
GeoIP *geoip.GeoIP
|
||||
|
|
@ -25,11 +22,9 @@ type ServicesBuilder struct {
|
|||
}
|
||||
|
||||
func New(cfg *config.Config, producer types.Producer, pgconn *cache.PGCache) *ServicesBuilder {
|
||||
rewriter := assets.NewRewriter(cfg.AssetsOrigin)
|
||||
return &ServicesBuilder{
|
||||
Database: pgconn,
|
||||
Producer: producer,
|
||||
Assets: assetscache.New(cfg, rewriter, producer),
|
||||
Storage: storage.NewS3(cfg.AWSRegion, cfg.S3BucketIOSImages),
|
||||
Tokenizer: token.NewTokenizer(cfg.TokenSecret),
|
||||
UaParser: uaparser.NewUAParser(cfg.UAParserFile),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Auto-generated, do not edit
|
||||
package messages
|
||||
|
||||
func IsReplayerType(id uint64) bool {
|
||||
func IsReplayerType(id int) bool {
|
||||
return 0 == id || 2 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 22 == id || 37 == id || 38 == id || 39 == id || 40 == id || 41 == id || 44 == id || 45 == id || 46 == id || 47 == id || 48 == id || 49 == id || 54 == id || 55 == id || 59 == id || 69 == id || 70 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id
|
||||
}
|
||||
|
||||
func IsIOSType(id uint64) bool {
|
||||
func IsIOSType(id int) bool {
|
||||
return 107 == id || 90 == id || 91 == id || 92 == id || 93 == id || 94 == id || 95 == id || 96 == id || 97 == id || 98 == id || 99 == id || 100 == id || 101 == id || 102 == id || 103 == id || 104 == id || 105 == id || 110 == id || 111 == id
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@ package messages
|
|||
func transformDeprecated(msg Message) Message {
|
||||
switch m := msg.(type) {
|
||||
case *MouseClickDepricated:
|
||||
meta := m.Meta()
|
||||
meta.TypeID = 33
|
||||
return &MouseClick{
|
||||
meta: meta,
|
||||
ID: m.ID,
|
||||
HesitationTime: m.HesitationTime,
|
||||
Label: m.Label,
|
||||
|
|
|
|||
16
backend/pkg/messages/message.go
Normal file
16
backend/pkg/messages/message.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package messages
|
||||
|
||||
type message struct {
|
||||
Timestamp int64
|
||||
Index uint64
|
||||
}
|
||||
|
||||
func (m *message) Meta() *message {
|
||||
return m
|
||||
}
|
||||
|
||||
type Message interface {
|
||||
Encode() []byte
|
||||
TypeID() int
|
||||
Meta() *message
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -14,7 +14,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
switch t {
|
||||
|
||||
case 80:
|
||||
msg := &BatchMeta{meta: &meta{TypeID: 80}}
|
||||
msg := &BatchMeta{}
|
||||
if msg.PageNo, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -27,14 +27,14 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 0:
|
||||
msg := &Timestamp{meta: &meta{TypeID: 0}}
|
||||
msg := &Timestamp{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 1:
|
||||
msg := &SessionStart{meta: &meta{TypeID: 1}}
|
||||
msg := &SessionStart{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -86,21 +86,21 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 2:
|
||||
msg := &SessionDisconnect{meta: &meta{TypeID: 2}}
|
||||
msg := &SessionDisconnect{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 3:
|
||||
msg := &SessionEnd{meta: &meta{TypeID: 3}}
|
||||
msg := &SessionEnd{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 4:
|
||||
msg := &SetPageLocation{meta: &meta{TypeID: 4}}
|
||||
msg := &SetPageLocation{}
|
||||
if msg.URL, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 5:
|
||||
msg := &SetViewportSize{meta: &meta{TypeID: 5}}
|
||||
msg := &SetViewportSize{}
|
||||
if msg.Width, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -123,7 +123,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 6:
|
||||
msg := &SetViewportScroll{meta: &meta{TypeID: 6}}
|
||||
msg := &SetViewportScroll{}
|
||||
if msg.X, err = ReadInt(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -133,12 +133,12 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 7:
|
||||
msg := &CreateDocument{meta: &meta{TypeID: 7}}
|
||||
msg := &CreateDocument{}
|
||||
|
||||
return msg, nil
|
||||
|
||||
case 8:
|
||||
msg := &CreateElementNode{meta: &meta{TypeID: 8}}
|
||||
msg := &CreateElementNode{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -157,7 +157,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 9:
|
||||
msg := &CreateTextNode{meta: &meta{TypeID: 9}}
|
||||
msg := &CreateTextNode{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 10:
|
||||
msg := &MoveNode{meta: &meta{TypeID: 10}}
|
||||
msg := &MoveNode{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -183,14 +183,14 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 11:
|
||||
msg := &RemoveNode{meta: &meta{TypeID: 11}}
|
||||
msg := &RemoveNode{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 12:
|
||||
msg := &SetNodeAttribute{meta: &meta{TypeID: 12}}
|
||||
msg := &SetNodeAttribute{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 13:
|
||||
msg := &RemoveNodeAttribute{meta: &meta{TypeID: 13}}
|
||||
msg := &RemoveNodeAttribute{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -213,7 +213,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 14:
|
||||
msg := &SetNodeData{meta: &meta{TypeID: 14}}
|
||||
msg := &SetNodeData{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -223,7 +223,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 15:
|
||||
msg := &SetCSSData{meta: &meta{TypeID: 15}}
|
||||
msg := &SetCSSData{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 16:
|
||||
msg := &SetNodeScroll{meta: &meta{TypeID: 16}}
|
||||
msg := &SetNodeScroll{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -246,7 +246,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 17:
|
||||
msg := &SetInputTarget{meta: &meta{TypeID: 17}}
|
||||
msg := &SetInputTarget{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -256,7 +256,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 18:
|
||||
msg := &SetInputValue{meta: &meta{TypeID: 18}}
|
||||
msg := &SetInputValue{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 19:
|
||||
msg := &SetInputChecked{meta: &meta{TypeID: 19}}
|
||||
msg := &SetInputChecked{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -279,7 +279,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 20:
|
||||
msg := &MouseMove{meta: &meta{TypeID: 20}}
|
||||
msg := &MouseMove{}
|
||||
if msg.X, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -289,7 +289,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 21:
|
||||
msg := &MouseClickDepricated{meta: &meta{TypeID: 21}}
|
||||
msg := &MouseClickDepricated{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -302,7 +302,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 22:
|
||||
msg := &ConsoleLog{meta: &meta{TypeID: 22}}
|
||||
msg := &ConsoleLog{}
|
||||
if msg.Level, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -312,7 +312,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 23:
|
||||
msg := &PageLoadTiming{meta: &meta{TypeID: 23}}
|
||||
msg := &PageLoadTiming{}
|
||||
if msg.RequestStart, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -343,7 +343,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 24:
|
||||
msg := &PageRenderTiming{meta: &meta{TypeID: 24}}
|
||||
msg := &PageRenderTiming{}
|
||||
if msg.SpeedIndex, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -356,7 +356,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 25:
|
||||
msg := &JSException{meta: &meta{TypeID: 25}}
|
||||
msg := &JSException{}
|
||||
if msg.Name, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 26:
|
||||
msg := &RawErrorEvent{meta: &meta{TypeID: 26}}
|
||||
msg := &RawErrorEvent{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -388,7 +388,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 27:
|
||||
msg := &RawCustomEvent{meta: &meta{TypeID: 27}}
|
||||
msg := &RawCustomEvent{}
|
||||
if msg.Name, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -398,21 +398,21 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 28:
|
||||
msg := &UserID{meta: &meta{TypeID: 28}}
|
||||
msg := &UserID{}
|
||||
if msg.ID, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 29:
|
||||
msg := &UserAnonymousID{meta: &meta{TypeID: 29}}
|
||||
msg := &UserAnonymousID{}
|
||||
if msg.ID, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 30:
|
||||
msg := &Metadata{meta: &meta{TypeID: 30}}
|
||||
msg := &Metadata{}
|
||||
if msg.Key, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -422,7 +422,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 31:
|
||||
msg := &PageEvent{meta: &meta{TypeID: 31}}
|
||||
msg := &PageEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -477,7 +477,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 32:
|
||||
msg := &InputEvent{meta: &meta{TypeID: 32}}
|
||||
msg := &InputEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -496,7 +496,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 33:
|
||||
msg := &ClickEvent{meta: &meta{TypeID: 33}}
|
||||
msg := &ClickEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -515,7 +515,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 34:
|
||||
msg := &ErrorEvent{meta: &meta{TypeID: 34}}
|
||||
msg := &ErrorEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -537,7 +537,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 35:
|
||||
msg := &ResourceEvent{meta: &meta{TypeID: 35}}
|
||||
msg := &ResourceEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -577,7 +577,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 36:
|
||||
msg := &CustomEvent{meta: &meta{TypeID: 36}}
|
||||
msg := &CustomEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -593,7 +593,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 37:
|
||||
msg := &CSSInsertRule{meta: &meta{TypeID: 37}}
|
||||
msg := &CSSInsertRule{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -606,7 +606,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 38:
|
||||
msg := &CSSDeleteRule{meta: &meta{TypeID: 38}}
|
||||
msg := &CSSDeleteRule{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -616,7 +616,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 39:
|
||||
msg := &Fetch{meta: &meta{TypeID: 39}}
|
||||
msg := &Fetch{}
|
||||
if msg.Method, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -641,7 +641,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 40:
|
||||
msg := &Profiler{meta: &meta{TypeID: 40}}
|
||||
msg := &Profiler{}
|
||||
if msg.Name, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -657,7 +657,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 41:
|
||||
msg := &OTable{meta: &meta{TypeID: 41}}
|
||||
msg := &OTable{}
|
||||
if msg.Key, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -667,14 +667,14 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 42:
|
||||
msg := &StateAction{meta: &meta{TypeID: 42}}
|
||||
msg := &StateAction{}
|
||||
if msg.Type, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 43:
|
||||
msg := &StateActionEvent{meta: &meta{TypeID: 43}}
|
||||
msg := &StateActionEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -687,7 +687,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 44:
|
||||
msg := &Redux{meta: &meta{TypeID: 44}}
|
||||
msg := &Redux{}
|
||||
if msg.Action, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -700,7 +700,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 45:
|
||||
msg := &Vuex{meta: &meta{TypeID: 45}}
|
||||
msg := &Vuex{}
|
||||
if msg.Mutation, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -710,7 +710,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 46:
|
||||
msg := &MobX{meta: &meta{TypeID: 46}}
|
||||
msg := &MobX{}
|
||||
if msg.Type, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -720,7 +720,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 47:
|
||||
msg := &NgRx{meta: &meta{TypeID: 47}}
|
||||
msg := &NgRx{}
|
||||
if msg.Action, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -733,7 +733,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 48:
|
||||
msg := &GraphQL{meta: &meta{TypeID: 48}}
|
||||
msg := &GraphQL{}
|
||||
if msg.OperationKind, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -749,7 +749,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 49:
|
||||
msg := &PerformanceTrack{meta: &meta{TypeID: 49}}
|
||||
msg := &PerformanceTrack{}
|
||||
if msg.Frames, err = ReadInt(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -765,7 +765,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 50:
|
||||
msg := &GraphQLEvent{meta: &meta{TypeID: 50}}
|
||||
msg := &GraphQLEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -787,7 +787,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 51:
|
||||
msg := &FetchEvent{meta: &meta{TypeID: 51}}
|
||||
msg := &FetchEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -815,14 +815,14 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 52:
|
||||
msg := &DOMDrop{meta: &meta{TypeID: 52}}
|
||||
msg := &DOMDrop{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 53:
|
||||
msg := &ResourceTiming{meta: &meta{TypeID: 53}}
|
||||
msg := &ResourceTiming{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -850,7 +850,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 54:
|
||||
msg := &ConnectionInformation{meta: &meta{TypeID: 54}}
|
||||
msg := &ConnectionInformation{}
|
||||
if msg.Downlink, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -860,14 +860,14 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 55:
|
||||
msg := &SetPageVisibility{meta: &meta{TypeID: 55}}
|
||||
msg := &SetPageVisibility{}
|
||||
if msg.hidden, err = ReadBoolean(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 56:
|
||||
msg := &PerformanceTrackAggr{meta: &meta{TypeID: 56}}
|
||||
msg := &PerformanceTrackAggr{}
|
||||
if msg.TimestampStart, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -913,7 +913,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 59:
|
||||
msg := &LongTask{meta: &meta{TypeID: 59}}
|
||||
msg := &LongTask{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -938,7 +938,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 60:
|
||||
msg := &SetNodeAttributeURLBased{meta: &meta{TypeID: 60}}
|
||||
msg := &SetNodeAttributeURLBased{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -954,7 +954,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 61:
|
||||
msg := &SetCSSDataURLBased{meta: &meta{TypeID: 61}}
|
||||
msg := &SetCSSDataURLBased{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -967,7 +967,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 62:
|
||||
msg := &IssueEvent{meta: &meta{TypeID: 62}}
|
||||
msg := &IssueEvent{}
|
||||
if msg.MessageID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -989,7 +989,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 63:
|
||||
msg := &TechnicalInfo{meta: &meta{TypeID: 63}}
|
||||
msg := &TechnicalInfo{}
|
||||
if msg.Type, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -999,7 +999,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 64:
|
||||
msg := &CustomIssue{meta: &meta{TypeID: 64}}
|
||||
msg := &CustomIssue{}
|
||||
if msg.Name, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1009,19 +1009,19 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 65:
|
||||
msg := &PageClose{meta: &meta{TypeID: 65}}
|
||||
msg := &PageClose{}
|
||||
|
||||
return msg, nil
|
||||
|
||||
case 66:
|
||||
msg := &AssetCache{meta: &meta{TypeID: 66}}
|
||||
msg := &AssetCache{}
|
||||
if msg.URL, err = ReadString(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 67:
|
||||
msg := &CSSInsertRuleURLBased{meta: &meta{TypeID: 67}}
|
||||
msg := &CSSInsertRuleURLBased{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1037,7 +1037,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 69:
|
||||
msg := &MouseClick{meta: &meta{TypeID: 69}}
|
||||
msg := &MouseClick{}
|
||||
if msg.ID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1053,7 +1053,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 70:
|
||||
msg := &CreateIFrameDocument{meta: &meta{TypeID: 70}}
|
||||
msg := &CreateIFrameDocument{}
|
||||
if msg.FrameID, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1063,7 +1063,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 107:
|
||||
msg := &IOSBatchMeta{meta: &meta{TypeID: 107}}
|
||||
msg := &IOSBatchMeta{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1076,7 +1076,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 90:
|
||||
msg := &IOSSessionStart{meta: &meta{TypeID: 90}}
|
||||
msg := &IOSSessionStart{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1110,14 +1110,14 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 91:
|
||||
msg := &IOSSessionEnd{meta: &meta{TypeID: 91}}
|
||||
msg := &IOSSessionEnd{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
|
||||
case 92:
|
||||
msg := &IOSMetadata{meta: &meta{TypeID: 92}}
|
||||
msg := &IOSMetadata{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1133,7 +1133,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 93:
|
||||
msg := &IOSCustomEvent{meta: &meta{TypeID: 93}}
|
||||
msg := &IOSCustomEvent{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1149,7 +1149,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 94:
|
||||
msg := &IOSUserID{meta: &meta{TypeID: 94}}
|
||||
msg := &IOSUserID{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1162,7 +1162,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 95:
|
||||
msg := &IOSUserAnonymousID{meta: &meta{TypeID: 95}}
|
||||
msg := &IOSUserAnonymousID{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1175,7 +1175,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 96:
|
||||
msg := &IOSScreenChanges{meta: &meta{TypeID: 96}}
|
||||
msg := &IOSScreenChanges{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1197,7 +1197,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 97:
|
||||
msg := &IOSCrash{meta: &meta{TypeID: 97}}
|
||||
msg := &IOSCrash{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1216,7 +1216,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 98:
|
||||
msg := &IOSScreenEnter{meta: &meta{TypeID: 98}}
|
||||
msg := &IOSScreenEnter{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1232,7 +1232,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 99:
|
||||
msg := &IOSScreenLeave{meta: &meta{TypeID: 99}}
|
||||
msg := &IOSScreenLeave{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1248,7 +1248,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 100:
|
||||
msg := &IOSClickEvent{meta: &meta{TypeID: 100}}
|
||||
msg := &IOSClickEvent{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1267,7 +1267,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 101:
|
||||
msg := &IOSInputEvent{meta: &meta{TypeID: 101}}
|
||||
msg := &IOSInputEvent{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1286,7 +1286,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 102:
|
||||
msg := &IOSPerformanceEvent{meta: &meta{TypeID: 102}}
|
||||
msg := &IOSPerformanceEvent{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1302,7 +1302,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 103:
|
||||
msg := &IOSLog{meta: &meta{TypeID: 103}}
|
||||
msg := &IOSLog{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1318,7 +1318,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 104:
|
||||
msg := &IOSInternalError{meta: &meta{TypeID: 104}}
|
||||
msg := &IOSInternalError{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1331,7 +1331,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 105:
|
||||
msg := &IOSNetworkCall{meta: &meta{TypeID: 105}}
|
||||
msg := &IOSNetworkCall{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1362,7 +1362,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 110:
|
||||
msg := &IOSPerformanceAggregated{meta: &meta{TypeID: 110}}
|
||||
msg := &IOSPerformanceAggregated{}
|
||||
if msg.TimestampStart, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1408,7 +1408,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
|
|||
return msg, nil
|
||||
|
||||
case 111:
|
||||
msg := &IOSIssueEvent{meta: &meta{TypeID: 111}}
|
||||
msg := &IOSIssueEvent{}
|
||||
if msg.Timestamp, err = ReadUint(reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
0
backend/services/sink/build_hack
Normal file
0
backend/services/sink/build_hack
Normal file
|
|
@ -24,7 +24,8 @@ const siteIdRequiredPaths = [
|
|||
'/heatmaps',
|
||||
'/custom_metrics',
|
||||
'/dashboards',
|
||||
'/metrics'
|
||||
'/metrics',
|
||||
'/trails',
|
||||
// '/custom_metrics/sessions',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import { JSONTree } from 'UI';
|
||||
import { checkForRecent } from 'App/date';
|
||||
|
||||
interface Props {
|
||||
audit: any;
|
||||
}
|
||||
function AuditDetailModal(props: Props) {
|
||||
const { audit } = props;
|
||||
// const jsonResponse = typeof audit.payload === 'string' ? JSON.parse(audit.payload) : audit.payload;
|
||||
// console.log('jsonResponse', jsonResponse)
|
||||
|
||||
return (
|
||||
<div style={{ width: '500px' }} className="bg-white h-screen overflow-y-auto">
|
||||
<h1 className="text-2xl p-4">Audit Details</h1>
|
||||
<div className="p-4">
|
||||
<h5 className="mb-2">{ 'URL'}</h5>
|
||||
<div className="color-gray-darkest p-2 bg-gray-lightest rounded">{ audit.endPoint }</div>
|
||||
|
||||
<div className="grid grid-cols-2 my-6">
|
||||
<div className="">
|
||||
<div className="font-medium mb-2">Username</div>
|
||||
<div>{audit.username}</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<div className="font-medium mb-2">Created At</div>
|
||||
<div>{audit.createdAt && checkForRecent(audit.createdAt, 'LLL dd, yyyy, hh:mm a')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 my-6">
|
||||
<div className="">
|
||||
<div className="font-medium mb-2">Action</div>
|
||||
<div>{audit.action}</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<div className="font-medium mb-2">Method</div>
|
||||
<div>{audit.method}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ audit.payload && (
|
||||
<div className="my-6">
|
||||
<div className="font-medium mb-3">Payload</div>
|
||||
<JSONTree src={ audit.payload } collapsed={ false } enableClipboard />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuditDetailModal;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AuditDetailModal';
|
||||
67
frontend/app/components/Client/Audit/AuditList/AuditList.tsx
Normal file
67
frontend/app/components/Client/Audit/AuditList/AuditList.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { useModal } from 'App/components/Modal';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Loader, Pagination, NoContent } from 'UI';
|
||||
import AuditDetailModal from '../AuditDetailModal';
|
||||
import AuditListItem from '../AuditListItem';
|
||||
|
||||
interface Props {
|
||||
|
||||
}
|
||||
function AuditList(props: Props) {
|
||||
const { auditStore } = useStore();
|
||||
const loading = useObserver(() => auditStore.isLoading);
|
||||
const list = useObserver(() => auditStore.list);
|
||||
const searchQuery = useObserver(() => auditStore.searchQuery);
|
||||
const page = useObserver(() => auditStore.page);
|
||||
const order = useObserver(() => auditStore.order);
|
||||
const period = useObserver(() => auditStore.period);
|
||||
const { showModal } = useModal();
|
||||
console.log('AuditList', period.toTimestamps());
|
||||
|
||||
useEffect(() => {
|
||||
const { startTimestamp, endTimestamp } = period.toTimestamps();
|
||||
auditStore.fetchAudits({
|
||||
page: auditStore.page,
|
||||
limit: auditStore.pageSize,
|
||||
query: auditStore.searchQuery,
|
||||
order: auditStore.order,
|
||||
startDate: startTimestamp,
|
||||
endDate: endTimestamp,
|
||||
});
|
||||
}, [page, searchQuery, order, period]);
|
||||
|
||||
return useObserver(() => (
|
||||
<Loader loading={loading}>
|
||||
<NoContent show={list.length === 0} animatedIcon="empty-state">
|
||||
<div className="px-2 grid grid-cols-12 gap-4 items-center py-3 font-medium">
|
||||
<div className="col-span-5">Name</div>
|
||||
<div className="col-span-4">Status</div>
|
||||
<div className="col-span-3">Time</div>
|
||||
</div>
|
||||
|
||||
{list.map((item, index) => (
|
||||
<div className="px-2 border-t hover:bg-active-blue" key={index}>
|
||||
<AuditListItem
|
||||
audit={item}
|
||||
onShowDetails={() => showModal(<AuditDetailModal audit={item} />, { right: true })}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="w-full flex items-center justify-center py-10">
|
||||
<Pagination
|
||||
page={auditStore.page}
|
||||
totalPages={Math.ceil(auditStore.total / auditStore.pageSize)}
|
||||
onPageChange={(page) => auditStore.updateKey('page', page)}
|
||||
limit={auditStore.pageSize}
|
||||
debounceRequest={200}
|
||||
/>
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
));
|
||||
}
|
||||
|
||||
export default AuditList;
|
||||
1
frontend/app/components/Client/Audit/AuditList/index.ts
Normal file
1
frontend/app/components/Client/Audit/AuditList/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AuditList'
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import { checkForRecent } from 'App/date';
|
||||
|
||||
interface Props {
|
||||
audit: any;
|
||||
onShowDetails: () => void;
|
||||
}
|
||||
function AuditListItem(props: Props) {
|
||||
const { audit, onShowDetails } = props;
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-4 items-center py-3">
|
||||
<div className="col-span-5">{audit.username}</div>
|
||||
<div className="col-span-4 link cursor-pointer select-none" onClick={onShowDetails}>{audit.action}</div>
|
||||
<div className="col-span-3">{audit.createdAt && checkForRecent(audit.createdAt, 'LLL dd, yyyy, hh:mm a')}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuditListItem;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AuditListItem';
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
|
||||
let debounceUpdate: any = () => {}
|
||||
interface Props {
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
function AuditSearchField(props: Props) {
|
||||
const { onChange } = props;
|
||||
|
||||
useEffect(() => {
|
||||
debounceUpdate = debounce((value) => onChange(value), 500);
|
||||
}, [])
|
||||
|
||||
const write = ({ target: { name, value } }) => {
|
||||
debounceUpdate(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative" style={{ width: '220px'}}>
|
||||
<Icon name="search" className="absolute top-0 bottom-0 ml-3 m-auto" size="16" />
|
||||
<input
|
||||
name="searchQuery"
|
||||
className="bg-white p-2 border border-gray-light rounded w-full pl-10"
|
||||
placeholder="Filter by Name"
|
||||
onChange={write}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuditSearchField;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AuditSearchField';
|
||||
65
frontend/app/components/Client/Audit/AuditView/AuditView.tsx
Normal file
65
frontend/app/components/Client/Audit/AuditView/AuditView.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import React from 'react';
|
||||
import { PageTitle, Icon } from 'UI';
|
||||
import AuditList from '../AuditList';
|
||||
import AuditSearchField from '../AuditSearchField';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import Select from 'Shared/Select';
|
||||
import SelectDateRange from 'Shared/SelectDateRange';
|
||||
|
||||
function AuditView(props) {
|
||||
const { auditStore } = useStore();
|
||||
const order = useObserver(() => auditStore.order);
|
||||
const total = useObserver(() => auditStore.total);
|
||||
|
||||
const exportToCsv = () => {
|
||||
auditStore.exportToCsv();
|
||||
}
|
||||
|
||||
const onChange = (data) => {
|
||||
auditStore.setDateRange(data);
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div>
|
||||
<div className="flex items-center mb-4">
|
||||
<PageTitle title={
|
||||
<div className="flex items-center">
|
||||
<span>Audit Trail</span>
|
||||
<span className="color-gray-medium ml-2">{total}</span>
|
||||
</div>
|
||||
} />
|
||||
<div className="flex items-center ml-auto">
|
||||
<div className="mx-2">
|
||||
<SelectDateRange
|
||||
period={auditStore.period}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2">
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'Newest First', value: 'desc' },
|
||||
{ label: 'Oldest First', value: 'asc' },
|
||||
]}
|
||||
defaultValue={order}
|
||||
plain
|
||||
onChange={({ value }) => auditStore.updateKey('order', value)}
|
||||
/>
|
||||
</div>
|
||||
<AuditSearchField onChange={(value) => auditStore.updateKey('searchQuery', value) }/>
|
||||
<div>
|
||||
<button className="color-teal flex items-center ml-3" onClick={exportToCsv}>
|
||||
<Icon name="grid-3x3" />
|
||||
<span className="ml-2">Export to CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AuditList />
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default AuditView;
|
||||
1
frontend/app/components/Client/Audit/AuditView/index.ts
Normal file
1
frontend/app/components/Client/Audit/AuditView/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AuditView'
|
||||
|
|
@ -7,6 +7,8 @@ import { fetchList as fetchMemberList } from 'Duck/member';
|
|||
import ProfileSettings from './ProfileSettings';
|
||||
import Integrations from './Integrations';
|
||||
import ManageUsers from './ManageUsers';
|
||||
import UserView from './Users/UsersView';
|
||||
import AuditView from './Audit/AuditView';
|
||||
import Sites from './Sites';
|
||||
import CustomFields from './CustomFields';
|
||||
import Webhooks from './Webhooks';
|
||||
|
|
@ -25,7 +27,7 @@ import Roles from './Roles';
|
|||
export default class Client extends React.PureComponent {
|
||||
constructor(props){
|
||||
super(props);
|
||||
props.fetchMemberList();
|
||||
// props.fetchMemberList();
|
||||
}
|
||||
|
||||
setTab = (tab) => {
|
||||
|
|
@ -36,12 +38,13 @@ export default class Client extends React.PureComponent {
|
|||
<Switch>
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.PROFILE) } component={ ProfileSettings } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.INTEGRATIONS) } component={ Integrations } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.MANAGE_USERS) } component={ ManageUsers } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.MANAGE_USERS) } component={ UserView } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.SITES) } component={ Sites } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.CUSTOM_FIELDS) } component={ CustomFields } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.WEBHOOKS) } component={ Webhooks } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.NOTIFICATIONS) } component={ Notifications } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.MANAGE_ROLES) } component={ Roles } />
|
||||
<Route exact strict path={ clientRoute(CLIENT_TABS.AUDIT) } component={ AuditView } />
|
||||
<Redirect to={ clientRoute(CLIENT_TABS.PROFILE) } />
|
||||
</Switch>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ class ManageUsers extends React.PureComponent {
|
|||
title="No users are available."
|
||||
size="small"
|
||||
show={ members.size === 0 }
|
||||
icon
|
||||
animatedIcon="empty-state"
|
||||
>
|
||||
<div className={ styles.list }>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -78,6 +78,17 @@ function PreferencesMenu({ activeTab, appearance, history, isEnterprise }) {
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ isEnterprise && (
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
active={ activeTab === CLIENT_TABS.AUDIT }
|
||||
title="Audit"
|
||||
iconName="list-ul"
|
||||
onClick={() => setTab(CLIENT_TABS.AUDIT) }
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
|
|
@ -95,7 +106,7 @@ function PreferencesMenu({ activeTab, appearance, history, isEnterprise }) {
|
|||
iconName="bell"
|
||||
onClick={() => setTab(CLIENT_TABS.NOTIFICATIONS) }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { Input, Button, Label } from 'UI';
|
||||
import { save, edit, update , fetchList } from 'Duck/site';
|
||||
import { Input, Button, Icon } from 'UI';
|
||||
import { save, edit, update , fetchList, remove } from 'Duck/site';
|
||||
import { pushNewSite } from 'Duck/user';
|
||||
import { setSiteId } from 'Duck/site';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import styles from './siteForm.css';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
|
||||
@connect(state => ({
|
||||
site: state.getIn([ 'site', 'instance' ]),
|
||||
|
|
@ -13,6 +14,7 @@ import styles from './siteForm.css';
|
|||
loading: state.getIn([ 'site', 'save', 'loading' ]),
|
||||
}), {
|
||||
save,
|
||||
remove,
|
||||
edit,
|
||||
update,
|
||||
pushNewSite,
|
||||
|
|
@ -52,6 +54,17 @@ export default class NewSiteForm extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
remove = async (site) => {
|
||||
if (await confirm({
|
||||
header: 'Projects',
|
||||
confirmation: `Are you sure you want to delete this Project? We won't be able to record anymore sessions.`
|
||||
})) {
|
||||
this.props.remove(site.id).then(() => {
|
||||
this.props.onClose(null)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
edit = ({ target: { name, value } }) => {
|
||||
this.setState({ existsError: false });
|
||||
this.props.edit({ [ name ]: value });
|
||||
|
|
@ -72,7 +85,7 @@ export default class NewSiteForm extends React.PureComponent {
|
|||
className={ styles.input }
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<div className="mt-6 flex justify-between">
|
||||
<Button
|
||||
primary
|
||||
type="submit"
|
||||
|
|
@ -80,7 +93,10 @@ export default class NewSiteForm extends React.PureComponent {
|
|||
loading={ loading }
|
||||
content={site.exists() ? 'Update' : 'Add'}
|
||||
/>
|
||||
</div>
|
||||
<Button type="button" plain onClick={() => this.remove(site)}>
|
||||
<Icon name="trash" size="16" />
|
||||
</Button>
|
||||
</div>
|
||||
{ this.state.existsError &&
|
||||
<div className={ styles.errorMessage }>
|
||||
{ "Site exists already. Please choose another one." }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
|
||||
let debounceUpdate: any = () => {}
|
||||
interface Props {
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
function SiteSearch(props: Props) {
|
||||
const { onChange } = props;
|
||||
|
||||
useEffect(() => {
|
||||
debounceUpdate = debounce((value) => onChange(value), 500);
|
||||
}, [])
|
||||
|
||||
const write = ({ target: { name, value } }) => {
|
||||
debounceUpdate(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative" style={{ width: '300px'}}>
|
||||
<Icon name="search" className="absolute top-0 bottom-0 ml-3 m-auto" size="16" />
|
||||
<input
|
||||
// value={query}
|
||||
name="searchQuery"
|
||||
className="bg-white p-2 border border-gray-light rounded w-full pl-10"
|
||||
placeholder="Filter by Name"
|
||||
onChange={write}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SiteSearch;
|
||||
1
frontend/app/components/Client/Sites/SiteSearch/index.ts
Normal file
1
frontend/app/components/Client/Sites/SiteSearch/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './SiteSearch';
|
||||
|
|
@ -10,6 +10,7 @@ import GDPRForm from './GDPRForm';
|
|||
import TrackingCodeModal from 'Shared/TrackingCodeModal';
|
||||
import BlockedIps from './BlockedIps';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
import SiteSearch from './SiteSearch';
|
||||
|
||||
const STATUS_MESSAGE_MAP = {
|
||||
[ RED ]: ' There seems to be an issue (please verify your installation)',
|
||||
|
|
@ -43,6 +44,7 @@ class Sites extends React.PureComponent {
|
|||
showTrackingCode: false,
|
||||
modalContent: NONE,
|
||||
detailContent: NONE,
|
||||
searchQuery: '',
|
||||
};
|
||||
|
||||
toggleBlockedIp = () => {
|
||||
|
|
@ -85,7 +87,7 @@ class Sites extends React.PureComponent {
|
|||
getModalTitle() {
|
||||
switch (this.state.modalContent) {
|
||||
case NEW_SITE_FORM:
|
||||
return 'New Project';
|
||||
return this.props.site.exists() ? 'Update Project' : 'New Project';
|
||||
case GDPR_FORM:
|
||||
return 'Project Settings';
|
||||
default:
|
||||
|
|
@ -119,6 +121,7 @@ class Sites extends React.PureComponent {
|
|||
const isAdmin = user.admin || user.superAdmin;
|
||||
const canAddSites = isAdmin && account.limits.projects && account.limits.projects.remaining !== 0;
|
||||
const canDeleteSites = sites.size > 1 && isAdmin;
|
||||
const filteredSites = sites.filter(site => site.name.toLowerCase().includes(this.state.searchQuery.toLowerCase()));
|
||||
|
||||
return (
|
||||
<Loader loading={ loading }>
|
||||
|
|
@ -159,54 +162,71 @@ class Sites extends React.PureComponent {
|
|||
position="top left"
|
||||
/>
|
||||
|
||||
<TextLink
|
||||
icon="book"
|
||||
className="ml-auto"
|
||||
href="https://docs.openreplay.com/installation"
|
||||
label="Documentation"
|
||||
/>
|
||||
<div className="flex ml-auto items-center">
|
||||
<TextLink
|
||||
icon="book"
|
||||
className="mr-4"
|
||||
href="https://docs.openreplay.com/installation"
|
||||
label="Documentation"
|
||||
/>
|
||||
<SiteSearch onChange={(value) => this.setState({ searchQuery: value })} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={ stl.list }>
|
||||
<div className="grid grid-cols-12 gap-2 w-full items-center border-b px-2 py-3 font-medium">
|
||||
<div className="col-span-4">Name</div>
|
||||
<div className="col-span-4">Key</div>
|
||||
<div className="col-span-4"></div>
|
||||
|
||||
</div>
|
||||
{
|
||||
sites.map(_site => (
|
||||
<div key={ _site.key } className={ stl.site } data-inactive={ _site.status === RED }>
|
||||
<div className="flex items-center">
|
||||
<Popup
|
||||
trigger={
|
||||
<div style={ { width: '10px' } }>
|
||||
<Icon name="circle" size="10" color={ STATUS_COLOR_MAP[ _site.status ] } />
|
||||
filteredSites.map(_site => (
|
||||
// <div key={ _site.key } data-inactive={ _site.status === RED }>
|
||||
<div key={ _site.key } className="grid grid-cols-12 gap-2 w-full group hover:bg-active-blue items-center border-b px-2 py-3">
|
||||
<div className="col-span-4">
|
||||
<div className="flex items-center">
|
||||
<Popup
|
||||
trigger={
|
||||
<div style={ { width: '10px' } }>
|
||||
<Icon name="circle" size="10" color={ STATUS_COLOR_MAP[ _site.status ] } />
|
||||
</div>
|
||||
}
|
||||
content={ STATUS_MESSAGE_MAP[ _site.status ] }
|
||||
inverted
|
||||
position="top center"
|
||||
/>
|
||||
<span className="ml-2">{ _site.host }</span>
|
||||
</div>
|
||||
}
|
||||
content={ STATUS_MESSAGE_MAP[ _site.status ] }
|
||||
inverted
|
||||
position="top center"
|
||||
/>
|
||||
<div className="ml-3 flex items-center">
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
<span className="px-2 py-1 bg-gray-lightest rounded border text-sm">{_site.projectKey}</span>
|
||||
</div>
|
||||
{/* <div className="ml-3 flex items-center">
|
||||
<div>{ _site.host }</div>
|
||||
<div className={ stl.label}>{_site.projectKey}</div>
|
||||
</div> */}
|
||||
<div className="col-span-4 justify-self-end flex items-center invisible group-hover:visible">
|
||||
<div className="mr-4"><Button size="small" primary onClick={ () => this.showTrackingCode(_site) }>{ 'Installation' }</Button></div>
|
||||
{/* <button
|
||||
className={cn('mx-3', {'hidden' : !canDeleteSites})}
|
||||
disabled={ !canDeleteSites }
|
||||
onClick={ () => canDeleteSites && this.remove(_site) }
|
||||
>
|
||||
<Icon name="trash" size="16" color="teal" />
|
||||
</button> */}
|
||||
<button
|
||||
className={cn('mx-3', {'hidden' : !isAdmin})}
|
||||
disabled={ !isAdmin }
|
||||
onClick={ () => isAdmin && this.edit(_site) }
|
||||
data-clickable
|
||||
>
|
||||
<Icon name="edit" size="16" color="teal"/>
|
||||
</button>
|
||||
{/* <button disabled={ !isAdmin } onClick={ () => this.showGDPRForm(_site) } ><Icon name="cog" size="16" color="teal" /></button> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className={ stl.actions }>
|
||||
<button
|
||||
className={cn({'hidden' : !canDeleteSites})}
|
||||
disabled={ !canDeleteSites }
|
||||
onClick={ () => canDeleteSites && this.remove(_site) }
|
||||
>
|
||||
<Icon name="trash" size="16" color="teal" />
|
||||
</button>
|
||||
<button
|
||||
className={cn({'hidden' : !isAdmin})}
|
||||
disabled={ !isAdmin }
|
||||
onClick={ () => isAdmin && this.edit(_site) }
|
||||
data-clickable
|
||||
>
|
||||
<Icon name="edit" size="16" color="teal"/>
|
||||
</button>
|
||||
<div><Button size="small" outline primary onClick={ () => this.showTrackingCode(_site) }>{ 'Tracking Code' }</Button></div>
|
||||
{/* <button disabled={ !isAdmin } onClick={ () => this.showGDPRForm(_site) } ><Icon name="cog" size="16" color="teal" /></button> */}
|
||||
</div>
|
||||
</div>
|
||||
// </div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
80
frontend/app/components/Client/Users/UsersView.tsx
Normal file
80
frontend/app/components/Client/Users/UsersView.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import UserList from './components/UserList';
|
||||
import { PageTitle, Popup, IconButton } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import UserSearch from './components/UserSearch';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import UserForm from './components/UserForm';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const PERMISSION_WARNING = 'You don’t have the permissions to perform this action.';
|
||||
const LIMIT_WARNING = 'You have reached users limit.';
|
||||
interface Props {
|
||||
account: any;
|
||||
isEnterprise: boolean;
|
||||
limits: any;
|
||||
}
|
||||
function UsersView(props: Props) {
|
||||
const { account, limits, isEnterprise } = props;
|
||||
const { userStore, roleStore } = useStore();
|
||||
const userCount = useObserver(() => userStore.list.length);
|
||||
const roles = useObserver(() => roleStore.list);
|
||||
const { showModal } = useModal();
|
||||
|
||||
const reachedLimit = (limits.remaining + userStore.modifiedCount) <= 0;
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
|
||||
const editHandler = (user = null) => {
|
||||
userStore.initUser(user).then(() => {
|
||||
showModal(<UserForm />, {});
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (roles.length === 0 && isEnterprise) {
|
||||
roleStore.fetchRoles();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<PageTitle
|
||||
title={<div>Team <span className="color-gray-medium">{userCount}</span></div>}
|
||||
actionButton={(
|
||||
<Popup
|
||||
trigger={
|
||||
<div>
|
||||
<IconButton
|
||||
id="add-button"
|
||||
disabled={ reachedLimit || !isAdmin }
|
||||
circle
|
||||
icon="plus"
|
||||
outline
|
||||
className="ml-3"
|
||||
onClick={ () => editHandler(null) }
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
content={ `${ !isAdmin ? PERMISSION_WARNING : (reachedLimit ? LIMIT_WARNING : 'Add team member') }` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top left"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<UserSearch />
|
||||
</div>
|
||||
</div>
|
||||
<UserList isEnterprise={isEnterprise} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
account: state.getIn([ 'user', 'account' ]),
|
||||
isEnterprise: state.getIn([ 'user', 'client', 'edition' ]) === 'ee',
|
||||
limits: state.getIn([ 'user', 'account', 'limits', 'teamMember' ]),
|
||||
}))(UsersView);
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
import React from 'react';
|
||||
import { Input, CopyButton, Button, Icon } from 'UI'
|
||||
import cn from 'classnames';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import Select from 'Shared/Select';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
interface Props {
|
||||
isSmtp?: boolean;
|
||||
isEnterprise?: boolean;
|
||||
}
|
||||
function UserForm(props: Props) {
|
||||
const { isSmtp = false, isEnterprise = false } = props;
|
||||
const { hideModal } = useModal();
|
||||
const { userStore, roleStore } = useStore();
|
||||
const isSaving = useObserver(() => userStore.saving);
|
||||
const user: any = useObserver(() => userStore.instance);
|
||||
const roles = useObserver(() => roleStore.list.filter(r => r.isProtected ? user.isSuperAdmin : true).map(r => ({ label: r.name, value: r.roleId })));
|
||||
|
||||
const onChangeCheckbox = (e: any) => {
|
||||
user.updateKey('isAdmin', !user.isAdmin);
|
||||
}
|
||||
|
||||
const onSave = () => {
|
||||
userStore.saveUser(user).then(() => {
|
||||
hideModal();
|
||||
});
|
||||
}
|
||||
|
||||
const write = ({ target: { name, value } }) => {
|
||||
user.updateKey(name, value);
|
||||
}
|
||||
|
||||
const deleteHandler = async () => {
|
||||
if (await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: `Are you sure you want to permanently delete this user?`
|
||||
})) {
|
||||
userStore.deleteUser(user.userId).then(() => {
|
||||
hideModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="bg-white h-screen p-6" style={{ width: '400px'}}>
|
||||
<div className="">
|
||||
<h1 className="text-2xl mb-4">{`${user.exists() ? 'Update' : 'Invite'} User`}</h1>
|
||||
</div>
|
||||
<form onSubmit={ onSave } >
|
||||
<div className="form-group">
|
||||
<label>{ 'Full Name' }</label>
|
||||
<Input
|
||||
name="name"
|
||||
autoFocus
|
||||
value={ user.name }
|
||||
onChange={ write }
|
||||
className="w-full"
|
||||
id="name-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>{ 'Email Address' }</label>
|
||||
<Input
|
||||
disabled={user.exists()}
|
||||
name="email"
|
||||
value={ user.email }
|
||||
onChange={ write }
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
{ !isSmtp &&
|
||||
<div className={cn("mb-4 p-2 bg-yellow rounded")}>
|
||||
SMTP is not configured (see <a className="link" href="https://docs.openreplay.com/configuration/configure-smtp" target="_blank">here</a> how to set it up). You can still add new users, but you’d have to manually copy then send them the invitation link.
|
||||
</div>
|
||||
}
|
||||
<div className="form-group">
|
||||
<label className="flex items-start cursor-pointer">
|
||||
<input
|
||||
name="admin"
|
||||
type="checkbox"
|
||||
checked={ !!user.isAdmin || !!user.isSuperAdmin }
|
||||
onChange={ onChangeCheckbox }
|
||||
disabled={user.isSuperAdmin}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="ml-2 select-none">
|
||||
<span>Admin Privileges</span>
|
||||
<div className="text-sm color-gray-medium -mt-1">{ 'Can manage Projects and team members.' }</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{ !isEnterprise && (
|
||||
<div className="form-group">
|
||||
<label htmlFor="role">{ 'Role' }</label>
|
||||
<Select
|
||||
placeholder="Selct Role"
|
||||
selection
|
||||
options={ roles }
|
||||
name="roleId"
|
||||
defaultValue={ user.roleId }
|
||||
onChange={({ value }) => user.updateKey('roleId', value)}
|
||||
className="block"
|
||||
isDisabled={user.isSuperAdmin}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center mr-auto">
|
||||
<Button
|
||||
onClick={ onSave }
|
||||
disabled={ !user.valid() || isSaving }
|
||||
loading={ isSaving }
|
||||
primary
|
||||
marginRight
|
||||
>
|
||||
{ user.exists() ? 'Update' : 'Invite' }
|
||||
</Button>
|
||||
<Button
|
||||
data-hidden={ !user.exists() }
|
||||
onClick={ hideModal }
|
||||
outline
|
||||
>
|
||||
{ 'Cancel' }
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
data-hidden={ !user.exists() }
|
||||
onClick={ deleteHandler }
|
||||
>
|
||||
<Icon name="trash" size="16" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ !user.isJoined && user.invitationLink &&
|
||||
<CopyButton
|
||||
content={user.invitationLink}
|
||||
className="link mt-4"
|
||||
btnText="Copy invite link"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default UserForm;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './UserForm';
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import React, { useEffect } from 'react';
|
||||
import UserListItem from '../UserListItem';
|
||||
import { sliceListPerPage, getRE } from 'App/utils';
|
||||
import { Pagination, NoContent, Loader } from 'UI';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import UserForm from '../UserForm';
|
||||
|
||||
interface Props {
|
||||
isEnterprise?: boolean;
|
||||
}
|
||||
function UserList(props: Props) {
|
||||
const { isEnterprise = false } = props;
|
||||
const { userStore } = useStore();
|
||||
const loading = useObserver(() => userStore.loading);
|
||||
const users = useObserver(() => userStore.list);
|
||||
const searchQuery = useObserver(() => userStore.searchQuery);
|
||||
const { showModal } = useModal();
|
||||
|
||||
const filterList = (list) => {
|
||||
const filterRE = getRE(searchQuery, 'i');
|
||||
let _list = list.filter(w => {
|
||||
return filterRE.test(w.email) || filterRE.test(w.roleName);
|
||||
});
|
||||
return _list
|
||||
}
|
||||
|
||||
const list: any = searchQuery !== '' ? filterList(users) : users;
|
||||
const length = list.length;
|
||||
|
||||
useEffect(() => {
|
||||
userStore.fetchUsers();
|
||||
}, []);
|
||||
|
||||
const editHandler = (user) => {
|
||||
userStore.initUser(user).then(() => {
|
||||
showModal(<UserForm />, { });
|
||||
});
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<Loader loading={loading}>
|
||||
<NoContent show={!loading && length === 0} animatedIcon="empty-state">
|
||||
<div className="mt-3 rounded bg-white">
|
||||
<div className="grid grid-cols-12 p-3 border-b font-medium">
|
||||
<div className="col-span-5">Name</div>
|
||||
<div className="col-span-3">Role</div>
|
||||
<div className="col-span-2">Created On</div>
|
||||
<div className="col-span-2"></div>
|
||||
</div>
|
||||
|
||||
{sliceListPerPage(list, userStore.page - 1, userStore.pageSize).map((user: any) => (
|
||||
<div key={user.id} className="">
|
||||
<UserListItem
|
||||
user={user}
|
||||
editHandler={() => editHandler(user)}
|
||||
generateInvite={() => userStore.generateInviteCode(user.userId)}
|
||||
copyInviteCode={() => userStore.copyInviteCode(user.userId)}
|
||||
// isEnterprise={isEnterprise}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="w-full flex items-center justify-center py-10">
|
||||
<Pagination
|
||||
page={userStore.page}
|
||||
totalPages={Math.ceil(length / userStore.pageSize)}
|
||||
onPageChange={(page) => userStore.updateKey('page', page)}
|
||||
limit={userStore.pageSize}
|
||||
debounceRequest={100}
|
||||
/>
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
));
|
||||
}
|
||||
|
||||
export default UserList;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './UserList'
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
//@ts-nocheck
|
||||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { checkForRecent } from 'App/date';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
|
||||
|
||||
const AdminPrivilegeLabel = ({ user }) => {
|
||||
return (
|
||||
<>
|
||||
{user.isAdmin && <span className="px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">Admin</span>}
|
||||
{user.isSuperAdmin && <span className="px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">Owner</span>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
interface Props {
|
||||
user: any;
|
||||
editHandler?: any;
|
||||
generateInvite?: any;
|
||||
copyInviteCode?: any;
|
||||
isEnterprise?: boolean;
|
||||
}
|
||||
function UserListItem(props: Props) {
|
||||
const {
|
||||
user,
|
||||
editHandler = () => {},
|
||||
generateInvite = () => {},
|
||||
copyInviteCode = () => {},
|
||||
isEnterprise = false,
|
||||
} = props;
|
||||
return (
|
||||
<div className="grid grid-cols-12 p-3 py-4 border-b items-center select-none hover:bg-active-blue group">
|
||||
<div className="col-span-5">
|
||||
<span className="mr-2">{user.name}</span>
|
||||
{isEnterprise && <AdminPrivilegeLabel user={user} />}
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
{!isEnterprise && <AdminPrivilegeLabel user={user} />}
|
||||
{isEnterprise && (
|
||||
<span className="px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">
|
||||
{user.roleName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<span>{user.createdAt && checkForRecent(user.createdAt, 'LLL dd, yyyy, hh:mm a')}</span>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2 justify-self-end invisible group-hover:visible">
|
||||
<div className="grid grid-cols-2 gap-3 items-center justify-end">
|
||||
{!user.isJoined && user.invitationLink ? (
|
||||
<Tooltip
|
||||
delay={500}
|
||||
arrow
|
||||
title="Copy Invite Code"
|
||||
hideOnClick={true}
|
||||
>
|
||||
<button className='' onClick={copyInviteCode}>
|
||||
<Icon name="link-45deg" size="16" color="teal"/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
) : <div/>}
|
||||
{!user.isJoined && user.isExpiredInvite && (
|
||||
<Tooltip
|
||||
delay={500}
|
||||
arrow
|
||||
title="Generate Invite"
|
||||
hideOnClick={true}
|
||||
>
|
||||
<button className='' onClick={generateInvite}>
|
||||
<Icon name="link-45deg" size="16" color="red"/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<button className='' onClick={editHandler}>
|
||||
<Icon name="pencil" color="teal" size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserListItem;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './UserListItem';
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Icon } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
|
||||
let debounceUpdate: any = () => {}
|
||||
function UserSearch(props) {
|
||||
const { userStore } = useStore();
|
||||
const [query, setQuery] = useState(userStore.searchQuery);
|
||||
|
||||
useEffect(() => {
|
||||
debounceUpdate = debounce((key, value) => userStore.updateKey(key, value), 500);
|
||||
}, [])
|
||||
|
||||
const write = ({ target: { name, value } }) => {
|
||||
setQuery(value);
|
||||
debounceUpdate(name, value);
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="relative" style={{ width: '300px'}}>
|
||||
<Icon name="search" className="absolute top-0 bottom-0 ml-3 m-auto" size="16" />
|
||||
<input
|
||||
value={query}
|
||||
name="searchQuery"
|
||||
className="bg-white p-2 border border-gray-light rounded w-full pl-10"
|
||||
placeholder="Filter by Name, Role"
|
||||
onChange={write}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default UserSearch;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './UserSearch';
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import Select, { components, DropdownIndicatorProps } from 'react-select';
|
||||
import { Icon } from 'UI';
|
||||
import colors from 'App/theme/colors';
|
||||
|
||||
interface Props {
|
||||
options: any[];
|
||||
isSearchable?: boolean;
|
||||
defaultValue?: string;
|
||||
plain?: boolean;
|
||||
components?: any;
|
||||
[x:string]: any;
|
||||
}
|
||||
export default function({ plain = false, options, isSearchable = false, defaultValue = '', ...rest }: Props) {
|
||||
export default function({ plain = false, options, isSearchable = false, components = {}, defaultValue = '', ...rest }: Props) {
|
||||
const customStyles = {
|
||||
option: (provided, state) => ({
|
||||
...provided,
|
||||
|
|
@ -17,14 +20,26 @@ export default function({ plain = false, options, isSearchable = false, defaultV
|
|||
menu: (provided, state) => ({
|
||||
...provided,
|
||||
top: 31,
|
||||
minWidth: 'fit-content',
|
||||
}),
|
||||
control: (provided) => {
|
||||
const obj = {
|
||||
...provided,
|
||||
border: 'solid thin #ddd'
|
||||
border: 'solid thin #ddd',
|
||||
cursor: 'pointer',
|
||||
}
|
||||
if (plain) {
|
||||
obj['border'] = '1px solid transparent'
|
||||
obj['&:hover'] = {
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: colors['gray-light']
|
||||
}
|
||||
obj['&:focus'] = {
|
||||
borderColor: 'transparent'
|
||||
}
|
||||
obj['&:active'] = {
|
||||
borderColor: 'transparent'
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
|
@ -39,14 +54,16 @@ export default function({ plain = false, options, isSearchable = false, defaultV
|
|||
return { ...provided, opacity, transition };
|
||||
}
|
||||
}
|
||||
const defaultSelected = defaultValue ? options.find(x => x.value === defaultValue) : options[0];
|
||||
const defaultSelected = defaultValue ? options.find(x => x.value === defaultValue) : null;
|
||||
return (
|
||||
<Select
|
||||
options={options}
|
||||
isSearchable={isSearchable}
|
||||
defaultValue={defaultSelected}
|
||||
components={{
|
||||
IndicatorSeparator: () => null
|
||||
IndicatorSeparator: () => null,
|
||||
DropdownIndicator,
|
||||
...components,
|
||||
}}
|
||||
styles={customStyles}
|
||||
theme={(theme) => ({
|
||||
|
|
@ -56,9 +73,18 @@ export default function({ plain = false, options, isSearchable = false, defaultV
|
|||
primary: '#394EFF',
|
||||
}
|
||||
})}
|
||||
blurInputOnSelect={true}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// export default Select;
|
||||
const DropdownIndicator = (
|
||||
props: DropdownIndicatorProps<true>
|
||||
) => {
|
||||
return (
|
||||
<components.DropdownIndicator {...props}>
|
||||
<Icon name="chevron-down" size="18" />
|
||||
</components.DropdownIndicator>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import { DATE_RANGE_OPTIONS, CUSTOM_RANGE } from 'App/dateRange'
|
||||
import Select from 'Shared/Select';
|
||||
import Period, { LAST_7_DAYS } from 'Types/app/period';
|
||||
import { components } from 'react-select';
|
||||
import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
|
||||
interface Props {
|
||||
period: any,
|
||||
onChange: (data: any) => void;
|
||||
}
|
||||
function SelectDateRange(props: Props) {
|
||||
const [isCustom, setIsCustom] = React.useState(false);
|
||||
const { period } = props;
|
||||
const selectedValue = DATE_RANGE_OPTIONS.find(obj => obj.value === period.rangeName)
|
||||
|
||||
const onChange = (value: any) => {
|
||||
if (value === CUSTOM_RANGE) {
|
||||
setIsCustom(true);
|
||||
} else {
|
||||
props.onChange(new Period({ rangeName: value }));
|
||||
}
|
||||
}
|
||||
|
||||
const onApplyDateRange = (value: any) => {
|
||||
props.onChange(new Period({ rangeName: CUSTOM_RANGE, start: value.start, end: value.end }));
|
||||
setIsCustom(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Select
|
||||
plain
|
||||
value={selectedValue}
|
||||
options={DATE_RANGE_OPTIONS}
|
||||
onChange={({ value }) => onChange(value)}
|
||||
components={{ SingleValue: ({ children, ...props} : any) => {
|
||||
return (
|
||||
<components.SingleValue {...props}>
|
||||
{period.rangeName === CUSTOM_RANGE ? period.rangeFormatted() : children}
|
||||
</components.SingleValue>
|
||||
)
|
||||
} }}
|
||||
period={period}
|
||||
/>
|
||||
{
|
||||
isCustom &&
|
||||
<OutsideClickDetectingDiv
|
||||
onClickOutside={() => setIsCustom(false)}
|
||||
>
|
||||
<div className="absolute top-0 mx-auto mt-10 z-40" style={{
|
||||
width: '770px',
|
||||
margin: 'auto 50vh 0',
|
||||
transform: 'translateX(-50%)'
|
||||
}}>
|
||||
<DateRangePopup
|
||||
onApply={ onApplyDateRange }
|
||||
onCancel={ () => setIsCustom(false) }
|
||||
selectedDateRange={ period.range }
|
||||
/>
|
||||
</div>
|
||||
</OutsideClickDetectingDiv>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectDateRange;
|
||||
|
||||
|
||||
1
frontend/app/components/shared/SelectDateRange/index.ts
Normal file
1
frontend/app/components/shared/SelectDateRange/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './SelectDateRange';
|
||||
|
|
@ -16,7 +16,6 @@ function DefaultTimezone(props) {
|
|||
const { settingsStore } = useStore();
|
||||
const [timezone, setTimezone] = React.useState(settingsStore.sessionSettings.timezone);
|
||||
const sessionSettings = useObserver(() => settingsStore.sessionSettings)
|
||||
console.log('timezone', timezone)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ Object.keys(DATE_RANGE_LABELS).forEach((key) => { DATE_RANGE_VALUES[ key ] = key
|
|||
export { DATE_RANGE_VALUES };
|
||||
export const dateRangeValues = Object.keys(DATE_RANGE_VALUES);
|
||||
|
||||
export const DATE_RANGE_OPTIONS = Object.keys(DATE_RANGE_LABELS).map((key) => {
|
||||
return {
|
||||
label: DATE_RANGE_LABELS[ key ],
|
||||
value: key,
|
||||
};
|
||||
});
|
||||
|
||||
export function getDateRangeFromTs(start, end) {
|
||||
return moment.range(
|
||||
moment(start),
|
||||
|
|
|
|||
|
|
@ -59,7 +59,12 @@ function reducer(state = initialState, action = {}) {
|
|||
case EDIT_OPTIONS:
|
||||
return state.mergeIn(["options"], action.instance);
|
||||
case success(FETCH):
|
||||
return state.set("instance", ErrorInfo(action.data));
|
||||
if (state.get("list").find(e => e.get("errorId") === action.id)) {
|
||||
return updateItemInList(state, { errorId: action.data.errorId, viewed: true })
|
||||
.set("instance", ErrorInfo(action.data));
|
||||
} else {
|
||||
return state.set("instance", ErrorInfo(action.data));
|
||||
}
|
||||
case success(FETCH_TRACE):
|
||||
return state.set("instanceTrace", List(action.data.trace)).set('sourcemapUploaded', action.data.sourcemapUploaded);
|
||||
case success(FETCH_LIST):
|
||||
|
|
|
|||
86
frontend/app/mstore/auditStore.ts
Normal file
86
frontend/app/mstore/auditStore.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
import { auditService } from "App/services"
|
||||
import Audit from './types/audit'
|
||||
import Period, { LAST_7_DAYS } from 'Types/app/period';
|
||||
import { toast } from 'react-toastify';
|
||||
import { exportCSVFile } from 'App/utils';
|
||||
import { formatDateTimeDefault } from 'App/date';
|
||||
import { DateTime, Duration } from 'luxon'; // TODO
|
||||
|
||||
export default class AuditStore {
|
||||
list: any[] = [];
|
||||
total: number = 0;
|
||||
page: number = 1;
|
||||
pageSize: number = 20;
|
||||
searchQuery: string = '';
|
||||
isLoading: boolean = false;
|
||||
order: string = 'desc';
|
||||
period: Period|null = Period({ rangeName: LAST_7_DAYS })
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
searchQuery: observable,
|
||||
period: observable,
|
||||
updateKey: action,
|
||||
fetchAudits: action,
|
||||
setDateRange: action,
|
||||
})
|
||||
}
|
||||
|
||||
setDateRange(data: any) {
|
||||
this['period'] = data;
|
||||
}
|
||||
|
||||
updateKey(key: string, value: any) {
|
||||
this[key] = value;
|
||||
}
|
||||
|
||||
fetchAudits = (data: any): Promise<void> => {
|
||||
this.isLoading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
auditService.all(data).then(response => {
|
||||
runInAction(() => {
|
||||
this.list = response.sessions.map(item => Audit.fromJson(item))
|
||||
this.total = response.count
|
||||
})
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
}).finally(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fetchAllAudits = async (data: any): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
auditService.all(data).then((data) => {
|
||||
const headers = [
|
||||
{ label: 'User', key: 'username' },
|
||||
{ label: 'Email', key: 'email' },
|
||||
{ label: 'UserID', key: 'userId' },
|
||||
{ label: 'Method', key: 'method' },
|
||||
{ label: 'Action', key: 'action' },
|
||||
{ label: 'Endpoint', key: 'endpoint' },
|
||||
{ label: 'Created At', key: 'createdAt' },
|
||||
]
|
||||
data = data.sessions.map(item => ({
|
||||
...item,
|
||||
createdAt: DateTime.fromMillis(item.createdAt).toFormat('LLL dd yyyy hh:mm a')
|
||||
}))
|
||||
exportCSVFile(headers, data, `audit-${new Date().toLocaleDateString()}`);
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exportToCsv = async (): Promise<void> => {
|
||||
const promise = this.fetchAllAudits({ limit: this.total })
|
||||
toast.promise(promise, {
|
||||
pending: 'Exporting...',
|
||||
success: 'Export successful',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,28 @@
|
|||
import React from 'react';
|
||||
import DashboardStore, { IDashboardSotre } from './dashboardStore';
|
||||
import MetricStore, { IMetricStore } from './metricStore';
|
||||
import UserStore from './userStore';
|
||||
import RoleStore from './roleStore';
|
||||
import APIClient from 'App/api_client';
|
||||
import { dashboardService, metricService, sessionService } from 'App/services';
|
||||
import { dashboardService, metricService, sessionService, userService, auditService } from 'App/services';
|
||||
import SettingsStore from './settingsStore';
|
||||
import AuditStore from './auditStore';
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: IDashboardSotre;
|
||||
metricStore: IMetricStore;
|
||||
settingsStore: SettingsStore;
|
||||
userStore: UserStore;
|
||||
roleStore: RoleStore;
|
||||
auditStore: AuditStore;
|
||||
|
||||
constructor() {
|
||||
this.dashboardStore = new DashboardStore();
|
||||
this.metricStore = new MetricStore();
|
||||
this.settingsStore = new SettingsStore();
|
||||
this.userStore = new UserStore();
|
||||
this.roleStore = new RoleStore();
|
||||
this.auditStore = new AuditStore();
|
||||
}
|
||||
|
||||
initClient() {
|
||||
|
|
@ -21,6 +30,7 @@ export class RootStore {
|
|||
dashboardService.initClient(client)
|
||||
metricService.initClient(client)
|
||||
sessionService.initClient(client)
|
||||
userService.initClient(client)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
31
frontend/app/mstore/roleStore.ts
Normal file
31
frontend/app/mstore/roleStore.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { makeAutoObservable, observable, action } from "mobx"
|
||||
import { userService } from "App/services";
|
||||
import Role, { IRole } from "./types/role";
|
||||
|
||||
export default class UserStore {
|
||||
list: IRole[] = [];
|
||||
loading: boolean = false;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
list: observable,
|
||||
loading: observable,
|
||||
})
|
||||
}
|
||||
|
||||
fetchRoles(): Promise<any> {
|
||||
this.loading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.getRoles()
|
||||
.then(response => {
|
||||
this.list = response.map((role: any) => new Role().fromJson(role));
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
40
frontend/app/mstore/types/audit.ts
Normal file
40
frontend/app/mstore/types/audit.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { DateTime } from 'luxon';
|
||||
import { unserscoreToSpaceAndCapitalize } from 'App/utils';
|
||||
|
||||
export default class Audit {
|
||||
id: string = '';
|
||||
username: string = '';
|
||||
email: string = '';
|
||||
action: string = '';
|
||||
createdAt: any = null;
|
||||
endPoint: string = '';
|
||||
parameters: any = {};
|
||||
method: string = '';
|
||||
status: string = '';
|
||||
payload: any = {}
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
static fromJson(json: any): Audit {
|
||||
const audit = new Audit();
|
||||
audit.id = json.rn;
|
||||
audit.username = json.username;
|
||||
audit.action = unserscoreToSpaceAndCapitalize(json.action);
|
||||
audit.createdAt = json.createdAt && DateTime.fromMillis(json.createdAt || 0);
|
||||
audit.endPoint = json.endpoint;
|
||||
audit.parameters = json.parameters;
|
||||
audit.method = json.method;
|
||||
audit.status = json.status
|
||||
audit.email = json.email
|
||||
audit.payload = typeof json.payload === 'string' ? JSON.parse(json.payload) : json.payload
|
||||
return audit;
|
||||
}
|
||||
|
||||
toJson(): any {
|
||||
return {
|
||||
id: this.id,
|
||||
username: this.username
|
||||
};
|
||||
}
|
||||
}
|
||||
45
frontend/app/mstore/types/role.ts
Normal file
45
frontend/app/mstore/types/role.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { makeAutoObservable, observable, runInAction } from "mobx";
|
||||
|
||||
export interface IRole {
|
||||
roleId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
isProtected: boolean;
|
||||
|
||||
fromJson(json: any);
|
||||
toJson(): any;
|
||||
}
|
||||
|
||||
export default class Role implements IRole {
|
||||
roleId: string = '';
|
||||
name: string = '';
|
||||
description: string = '';
|
||||
isProtected: boolean = false;
|
||||
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
roleId: observable,
|
||||
name: observable,
|
||||
description: observable,
|
||||
})
|
||||
}
|
||||
|
||||
fromJson(json: any) {
|
||||
runInAction(() => {
|
||||
this.roleId = json.roleId;
|
||||
this.name = json.name;
|
||||
this.description = json.description;
|
||||
this.isProtected = json.protected;
|
||||
})
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
id: this.roleId,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
105
frontend/app/mstore/types/user.ts
Normal file
105
frontend/app/mstore/types/user.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { runInAction, makeAutoObservable, observable } from 'mobx'
|
||||
import { DateTime } from 'luxon';
|
||||
import { validateEmail, validateName } from 'App/validate';
|
||||
|
||||
export interface IUser {
|
||||
userId: string
|
||||
email: string
|
||||
createdAt: string
|
||||
isAdmin: boolean
|
||||
isSuperAdmin: boolean
|
||||
isJoined: boolean
|
||||
isExpiredInvite: boolean
|
||||
roleId: string
|
||||
roleName: string
|
||||
invitationLink: string
|
||||
|
||||
|
||||
updateKey(key: string, value: any): void
|
||||
fromJson(json: any): IUser
|
||||
toJson(): any
|
||||
toSave(): any
|
||||
}
|
||||
|
||||
export default class User implements IUser {
|
||||
userId: string = '';
|
||||
name: string = '';
|
||||
email: string = '';
|
||||
createdAt: string = '';
|
||||
isAdmin: boolean = false;
|
||||
isSuperAdmin: boolean = false;
|
||||
isJoined: boolean = false;
|
||||
isExpiredInvite: boolean = false;
|
||||
roleId: string = '';
|
||||
roleName: string = '';
|
||||
invitationLink: string = '';
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
userId: observable,
|
||||
email: observable,
|
||||
createdAt: observable,
|
||||
isAdmin: observable,
|
||||
isSuperAdmin: observable,
|
||||
isJoined: observable,
|
||||
isExpiredInvite: observable,
|
||||
roleId: observable,
|
||||
roleName: observable,
|
||||
invitationLink: observable,
|
||||
})
|
||||
}
|
||||
|
||||
updateKey(key: string, value: any) {
|
||||
runInAction(() => {
|
||||
this[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
fromJson(json: any) {
|
||||
runInAction(() => {
|
||||
this.userId = json.userId || json.id; // TODO api returning id
|
||||
this.name = json.name;
|
||||
this.email = json.email;
|
||||
this.createdAt = json.createdAt && DateTime.fromMillis(json.createdAt || 0)
|
||||
this.isAdmin = json.admin
|
||||
this.isSuperAdmin = json.superAdmin
|
||||
this.isJoined = json.joined
|
||||
this.isExpiredInvite = json.expiredInvitation
|
||||
this.roleId = json.roleId
|
||||
this.roleName = json.roleName
|
||||
this.invitationLink = json.invitationLink
|
||||
})
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
userId: this.userId,
|
||||
name: this.name,
|
||||
email: this.email,
|
||||
admin: this.isAdmin,
|
||||
superAdmin: this.isSuperAdmin,
|
||||
roleId: this.roleId,
|
||||
joined: this.isJoined,
|
||||
invitationLink: this.invitationLink,
|
||||
expiredInvitation: this.isExpiredInvite,
|
||||
}
|
||||
}
|
||||
|
||||
toSave() {
|
||||
return {
|
||||
name: this.name,
|
||||
email: this.email,
|
||||
admin: this.isAdmin,
|
||||
roleId: this.roleId,
|
||||
}
|
||||
}
|
||||
|
||||
valid() {
|
||||
return validateName(this.name, { empty: false }) && validateEmail(this.email) && !!this.roleId;
|
||||
}
|
||||
|
||||
exists() {
|
||||
return !!this.userId;
|
||||
}
|
||||
}
|
||||
162
frontend/app/mstore/userStore.ts
Normal file
162
frontend/app/mstore/userStore.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import { makeAutoObservable, observable, action } from "mobx"
|
||||
import User, { IUser } from "./types/user";
|
||||
import { userService } from "App/services";
|
||||
import { toast } from 'react-toastify';
|
||||
import copy from 'copy-to-clipboard';
|
||||
|
||||
export default class UserStore {
|
||||
list: IUser[] = [];
|
||||
instance: IUser|null = null;
|
||||
page: number = 1;
|
||||
pageSize: number = 10;
|
||||
searchQuery: string = "";
|
||||
modifiedCount: number = 0;
|
||||
|
||||
loading: boolean = false;
|
||||
saving: boolean = false;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
instance: observable,
|
||||
updateUser: action,
|
||||
updateKey: action,
|
||||
initUser: action,
|
||||
})
|
||||
}
|
||||
|
||||
initUser(user?: any ): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (user) {
|
||||
this.instance = new User().fromJson(user.toJson());
|
||||
} else {
|
||||
this.instance = new User();
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
}
|
||||
|
||||
updateKey(key: string, value: any) {
|
||||
this[key] = value
|
||||
|
||||
if (key === 'searchQuery') {
|
||||
this.page = 1
|
||||
}
|
||||
}
|
||||
|
||||
updateUser(user: IUser) {
|
||||
const index = this.list.findIndex(u => u.userId === user.userId);
|
||||
if (index > -1) {
|
||||
this.list[index] = user;
|
||||
}
|
||||
}
|
||||
|
||||
fetchUser(userId: string): Promise<any> {
|
||||
this.loading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.one(userId)
|
||||
.then(response => {
|
||||
this.instance = new User().fromJson(response.data);
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchUsers(): Promise<any> {
|
||||
this.loading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.all()
|
||||
.then(response => {
|
||||
this.list = response.map(user => new User().fromJson(user));
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
saveUser(user: IUser): Promise<any> {
|
||||
this.saving = true;
|
||||
const wasCreating = !user.userId;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.save(user).then(response => {
|
||||
const newUser = new User().fromJson(response);
|
||||
if (wasCreating) {
|
||||
this.modifiedCount -= 1;
|
||||
this.list.push(new User().fromJson(newUser));
|
||||
toast.success('User created successfully');
|
||||
} else {
|
||||
this.updateUser(newUser);
|
||||
toast.success('User updated successfully');
|
||||
}
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.saving = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteUser(userId: string): Promise<any> {
|
||||
this.saving = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
userService.delete(userId)
|
||||
.then(response => {
|
||||
this.modifiedCount += 1;
|
||||
this.list = this.list.filter(user => user.userId !== userId);
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.saving = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
copyInviteCode(userId: string): void {
|
||||
const content = this.list.find(u => u.userId === userId)?.invitationLink;
|
||||
if (content) {
|
||||
copy(content);
|
||||
toast.success('Invite code copied successfully');
|
||||
} else {
|
||||
toast.error('Invite code not found');
|
||||
}
|
||||
}
|
||||
|
||||
generateInviteCode(userId: string): Promise<any> {
|
||||
this.saving = true;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
userService.generateInviteCode(userId)
|
||||
.then(response => {
|
||||
const index = this.list.findIndex(u => u.userId === userId);
|
||||
if (index > -1) {
|
||||
this.list[index].updateKey('isExpiredInvite', false);
|
||||
this.list[index].updateKey('invitationLink', response.invitationLink);
|
||||
}
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
this.saving = false;
|
||||
reject(error);
|
||||
}).finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
});
|
||||
|
||||
toast.promise(promise, {
|
||||
pending: 'Generating an invite code...',
|
||||
success: 'Invite code generated successfully',
|
||||
})
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,11 @@ import Profile from 'Types/session/profile';
|
|||
import ReduxAction from 'Types/session/reduxAction';
|
||||
|
||||
import { update } from '../store';
|
||||
import {
|
||||
import {
|
||||
init as initListsDepr,
|
||||
append as listAppend,
|
||||
setStartTime as setListsStartTime
|
||||
} from '../lists';
|
||||
setStartTime as setListsStartTime
|
||||
} from '../lists';
|
||||
|
||||
import StatedScreen from './StatedScreen/StatedScreen';
|
||||
|
||||
|
|
@ -26,6 +26,7 @@ import ActivityManager from './managers/ActivityManager';
|
|||
import AssistManager from './managers/AssistManager';
|
||||
|
||||
import MFileReader from './messages/MFileReader';
|
||||
import loadFiles from './network/loadFiles';
|
||||
|
||||
import { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './StatedScreen/StatedScreen';
|
||||
import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './managers/AssistManager';
|
||||
|
|
@ -33,7 +34,7 @@ import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './m
|
|||
import type { PerformanceChartPoint } from './managers/PerformanceTrackManager';
|
||||
import type { SkipInterval } from './managers/ActivityManager';
|
||||
|
||||
const LIST_NAMES = [ "redux", "mobx", "vuex", "ngrx", "graphql", "exceptions", "profiles", "longtasks" ] as const;
|
||||
const LIST_NAMES = ["redux", "mobx", "vuex", "ngrx", "graphql", "exceptions", "profiles", "longtasks"] as const;
|
||||
const LISTS_INITIAL_STATE = {};
|
||||
LIST_NAMES.forEach(name => {
|
||||
LISTS_INITIAL_STATE[`${name}ListNow`] = [];
|
||||
|
|
@ -65,15 +66,15 @@ type ListsObject = {
|
|||
}
|
||||
|
||||
function initLists(): ListsObject {
|
||||
const lists: Partial<ListsObject> = {} ;
|
||||
const lists: Partial<ListsObject> = {};
|
||||
for (var i = 0; i < LIST_NAMES.length; i++) {
|
||||
lists[ LIST_NAMES[i] ] = new ListWalker();
|
||||
lists[LIST_NAMES[i]] = new ListWalker();
|
||||
}
|
||||
return lists as ListsObject;
|
||||
}
|
||||
|
||||
|
||||
import type {
|
||||
import type {
|
||||
Message,
|
||||
SetPageLocation,
|
||||
ConnectionInformation,
|
||||
|
|
@ -110,7 +111,7 @@ export default class MessageDistributor extends StatedScreen {
|
|||
private navigationStartOffset: number = 0;
|
||||
private lastMessageTime: number = 0;
|
||||
|
||||
constructor(private readonly session: any /*Session*/, jwt: string, config, live: boolean) {
|
||||
constructor(private readonly session: any /*Session*/, jwt: string, config, live: boolean) {
|
||||
super();
|
||||
this.pagesManager = new PagesManager(this, this.session.isMobile)
|
||||
this.mouseManager = new MouseManager(this);
|
||||
|
|
@ -128,7 +129,7 @@ export default class MessageDistributor extends StatedScreen {
|
|||
/* == REFACTOR_ME == */
|
||||
const eventList = this.session.events.toJSON();
|
||||
initListsDepr({
|
||||
event: eventList,
|
||||
event: eventList,
|
||||
stack: this.session.stackEvents.toJSON(),
|
||||
resource: this.session.resources.toJSON(),
|
||||
});
|
||||
|
|
@ -146,96 +147,83 @@ export default class MessageDistributor extends StatedScreen {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// subscribeOnMessages(sockUrl) {
|
||||
// this.setMessagesLoading(true);
|
||||
// const socket = new WebSocket(sockUrl);
|
||||
// socket.binaryType = 'arraybuffer';
|
||||
// socket.onerror = (e) => {
|
||||
// // TODO: reconnect
|
||||
// update({ error: true });
|
||||
// }
|
||||
// socket.onmessage = (socketMessage) => {
|
||||
// const data = new Uint8Array(socketMessage.data);
|
||||
// const msgs = [];
|
||||
// messageGenerator // parseBuffer(msgs, data);
|
||||
// // TODO: count indexes. Now will not work due to wrong indexes
|
||||
// //msgs.forEach(this.distributeMessage);
|
||||
// this.setMessagesLoading(false);
|
||||
// this.setDisconnected(false);
|
||||
// }
|
||||
// this._socket = socket;
|
||||
// }
|
||||
|
||||
private waitingForFiles: boolean = false
|
||||
private loadMessages(): void {
|
||||
const fileUrl: string = this.session.mobsUrl;
|
||||
this.setMessagesLoading(true);
|
||||
window.fetch(fileUrl)
|
||||
.then(r => r.arrayBuffer())
|
||||
.then(b => {
|
||||
const r = new MFileReader(new Uint8Array(b), this.sessionStart);
|
||||
const msgs: Array<Message> = [];
|
||||
this.setMessagesLoading(true)
|
||||
this.waitingForFiles = true
|
||||
|
||||
while (r.hasNext()) {
|
||||
const next = r.next();
|
||||
if (next != null) {
|
||||
this.distributeMessage(next[0], next[1]);
|
||||
msgs.push(next[0]);
|
||||
const r = new MFileReader(new Uint8Array(), this.sessionStart)
|
||||
const msgs: Array<Message> = []
|
||||
loadFiles(this.session.mobsUrl,
|
||||
b => {
|
||||
r.append(b)
|
||||
let next: ReturnType<MFileReader['next']>
|
||||
while (next = r.next()) {
|
||||
const [msg, index] = next
|
||||
this.distributeMessage(msg, index)
|
||||
this.lastMessageTime = Math.max(msg.time, this.lastMessageTime)
|
||||
|
||||
msgs.push(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore Hack for upet (TODO: fix ordering in one mutation (removes first))
|
||||
const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id);
|
||||
//const createNodeTypes = ["create_text_node", "create_element_node"];
|
||||
this.pagesManager.sort((m1, m2) =>{
|
||||
if (m1.time === m2.time) {
|
||||
if (m1.tp === "remove_node" && m2.tp !== "remove_node") {
|
||||
if (headChildrenIds.includes(m1.id)) {
|
||||
return -1;
|
||||
}
|
||||
} else if (m2.tp === "remove_node" && m1.tp !== "remove_node") {
|
||||
if (headChildrenIds.includes(m2.id)) {
|
||||
return 1;
|
||||
}
|
||||
} else if (m2.tp === "remove_node" && m1.tp === "remove_node") {
|
||||
const m1FromHead = headChildrenIds.includes(m1.id);
|
||||
const m2FromHead = headChildrenIds.includes(m2.id);
|
||||
if (m1FromHead && !m2FromHead) {
|
||||
return -1;
|
||||
} else if (m2FromHead && !m1FromHead) {
|
||||
return 1;
|
||||
logger.info("Messages count: ", msgs.length, msgs)
|
||||
|
||||
// @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first))
|
||||
const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id);
|
||||
this.pagesManager.sort((m1, m2) => {
|
||||
if (m1.time === m2.time) {
|
||||
if (m1.tp === "remove_node" && m2.tp !== "remove_node") {
|
||||
if (headChildrenIds.includes(m1.id)) {
|
||||
return -1;
|
||||
}
|
||||
} else if (m2.tp === "remove_node" && m1.tp !== "remove_node") {
|
||||
if (headChildrenIds.includes(m2.id)) {
|
||||
return 1;
|
||||
}
|
||||
} else if (m2.tp === "remove_node" && m1.tp === "remove_node") {
|
||||
const m1FromHead = headChildrenIds.includes(m1.id);
|
||||
const m2FromHead = headChildrenIds.includes(m2.id);
|
||||
if (m1FromHead && !m2FromHead) {
|
||||
return -1;
|
||||
} else if (m2FromHead && !m1FromHead) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
|
||||
return 0;
|
||||
})
|
||||
|
||||
logger.info("Messages count: ", msgs.length, msgs);
|
||||
|
||||
const stateToUpdate: {[key:string]: any} = {
|
||||
performanceChartData: this.performanceTrackManager.chartData,
|
||||
performanceAvaliability: this.performanceTrackManager.avaliability,
|
||||
};
|
||||
this.activirtManager?.end();
|
||||
stateToUpdate.skipIntervals = this.activirtManager?.list || [];
|
||||
LIST_NAMES.forEach(key => {
|
||||
stateToUpdate[ `${ key }List` ] = this.lists[ key ].list;
|
||||
});
|
||||
update(stateToUpdate);
|
||||
|
||||
this.windowNodeCounter.reset();
|
||||
|
||||
this.setMessagesLoading(false);
|
||||
const stateToUpdate: {[key:string]: any} = {
|
||||
performanceChartData: this.performanceTrackManager.chartData,
|
||||
performanceAvaliability: this.performanceTrackManager.avaliability,
|
||||
}
|
||||
LIST_NAMES.forEach(key => {
|
||||
stateToUpdate[ `${ key }List` ] = this.lists[ key ].list
|
||||
})
|
||||
update(stateToUpdate)
|
||||
this.setMessagesLoading(false)
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.windowNodeCounter.reset()
|
||||
if (this.activirtManager) {
|
||||
this.activirtManager.end()
|
||||
update({
|
||||
skipIntervals: this.activirtManager.list
|
||||
})
|
||||
}
|
||||
this.waitingForFiles = false
|
||||
this.setMessagesLoading(false)
|
||||
})
|
||||
.catch(e => {
|
||||
logger.error(e)
|
||||
this.waitingForFiles = false
|
||||
this.setMessagesLoading(false)
|
||||
update({ error: true })
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
this.setMessagesLoading(false);
|
||||
update({ error: true });
|
||||
});
|
||||
}
|
||||
|
||||
move(t: number, index?: number):void {
|
||||
move(t: number, index?: number): void {
|
||||
const stateToUpdate: Partial<State> = {};
|
||||
/* == REFACTOR_ME == */
|
||||
const lastLoadedLocationMsg = this.loadedLocationManager.moveToLast(t, index);
|
||||
|
|
@ -248,7 +236,7 @@ export default class MessageDistributor extends StatedScreen {
|
|||
if (llEvent.domContentLoadedTime != null) {
|
||||
stateToUpdate.domContentLoadedTime = {
|
||||
time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db)
|
||||
value: llEvent.domContentLoadedTime,
|
||||
value: llEvent.domContentLoadedTime,
|
||||
}
|
||||
}
|
||||
if (llEvent.loadTime != null) {
|
||||
|
|
@ -277,9 +265,9 @@ export default class MessageDistributor extends StatedScreen {
|
|||
}
|
||||
|
||||
LIST_NAMES.forEach(key => {
|
||||
const lastMsg = this.lists[ key ].moveToLast(t, key === 'exceptions' ? undefined : index);
|
||||
const lastMsg = this.lists[key].moveToLast(t, key === 'exceptions' ? undefined : index);
|
||||
if (lastMsg != null) {
|
||||
stateToUpdate[`${key}ListNow`] = this.lists[ key ].listNow;
|
||||
stateToUpdate[`${key}ListNow`] = this.lists[key].listNow;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -298,21 +286,25 @@ export default class MessageDistributor extends StatedScreen {
|
|||
this.window.scrollTo(lastScroll.x, lastScroll.y);
|
||||
}
|
||||
// Moving mouse and setting :hover classes on ready view
|
||||
this.mouseManager.move(t);
|
||||
this.mouseManager.move(t);
|
||||
const lastClick = this.clickManager.moveToLast(t);
|
||||
if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms
|
||||
this.cursor.click();
|
||||
}
|
||||
// After all changes - redraw the marker
|
||||
//this.marker.redraw();
|
||||
})
|
||||
})
|
||||
|
||||
if (this.waitingForFiles && this.lastMessageTime <= t) {
|
||||
this.setMessagesLoading(true)
|
||||
}
|
||||
}
|
||||
|
||||
_decodeMessage(msg, keys: Array<string>) {
|
||||
const decoded = {};
|
||||
try {
|
||||
keys.forEach(key => {
|
||||
decoded[ key ] = this.decoder.decode(msg[ key ]);
|
||||
decoded[key] = this.decoder.decode(msg[key]);
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error("Error on message decoding: ", e, msg);
|
||||
|
|
@ -323,15 +315,13 @@ export default class MessageDistributor extends StatedScreen {
|
|||
|
||||
/* Binded */
|
||||
distributeMessage = (msg: Message, index: number): void => {
|
||||
this.lastMessageTime = msg.time;
|
||||
|
||||
if ([
|
||||
if ([
|
||||
"mouse_move",
|
||||
"mouse_click",
|
||||
"create_element_node", // not a user activity, though visual change
|
||||
"set_input_value",
|
||||
"set_input_checked",
|
||||
"set_viewport_size",
|
||||
"set_viewport_size",
|
||||
"set_viewport_scroll",
|
||||
].includes(msg.tp)) {
|
||||
this.activirtManager?.updateAcctivity(msg.time);
|
||||
|
|
@ -343,13 +333,13 @@ export default class MessageDistributor extends StatedScreen {
|
|||
/* Lists: */
|
||||
case "console_log":
|
||||
if (msg.level === 'debug') break;
|
||||
listAppend("log", Log({
|
||||
listAppend("log", Log({
|
||||
level: msg.level,
|
||||
value: msg.value,
|
||||
time,
|
||||
time,
|
||||
index,
|
||||
}));
|
||||
break;
|
||||
break;
|
||||
case "fetch":
|
||||
listAppend("fetch", Resource({
|
||||
method: msg.method,
|
||||
|
|
@ -362,118 +352,117 @@ export default class MessageDistributor extends StatedScreen {
|
|||
time: msg.timestamp - this.sessionStart, //~
|
||||
index,
|
||||
}));
|
||||
break;
|
||||
break;
|
||||
/* */
|
||||
case "set_page_location":
|
||||
this.locationManager.add(msg);
|
||||
if (msg.navigationStart > 0) {
|
||||
this.loadedLocationManager.add(msg);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case "set_viewport_size":
|
||||
this.resizeManager.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "mouse_move":
|
||||
this.mouseManager.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "mouse_click":
|
||||
this.clickManager.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "set_viewport_scroll":
|
||||
this.scrollManager.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "performance_track":
|
||||
this.performanceTrackManager.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "set_page_visibility":
|
||||
this.performanceTrackManager.handleVisibility(msg)
|
||||
break;
|
||||
break;
|
||||
case "connection_information":
|
||||
this.connectionInfoManger.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "o_table":
|
||||
this.decoder.set(msg.key, msg.value);
|
||||
break;
|
||||
break;
|
||||
case "redux":
|
||||
decoded = this._decodeMessage(msg, ["state", "action"]);
|
||||
logger.log(decoded)
|
||||
if (decoded != null) {
|
||||
this.lists.redux.add(decoded);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case "ng_rx":
|
||||
decoded = this._decodeMessage(msg, ["state", "action"]);
|
||||
logger.log(decoded)
|
||||
if (decoded != null) {
|
||||
this.lists.ngrx.add(decoded);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "vuex":
|
||||
decoded = this._decodeMessage(msg, ["state", "mutation"]);
|
||||
logger.log(decoded)
|
||||
if (decoded != null) {
|
||||
this.lists.vuex.add(decoded);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "mob_x":
|
||||
decoded = this._decodeMessage(msg, ["payload"]);
|
||||
logger.log(decoded)
|
||||
|
||||
if (decoded != null) {
|
||||
this.lists.mobx.add(decoded);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "graph_ql":
|
||||
// @ts-ignore some hack? TODO: remove
|
||||
msg.duration = 0;
|
||||
this.lists.graphql.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "profiler":
|
||||
this.lists.profiles.add(msg);
|
||||
break;
|
||||
break;
|
||||
case "long_task":
|
||||
this.lists.longtasks.add({
|
||||
...msg,
|
||||
time: msg.timestamp - this.sessionStart,
|
||||
});
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
switch (msg.tp){
|
||||
switch (msg.tp) {
|
||||
case "create_document":
|
||||
this.windowNodeCounter.reset();
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
break;
|
||||
case "create_text_node":
|
||||
case "create_element_node":
|
||||
this.windowNodeCounter.addNode(msg.id, msg.parentID);
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
break;
|
||||
case "move_node":
|
||||
this.windowNodeCounter.moveNode(msg.id, msg.parentID);
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
break;
|
||||
case "remove_node":
|
||||
this.windowNodeCounter.removeNode(msg.id);
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
this.pagesManager.add(msg);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getLastMessageTime():number {
|
||||
getLastMessageTime(): number {
|
||||
return this.lastMessageTime;
|
||||
}
|
||||
|
||||
getFirstMessageTime():number {
|
||||
getFirstMessageTime(): number {
|
||||
return 0; //this.pagesManager.minTime;
|
||||
}
|
||||
|
||||
// TODO: clean managers?
|
||||
clean() {
|
||||
super.clean();
|
||||
//if (this._socket) this._socket.close();
|
||||
update(INITIAL_STATE);
|
||||
this.assistManager.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,62 +7,65 @@ import RawMessageReader from './RawMessageReader';
|
|||
// needSkipMessage() and next() methods here use buf and p protected properties,
|
||||
// which should be probably somehow incapsulated
|
||||
export default class MFileReader extends RawMessageReader {
|
||||
private pLastMessageID: number = 0;
|
||||
private currentTime: number = 0;
|
||||
public error: boolean = false;
|
||||
private pLastMessageID: number = 0
|
||||
private currentTime: number = 0
|
||||
public error: boolean = false
|
||||
constructor(data: Uint8Array, private readonly startTime: number) {
|
||||
super(data);
|
||||
super(data)
|
||||
}
|
||||
|
||||
private needSkipMessage(): boolean {
|
||||
if (this.p === 0) return false;
|
||||
if (this.p === 0) return false
|
||||
for (let i = 7; i >= 0; i--) {
|
||||
if (this.buf[ this.p + i ] !== this.buf[ this.pLastMessageID + i ]) {
|
||||
return this.buf[ this.p + i ] - this.buf[ this.pLastMessageID + i ] < 0;
|
||||
return this.buf[ this.p + i ] - this.buf[ this.pLastMessageID + i ] < 0
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
private readRawMessage(): RawMessage | null {
|
||||
this.skip(8);
|
||||
this.skip(8)
|
||||
try {
|
||||
return super.readMessage();
|
||||
const msg = super.readMessage()
|
||||
if (!msg) {
|
||||
this.skip(-8)
|
||||
}
|
||||
return msg
|
||||
} catch (e) {
|
||||
this.error = true;
|
||||
logger.error("Read message error:", e);
|
||||
return null;
|
||||
this.error = true
|
||||
logger.error("Read message error:", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
hasNext():boolean {
|
||||
return !this.error && this.hasNextByte();
|
||||
}
|
||||
|
||||
next(): [ Message, number] | null {
|
||||
if (!this.hasNext()) {
|
||||
return null;
|
||||
if (this.error || !this.hasNextByte()) {
|
||||
return null
|
||||
}
|
||||
|
||||
while (this.needSkipMessage()) {
|
||||
this.readRawMessage();
|
||||
if (!this.readRawMessage()) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
this.pLastMessageID = this.p;
|
||||
|
||||
const rMsg = this.readRawMessage();
|
||||
this.pLastMessageID = this.p
|
||||
|
||||
const rMsg = this.readRawMessage()
|
||||
if (!rMsg) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
if (rMsg.tp === "timestamp") {
|
||||
this.currentTime = rMsg.timestamp - this.startTime;
|
||||
} else {
|
||||
const msg = Object.assign(rMsg, {
|
||||
time: this.currentTime,
|
||||
_index: this.pLastMessageID,
|
||||
})
|
||||
return [msg, this.pLastMessageID];
|
||||
}
|
||||
return null;
|
||||
this.currentTime = rMsg.timestamp - this.startTime
|
||||
return this.next()
|
||||
}
|
||||
|
||||
const msg = Object.assign(rMsg, {
|
||||
time: this.currentTime,
|
||||
_index: this.pLastMessageID,
|
||||
})
|
||||
return [msg, this.pLastMessageID]
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ export function resolveURL(baseURL: string, relURL: string): string {
|
|||
}
|
||||
|
||||
|
||||
var match = /bar/.exec("foobar");
|
||||
const re1 = /url\(("[^"]*"|'[^']*'|[^)]*)\)/g
|
||||
const re2 = /@import "(.*?)"/g
|
||||
function cssUrlsIndex(css: string): Array<[number, number]> {
|
||||
|
|
|
|||
49
frontend/app/player/MessageDistributor/network/loadFiles.ts
Normal file
49
frontend/app/player/MessageDistributor/network/loadFiles.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
const NO_NTH_FILE = "nnf"
|
||||
|
||||
export default function load(
|
||||
urls: string[],
|
||||
onData: (Uint8Array) => void,
|
||||
): Promise<void> {
|
||||
const firstFileURL = urls.shift()
|
||||
if (!firstFileURL) {
|
||||
return Promise.reject("No urls provided")
|
||||
}
|
||||
return window.fetch(firstFileURL)
|
||||
.then(r => {
|
||||
if (r.status >= 400) {
|
||||
throw new Error(`no start file. status code ${ r.status }`)
|
||||
}
|
||||
return r.arrayBuffer()
|
||||
})
|
||||
.then(b => new Uint8Array(b))
|
||||
.then(onData)
|
||||
.then(() =>
|
||||
urls.reduce((p, url) =>
|
||||
p.then(() =>
|
||||
window.fetch(url)
|
||||
.then(r => {
|
||||
return new Promise<ArrayBuffer>((res, rej) => {
|
||||
if (r.status == 404) {
|
||||
rej(NO_NTH_FILE)
|
||||
return
|
||||
}
|
||||
if (r.status >= 400) {
|
||||
rej(`Bad endfile status code ${r.status}`)
|
||||
return
|
||||
}
|
||||
res(r.arrayBuffer())
|
||||
})
|
||||
})
|
||||
.then(b => new Uint8Array(b))
|
||||
.then(onData)
|
||||
),
|
||||
Promise.resolve(),
|
||||
)
|
||||
)
|
||||
.catch(e => {
|
||||
if (e === NO_NTH_FILE) {
|
||||
return
|
||||
}
|
||||
throw e
|
||||
})
|
||||
}
|
||||
|
|
@ -61,10 +61,11 @@ export const CLIENT_TABS = {
|
|||
PROFILE: 'account',
|
||||
MANAGE_USERS: 'team',
|
||||
MANAGE_ROLES: 'roles',
|
||||
SITES: 'projects',
|
||||
SITES: 'projects',
|
||||
CUSTOM_FIELDS: 'metadata',
|
||||
WEBHOOKS: 'webhooks',
|
||||
NOTIFICATIONS: 'notifications',
|
||||
AUDIT: 'audit',
|
||||
};
|
||||
export const CLIENT_DEFAULT_TAB = CLIENT_TABS.PROFILE;
|
||||
const routerClientTabString = `:activeTab(${ Object.values(CLIENT_TABS).join('|') })`;
|
||||
|
|
|
|||
25
frontend/app/services/AuditService.ts
Normal file
25
frontend/app/services/AuditService.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import APIClient from 'App/api_client';
|
||||
|
||||
export default class AuditService {
|
||||
private client: APIClient;
|
||||
|
||||
constructor(client?: APIClient) {
|
||||
this.client = client ? client : new APIClient();
|
||||
}
|
||||
|
||||
initClient(client?: APIClient) {
|
||||
this.client = client || new APIClient();
|
||||
}
|
||||
|
||||
all(data: any): Promise<any> {
|
||||
return this.client.post('/trails', data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
}
|
||||
|
||||
one(id: string): Promise<any> {
|
||||
return this.client.get('/trails/' + id)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
}
|
||||
57
frontend/app/services/UserService.ts
Normal file
57
frontend/app/services/UserService.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import APIClient from 'App/api_client';
|
||||
import { IUser } from 'App/mstore/types/user'
|
||||
|
||||
export default class UserService {
|
||||
private client: APIClient;
|
||||
|
||||
constructor(client?: APIClient) {
|
||||
this.client = client ? client : new APIClient();
|
||||
}
|
||||
|
||||
initClient(client?: APIClient) {
|
||||
this.client = client || new APIClient();
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.client.get('/client/members')
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
}
|
||||
|
||||
one(userId: string) {
|
||||
return this.client.get('/users/' + userId)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
|
||||
save(user: IUser): Promise<any> {
|
||||
const data = user.toSave();
|
||||
if (user.userId) {
|
||||
return this.client.put('/client/members/' + user.userId, data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {})
|
||||
} else {
|
||||
return this.client.post('/client/members', data)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
}
|
||||
|
||||
generateInviteCode(userId: any): Promise<any> {
|
||||
return this.client.get(`/client/members/${userId}/reset`)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
|
||||
delete(userId: string) {
|
||||
return this.client.delete('/client/members/' + userId)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
return this.client.get('/client/roles')
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
import DashboardService, { IDashboardService } from "./DashboardService";
|
||||
import MetricService, { IMetricService } from "./MetricService";
|
||||
import SessionSerivce from "./SessionService";
|
||||
import UserService from "./UserService";
|
||||
import AuditService from './AuditService';
|
||||
|
||||
export const dashboardService: IDashboardService = new DashboardService();
|
||||
export const metricService: IMetricService = new MetricService();
|
||||
export const sessionService: SessionSerivce = new SessionSerivce();
|
||||
export const sessionService: SessionSerivce = new SessionSerivce();
|
||||
export const userService: UserService = new UserService();
|
||||
export const auditService: AuditService = new AuditService();
|
||||
|
|
@ -147,13 +147,4 @@
|
|||
height: 100vh;
|
||||
overflow-y: hidden;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/* .svg-map__location {
|
||||
fill: #EEE !important;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
fill: #fff !important;
|
||||
}
|
||||
} */
|
||||
}
|
||||
3
frontend/app/svg/icons/grid-3x3.svg
Normal file
3
frontend/app/svg/icons/grid-3x3.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-grid-3x3" viewBox="0 0 16 16">
|
||||
<path d="M0 1.5A1.5 1.5 0 0 1 1.5 0h13A1.5 1.5 0 0 1 16 1.5v13a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 14.5v-13zM1.5 1a.5.5 0 0 0-.5.5V5h4V1H1.5zM5 6H1v4h4V6zm1 4h4V6H6v4zm-1 1H1v3.5a.5.5 0 0 0 .5.5H5v-4zm1 0v4h4v-4H6zm5 0v4h3.5a.5.5 0 0 0 .5-.5V11h-4zm0-1h4V6h-4v4zm0-5h4V1.5a.5.5 0 0 0-.5-.5H11v4zm-1 0V1H6v4h4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 454 B |
3
frontend/app/svg/icons/list-ul.svg
Normal file
3
frontend/app/svg/icons/list-ul.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-list-ul" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 404 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-paragraph" viewBox="0 0 16 16">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-text-paragraph" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M2 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm4-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 374 B |
|
|
@ -103,6 +103,10 @@ export default Record({
|
|||
endTimestamp: this.end,
|
||||
};
|
||||
},
|
||||
rangeFormatted(format = 'MMM Do YY, hh:mm A') {
|
||||
console.log('period', this)
|
||||
return this.range.start.format(format) + ' - ' + this.range.end.format(format);
|
||||
},
|
||||
toTimestampstwo() {
|
||||
return {
|
||||
startTimestamp: this.start / 1000,
|
||||
|
|
|
|||
|
|
@ -264,4 +264,55 @@ export const convertElementToImage = async (el) => {
|
|||
},
|
||||
});
|
||||
return image;
|
||||
}
|
||||
|
||||
export const unserscoreToSpaceAndCapitalize = (str) => {
|
||||
return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
export const convertToCSV = (headers, objArray) => {
|
||||
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
|
||||
var str = '';
|
||||
const headersMap = headers.reduce((acc, curr) => {
|
||||
acc[curr.key] = curr;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
str += headers.map(h => h.label).join(',') + '\r\n';
|
||||
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
var line = '';
|
||||
for (var index in headersMap) {
|
||||
if (line !== '') line += ',';
|
||||
line += array[i][index];
|
||||
}
|
||||
str += line + '\r\n';
|
||||
}
|
||||
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
export const exportCSVFile = (headers, items, fileTitle) => {
|
||||
var jsonObject = JSON.stringify(items);
|
||||
var csv = convertToCSV(headers, jsonObject);
|
||||
var exportedFilenmae = fileTitle + '.csv' || 'export.csv';
|
||||
|
||||
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
if (navigator.msSaveBlob) { // IE 10+
|
||||
navigator.msSaveBlob(blob, exportedFilenmae);
|
||||
} else {
|
||||
var link = document.createElement("a");
|
||||
if (link.download !== undefined) {
|
||||
var url = URL.createObjectURL(blob);
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", exportedFilenmae);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ module.exports = {
|
|||
'height',
|
||||
'inset',
|
||||
'justifyContent',
|
||||
'justifySelf',
|
||||
'letterSpacing',
|
||||
'lineHeight',
|
||||
// 'listStylePosition',
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ usr=`whoami`
|
|||
|
||||
# Installing k3s
|
||||
curl -sL https://get.k3s.io | sudo K3S_KUBECONFIG_MODE="644" INSTALL_K3S_VERSION='v1.22.8+k3s1' INSTALL_K3S_EXEC="--no-deploy=traefik" sh -
|
||||
mkdir ~/.kube
|
||||
[[ -d ~/.kube ]] || mkdir ~/.kube
|
||||
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
|
||||
chmod 0644 ~/.kube/config
|
||||
sudo chmod 0644 ~/.kube/config
|
||||
sudo chown -R $usr ~/.kube/config
|
||||
|
||||
|
||||
|
|
|
|||
31
tracker/README.md
Normal file
31
tracker/README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
## Local build
|
||||
|
||||
|
||||
In order to build locally any of the javascript packages located under this directory, go to the corresponding folder first:
|
||||
|
||||
```sh
|
||||
cd tracker # or any tracker-* plugin
|
||||
|
||||
```
|
||||
Then run
|
||||
```sh
|
||||
yarn
|
||||
yarn build
|
||||
```
|
||||
OR
|
||||
|
||||
```sh
|
||||
npm i
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can then use it as a local javascript package by executing the folowing line under your local project location:
|
||||
|
||||
```sh
|
||||
yarn add file:../path/to/openreplay/monorepo/tracker/tracker
|
||||
````
|
||||
OR
|
||||
```sh
|
||||
npm install --save ../path/to/openreplay/monorepo/tracker/tracker
|
||||
```
|
||||
|
||||
62
tracker/tracker-assist/package-lock.json
generated
62
tracker/tracker-assist/package-lock.json
generated
|
|
@ -1,16 +1,16 @@
|
|||
{
|
||||
"name": "@openreplay/tracker-assist",
|
||||
"version": "3.5.7",
|
||||
"version": "3.5.8",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openreplay/tracker-assist",
|
||||
"version": "3.5.7",
|
||||
"version": "3.5.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.10",
|
||||
"peerjs": "^1.3.2",
|
||||
"peerjs": "1.3.2",
|
||||
"socket.io-client": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
},
|
||||
"../tracker": {
|
||||
"name": "@openreplay/tracker",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.11",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -644,15 +644,19 @@
|
|||
}
|
||||
},
|
||||
"../tracker/node_modules/ajv": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
|
||||
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"../tracker/node_modules/ansi-escapes": {
|
||||
|
|
@ -1791,12 +1795,6 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"../tracker/node_modules/json5/node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"../tracker/node_modules/levn": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
|
|
@ -1853,9 +1851,9 @@
|
|||
}
|
||||
},
|
||||
"../tracker/node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"../tracker/node_modules/mkdirp": {
|
||||
|
|
@ -2376,9 +2374,9 @@
|
|||
}
|
||||
},
|
||||
"../tracker/node_modules/strip-ansi/node_modules/ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
|
|
@ -4444,9 +4442,9 @@
|
|||
"requires": {}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
|
||||
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
|
|
@ -5337,14 +5335,6 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"levn": {
|
||||
|
|
@ -5391,9 +5381,9 @@
|
|||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
|
|
@ -5793,9 +5783,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker-assist",
|
||||
"description": "Tracker plugin for screen assistance through the WebRTC",
|
||||
"version": "3.5.8",
|
||||
"version": "3.5.9",
|
||||
"keywords": [
|
||||
"WebRTC",
|
||||
"assistance",
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.10",
|
||||
"peerjs": "^1.3.2",
|
||||
"peerjs": "1.3.2",
|
||||
"socket.io-client": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import AnnotationCanvas from './AnnotationCanvas.js';
|
|||
import ConfirmWindow, { callConfirmDefault, controlConfirmDefault } from './ConfirmWindow.js';
|
||||
import type { Options as ConfirmOptions } from './ConfirmWindow.js';
|
||||
|
||||
// TODO: fully specified strict check (everywhere)
|
||||
|
||||
//@ts-ignore peerjs hack for webpack5 (?!) TODO: ES/node modules;
|
||||
Peer = Peer.default || Peer;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default class RemoteControl {
|
|||
|
||||
reconnect(ids: string[]) {
|
||||
const storedID = sessionStorage.getItem(this.options.session_control_peer_key)
|
||||
if (storedID !== null && ids.includes(storedID)) {
|
||||
if (storedID !== null && ids.indexOf(storedID) !== -1) {
|
||||
this.grantControl(storedID)
|
||||
} else {
|
||||
sessionStorage.removeItem(this.options.session_control_peer_key)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "3.5.10",
|
||||
"version": "3.5.11",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -15,31 +15,31 @@ async function main() {
|
|||
to: webworker.replace(/'/g, "\\'"),
|
||||
});
|
||||
await fs.rename('build/main', 'lib');
|
||||
await fs.rename('build/messages', 'lib/messages');
|
||||
await fs.rename('build/common', 'lib/common');
|
||||
await replaceInFiles({
|
||||
files: 'lib/*',
|
||||
from: /\.\.\/messages/g,
|
||||
to: './messages',
|
||||
from: /\.\.\/common/g,
|
||||
to: './common',
|
||||
});
|
||||
await replaceInFiles({
|
||||
files: 'lib/**/*',
|
||||
from: /\.\.\/\.\.\/messages/g,
|
||||
to: '../messages',
|
||||
from: /\.\.\/\.\.\/common/g,
|
||||
to: '../common',
|
||||
});
|
||||
|
||||
|
||||
await fs.rename('build/cjs/main', 'cjs');
|
||||
await fs.rename('build/cjs/messages', 'cjs/messages');
|
||||
await fs.rename('build/cjs/common', 'cjs/common');
|
||||
await fs.writeFile('cjs/package.json', `{ "type": "commonjs" }`);
|
||||
await replaceInFiles({
|
||||
files: 'cjs/*',
|
||||
from: /\.\.\/messages/g,
|
||||
to: './messages',
|
||||
from: /\.\.\/common/g,
|
||||
to: './common',
|
||||
});
|
||||
await replaceInFiles({
|
||||
files: 'cjs/**/*',
|
||||
from: /\.\.\/\.\.\/messages/g,
|
||||
to: '../messages',
|
||||
from: /\.\.\/\.\.\/common/g,
|
||||
to: '../common',
|
||||
});
|
||||
}
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Auto-generated, do not edit
|
||||
import Message from "./message.js";
|
||||
import Writer from "./writer.js";
|
||||
import type { Writer, Message }from "./types.js";
|
||||
export default Message
|
||||
|
||||
function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
|
||||
Class: C & { new(...args: A): T }
|
||||
10
tracker/tracker/src/common/types.ts
Normal file
10
tracker/tracker/src/common/types.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export interface Writer {
|
||||
uint(n: number): boolean
|
||||
int(n: number): boolean
|
||||
string(s: string): boolean
|
||||
boolean(b: boolean): boolean
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
encode(w: Writer): boolean;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type Message from "../../common/messages.js";
|
||||
import { Timestamp, Metadata } from "../../common/messages.js";
|
||||
import { timestamp, deprecationWarn } from "../utils.js";
|
||||
import { Timestamp, Metadata } from "../../messages/index.js";
|
||||
import Message from "../../messages/message.js";
|
||||
import Nodes from "./nodes.js";
|
||||
import Observer from "./observer/top_observer.js";
|
||||
import Sanitizer from "./sanitizer.js";
|
||||
|
|
@ -13,7 +13,7 @@ import { deviceMemory, jsHeapSizeLimit } from "../modules/performance.js";
|
|||
import type { Options as ObserverOptions } from "./observer/top_observer.js";
|
||||
import type { Options as SanitizerOptions } from "./sanitizer.js";
|
||||
import type { Options as LoggerOptions } from "./logger.js"
|
||||
import type { Options as WebworkerOptions, WorkerMessageData } from "../../webworker/types.js";
|
||||
import type { Options as WebworkerOptions, WorkerMessageData } from "../../common/webworker.js";
|
||||
|
||||
|
||||
// TODO: Unify and clearly describe options logic
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Observer from "./observer.js";
|
||||
import { CreateIFrameDocument } from "../../../messages/index.js";
|
||||
import { CreateIFrameDocument } from "../../../common/messages.js";
|
||||
|
||||
export default class IFrameObserver extends Observer {
|
||||
observe(iframe: HTMLIFrameElement) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
CreateElementNode,
|
||||
MoveNode,
|
||||
RemoveNode,
|
||||
} from "../../../messages/index.js";
|
||||
} from "../../../common/messages.js";
|
||||
import App from "../index.js";
|
||||
import { isInstance, inDocument } from "../context.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Observer from "./observer.js";
|
||||
import { CreateIFrameDocument } from "../../../messages/index.js";
|
||||
import { CreateIFrameDocument } from "../../../common/messages.js";
|
||||
|
||||
export default class ShadowRootObserver extends Observer {
|
||||
observe(el: Element) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { Window } from "../context.js";
|
|||
import IFrameObserver from "./iframe_observer.js";
|
||||
import ShadowRootObserver from "./shadow_root_observer.js";
|
||||
|
||||
import { CreateDocument } from "../../../messages/index.js";
|
||||
import { CreateDocument } from "../../../common/messages.js";
|
||||
import App from "../index.js";
|
||||
import { IN_BROWSER, hasOpenreplayAttribute } from '../../utils.js'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import App, { StartOptions } from "./index.js";
|
||||
import { UserID, UserAnonymousID, Metadata } from "../../messages/index.js";
|
||||
import { UserID, UserAnonymousID, Metadata } from "../../common/messages.js";
|
||||
|
||||
|
||||
enum ActivityState {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import App, { DEFAULT_INGEST_POINT } from "./app/index.js";
|
||||
export { default as App } from './app/index.js';
|
||||
|
||||
import { UserID, UserAnonymousID, Metadata, RawCustomEvent, CustomIssue } from "../messages/index.js";
|
||||
import * as _Messages from "../messages/index.js";
|
||||
import { UserID, UserAnonymousID, Metadata, RawCustomEvent, CustomIssue } from "../common/messages.js";
|
||||
import * as _Messages from "../common/messages.js";
|
||||
export const Messages = _Messages;
|
||||
|
||||
import Connection from "./modules/connection.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import App from "../app/index.js";
|
||||
import { ConnectionInformation } from "../../messages/index.js";
|
||||
import { ConnectionInformation } from "../../common/messages.js";
|
||||
|
||||
export default function(app: App): void {
|
||||
const connection:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import App from "../app/index.js";
|
||||
import { IN_BROWSER } from "../utils.js";
|
||||
import { ConsoleLog } from "../../messages/index.js";
|
||||
import { ConsoleLog } from "../../common/messages.js";
|
||||
|
||||
const printError: (e: Error) => string =
|
||||
IN_BROWSER && 'InstallTrigger' in window // detect Firefox
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import App from "../app/index.js";
|
||||
import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from "../../messages/index.js";
|
||||
import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from "../../common/messages.js";
|
||||
|
||||
export default function(app: App | null) {
|
||||
if (app === null) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type Message from "../../common/messages.js";
|
||||
import App from "../app/index.js";
|
||||
import { JSException } from "../../messages/index.js";
|
||||
import Message from "../../messages/message.js";
|
||||
import { JSException } from "../../common/messages.js";
|
||||
import ErrorStackParser from 'error-stack-parser';
|
||||
|
||||
export interface Options {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { timestamp, isURL } from "../utils.js";
|
||||
import App from "../app/index.js";
|
||||
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from "../../messages/index.js";
|
||||
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from "../../common/messages.js";
|
||||
|
||||
const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
hasOpenreplayAttribute,
|
||||
} from "../utils.js";
|
||||
import App from "../app/index.js";
|
||||
import { SetInputTarget, SetInputValue, SetInputChecked } from "../../messages/index.js";
|
||||
import { SetInputTarget, SetInputValue, SetInputChecked } from "../../common/messages.js";
|
||||
|
||||
// TODO: take into consideration "contenteditable" attribute
|
||||
type TextEditableElement = HTMLInputElement | HTMLTextAreaElement
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import App from "../app/index.js";
|
||||
import { LongTask } from "../../messages/index.js";
|
||||
import { LongTask } from "../../common/messages.js";
|
||||
|
||||
// https://w3c.github.io/performance-timeline/#the-performanceentry-interface
|
||||
interface TaskAttributionTiming extends PerformanceEntry {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
getLabelAttribute,
|
||||
} from "../utils.js";
|
||||
import App from "../app/index.js";
|
||||
import { MouseMove, MouseClick } from "../../messages/index.js";
|
||||
import { MouseMove, MouseClick } from "../../common/messages.js";
|
||||
import { getInputLabel } from "./input.js";
|
||||
|
||||
function _getSelector(target: Element): string {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import App from "../app/index.js";
|
||||
import { IN_BROWSER } from "../utils.js";
|
||||
import { PerformanceTrack } from "../../messages/index.js";
|
||||
import { PerformanceTrack } from "../../common/messages.js";
|
||||
|
||||
|
||||
type Perf = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import App from "../app/index.js";
|
||||
import { SetViewportScroll, SetNodeScroll } from "../../messages/index.js";
|
||||
import { SetViewportScroll, SetNodeScroll } from "../../common/messages.js";
|
||||
|
||||
export default function (app: App): void {
|
||||
let documentScroll = false;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue