From 2f6a9258fca577bdad07a584e0c0087575bfe516 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 14 Feb 2022 20:26:20 +0100 Subject: [PATCH 01/15] feat(utilities): WS reduce maxHttpBufferSize feat(utilities): WS log status each 30s --- utilities/build.sh | 8 ++++---- utilities/servers/websocket.js | 22 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/utilities/build.sh b/utilities/build.sh index 99be144fe..9855d1b26 100644 --- a/utilities/build.sh +++ b/utilities/build.sh @@ -20,11 +20,11 @@ function build_api(){ [[ $1 == "ee" ]] && { cp -rf ../ee/utilities/* ./ } - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:${git_sha1} . + docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:1.5.0 . [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/utilities:${git_sha1} - docker tag ${DOCKER_REPO:-'local'}/utilities:${git_sha1} ${DOCKER_REPO:-'local'}/utilities:latest - docker push ${DOCKER_REPO:-'local'}/utilities:latest + docker push ${DOCKER_REPO:-'local'}/utilities:1.5.0 + docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:1.5.0-ee . + docker push ${DOCKER_REPO:-'local'}/utilities:1.5.0-ee } } diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index 96364f4a8..f96674b4e 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -161,7 +161,7 @@ module.exports = { wsRouter, start: (server) => { io = _io(server, { - maxHttpBufferSize: 7e6, + maxHttpBufferSize: 1e6, cors: { origin: "*", methods: ["GET", "POST", "PUT"] @@ -252,5 +252,25 @@ module.exports = { }); console.log("WS server started") + setInterval((io) => { + try { + let count = 0; + console.log(` ====== Rooms: ${io.sockets.adapter.rooms.size} ====== `); + const arr = Array.from(io.sockets.adapter.rooms) + const filtered = arr.filter(room => !room[1].has(room[0])) + for (let i of filtered) { + let {projectKey, sessionId} = extractPeerId(i[0]); + if (projectKey !== null && sessionId !== null) { + count++; + } + } + console.log(` ====== Valid Rooms: ${count} ====== `); + for (let item of filtered) { + console.log(`Room: ${item[0]} connected: ${item[1].size}`) + } + } catch (e) { + console.error(e); + } + }, 30000, io); } }; \ No newline at end of file From f0cc51821bf9ee53617054eac10e74d731762487 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Mon, 14 Feb 2022 20:35:58 +0100 Subject: [PATCH 02/15] feat(utilities): WS changed build script --- utilities/build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utilities/build.sh b/utilities/build.sh index 9855d1b26..dd1413064 100644 --- a/utilities/build.sh +++ b/utilities/build.sh @@ -20,11 +20,11 @@ function build_api(){ [[ $1 == "ee" ]] && { cp -rf ../ee/utilities/* ./ } - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:1.5.0 . + docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:v1.5.0 . [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/utilities:1.5.0 - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:1.5.0-ee . - docker push ${DOCKER_REPO:-'local'}/utilities:1.5.0-ee + docker push ${DOCKER_REPO:-'local'}/utilities:v1.5.0 + docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:v1.5.0-ee . + docker push ${DOCKER_REPO:-'local'}/utilities:v1.5.0-ee } } From 8f7fad5288d448a99e213365a67e24e6d5699e91 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 15 Feb 2022 16:18:18 +0100 Subject: [PATCH 03/15] feat(utilities): WS disable logs --- utilities/server.js | 7 ++-- utilities/servers/websocket.js | 60 ++++++++++++++++------------------ 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/utilities/server.js b/utilities/server.js index 89d67d5a3..661ef081c 100644 --- a/utilities/server.js +++ b/utilities/server.js @@ -9,11 +9,14 @@ const PORT = 9000; var app = express(); var wsapp = express(); +let debug = process.env.debug === "1" || false; const request_logger = (identity) => { return (req, res, next) => { - console.log(identity,new Date().toTimeString(), 'REQUEST', req.method, req.originalUrl); + debug && console.log(identity, new Date().toTimeString(), 'REQUEST', req.method, req.originalUrl); res.on('finish', function () { - console.log(new Date().toTimeString(), 'RESPONSE', req.method, req.originalUrl, this.statusCode); + if (this.statusCode !== 200 || debug) { + console.log(new Date().toTimeString(), 'RESPONSE', req.method, req.originalUrl, this.statusCode); + } }) next(); diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index f96674b4e..ab6a2c4d5 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -14,9 +14,9 @@ const SESSION_ALREADY_CONNECTED = "SESSION_ALREADY_CONNECTED"; // const wsReconnectionTimeout = process.env.wsReconnectionTimeout | 10 * 1000; let io; - +let debug = process.env.debug === "1" || false; wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) { - console.log("[WS]looking for all available sessions"); + debug && console.log("[WS]looking for all available sessions"); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { let {projectKey, sessionId} = extractPeerId(peerId); @@ -30,7 +30,7 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) { res.end(JSON.stringify({"data": liveSessions})); }); wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, res) { - console.log(`[WS]looking for available sessions for ${req.params.projectKey}`); + debug && console.log(`[WS]looking for available sessions for ${req.params.projectKey}`); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { let {projectKey, sessionId} = extractPeerId(peerId); @@ -45,7 +45,7 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, r }); wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) { - console.log("[WS]looking for all available LIVE sessions"); + debug && console.log("[WS]looking for all available LIVE sessions"); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { let {projectKey, sessionId} = extractPeerId(peerId); @@ -65,7 +65,7 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) { res.end(JSON.stringify({"data": liveSessions})); }); wsRouter.get(`/${process.env.S3_KEY}/sockets-live/:projectKey`, async function (req, res) { - console.log(`[WS]looking for available LIVE sessions for ${req.params.projectKey}`); + debug && console.log(`[WS]looking for available LIVE sessions for ${req.params.projectKey}`); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { let {projectKey, sessionId} = extractPeerId(peerId); @@ -128,8 +128,8 @@ async function get_all_agents_ids(io, socket) { function extractSessionInfo(socket) { if (socket.handshake.query.sessionInfo !== undefined) { - console.log("received headers"); - console.log(socket.handshake.headers); + debug && console.log("received headers"); + debug && console.log(socket.handshake.headers); socket.handshake.query.sessionInfo = JSON.parse(socket.handshake.query.sessionInfo); let ua = uaParser(socket.handshake.headers['user-agent']); @@ -146,8 +146,8 @@ function extractSessionInfo(socket) { // console.log("Looking for MMDB file in " + process.env.MAXMINDDB_FILE); geoip2Reader.open(process.env.MAXMINDDB_FILE, options) .then(reader => { - console.log("looking for location of "); - console.log(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); + debug && console.log("looking for location of "); + debug && console.log(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); let country = reader.country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; }) @@ -170,7 +170,7 @@ module.exports = { }); io.on('connection', async (socket) => { - console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`); + debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`); socket.peerId = socket.handshake.query.peerId; socket.identity = socket.handshake.query.identity; const {projectKey, sessionId} = extractPeerId(socket.peerId); @@ -180,24 +180,24 @@ module.exports = { let {c_sessions, c_agents} = await sessions_agents_count(io, socket); if (socket.identity === IDENTITIES.session) { if (c_sessions > 0) { - console.log(`session already connected, refusing new connexion`); + debug && console.log(`session already connected, refusing new connexion`); io.to(socket.id).emit(SESSION_ALREADY_CONNECTED); return socket.disconnect(); } extractSessionInfo(socket); if (c_agents > 0) { - console.log(`notifying new session about agent-existence`); + debug && console.log(`notifying new session about agent-existence`); let agents_ids = await get_all_agents_ids(io, socket); io.to(socket.id).emit(AGENTS_CONNECTED, agents_ids); } } else if (c_sessions <= 0) { - console.log(`notifying new agent about no SESSIONS`); + debug && console.log(`notifying new agent about no SESSIONS`); io.to(socket.id).emit(NO_SESSIONS); } socket.join(socket.peerId); if (io.sockets.adapter.rooms.get(socket.peerId)) { - console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); + debug && console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); } if (socket.identity === IDENTITIES.agent) { if (socket.handshake.query.agentInfo !== undefined) { @@ -207,44 +207,38 @@ module.exports = { } socket.on('disconnect', async () => { - // console.log(`${socket.id} disconnected from ${socket.peerId}, waiting ${wsReconnectionTimeout / 1000}s before checking remaining`); - console.log(`${socket.id} disconnected from ${socket.peerId}`); + debug && console.log(`${socket.id} disconnected from ${socket.peerId}`); if (socket.identity === IDENTITIES.agent) { socket.to(socket.peerId).emit(AGENT_DISCONNECT, socket.id); } - // wait a little bit before notifying everyone - // setTimeout(async () => { - console.log("checking for number of connected agents and sessions"); + debug && console.log("checking for number of connected agents and sessions"); let {c_sessions, c_agents} = await sessions_agents_count(io, socket); if (c_sessions === -1 && c_agents === -1) { - console.log(`room not found: ${socket.peerId}`); + debug && console.log(`room not found: ${socket.peerId}`); } if (c_sessions === 0) { - console.log(`notifying everyone in ${socket.peerId} about no SESSIONS`); + debug && console.log(`notifying everyone in ${socket.peerId} about no SESSIONS`); socket.to(socket.peerId).emit(NO_SESSIONS); } if (c_agents === 0) { - console.log(`notifying everyone in ${socket.peerId} about no AGENTS`); + debug && console.log(`notifying everyone in ${socket.peerId} about no AGENTS`); socket.to(socket.peerId).emit(NO_AGENTS); } - - - // }, wsReconnectionTimeout); }); socket.onAny(async (eventName, ...args) => { socket.lastMessageReceivedAt = Date.now(); if (socket.identity === IDENTITIES.session) { - console.log(`received event:${eventName}, from:${socket.identity}, sending message to room:${socket.peerId}, members: ${io.sockets.adapter.rooms.get(socket.peerId).size}`); + debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to room:${socket.peerId}, members: ${io.sockets.adapter.rooms.get(socket.peerId).size}`); socket.to(socket.peerId).emit(eventName, args[0]); } else { - console.log(`received event:${eventName}, from:${socket.identity}, sending message to session of room:${socket.peerId}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); + debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to session of room:${socket.peerId}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); let socketId = await findSessionSocketId(io, socket.peerId); if (socketId === null) { - console.log(`session not found for:${socket.peerId}`); + debug && console.log(`session not found for:${socket.peerId}`); io.to(socket.id).emit(NO_SESSIONS); } else { - console.log("message sent"); + debug && console.log("message sent"); io.to(socketId).emit(eventName, socket.id, args[0]); } } @@ -265,12 +259,14 @@ module.exports = { } } console.log(` ====== Valid Rooms: ${count} ====== `); - for (let item of filtered) { - console.log(`Room: ${item[0]} connected: ${item[1].size}`) + if (debug) { + for (let item of filtered) { + console.log(`Room: ${item[0]} connected: ${item[1].size}`) + } } } catch (e) { console.error(e); } - }, 30000, io); + }, 20000, io); } }; \ No newline at end of file From 7df30d6ac44699db07d2124da5cac3bed4f91c05 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 15 Feb 2022 17:36:36 +0100 Subject: [PATCH 04/15] feat(utilities): upgraded base image --- utilities/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/Dockerfile b/utilities/Dockerfile index d6e207cdc..9b82358b3 100644 --- a/utilities/Dockerfile +++ b/utilities/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.22-stretch +FROM node:17-stretch WORKDIR /work COPY . . RUN npm install From ab468ddf417830cebe473c11992f832bd44b65b6 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 15 Feb 2022 20:58:08 +0100 Subject: [PATCH 05/15] feat(utilities): WS use uWS --- utilities/package-lock.json | 11 +- utilities/package.json | 3 +- utilities/server.js | 46 ++++- utilities/server_back.js | 53 ++++++ utilities/servers/websocket.js | 84 ++++----- utilities/servers/websocket_back.js | 272 ++++++++++++++++++++++++++++ 6 files changed, 406 insertions(+), 63 deletions(-) create mode 100644 utilities/server_back.js create mode 100644 utilities/servers/websocket_back.js diff --git a/utilities/package-lock.json b/utilities/package-lock.json index 5ff7612c2..8fe161f1a 100644 --- a/utilities/package-lock.json +++ b/utilities/package-lock.json @@ -15,7 +15,8 @@ "peer": "^0.6.1", "socket.io": "^4.4.1", "source-map": "^0.7.3", - "ua-parser-js": "^1.0.2" + "ua-parser-js": "^1.0.2", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.6.0" } }, "node_modules/@maxmind/geoip2-node": { @@ -1287,6 +1288,10 @@ "uuid": "bin/uuid" } }, + "node_modules/uWebSockets.js": { + "version": "20.6.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#a58e810e47a23696410f6073c8c905dc38f75da5" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2350,6 +2355,10 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, + "uWebSockets.js": { + "version": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#a58e810e47a23696410f6073c8c905dc38f75da5", + "from": "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.6.0" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/utilities/package.json b/utilities/package.json index 452c3fe00..79c8513a4 100644 --- a/utilities/package.json +++ b/utilities/package.json @@ -24,6 +24,7 @@ "peer": "^0.6.1", "socket.io": "^4.4.1", "source-map": "^0.7.3", - "ua-parser-js": "^1.0.2" + "ua-parser-js": "^1.0.2", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.6.0" } } diff --git a/utilities/server.js b/utilities/server.js index 661ef081c..cbae8f5ff 100644 --- a/utilities/server.js +++ b/utilities/server.js @@ -8,7 +8,6 @@ const HOST = '0.0.0.0'; const PORT = 9000; var app = express(); -var wsapp = express(); let debug = process.env.debug === "1" || false; const request_logger = (identity) => { return (req, res, next) => { @@ -23,20 +22,15 @@ const request_logger = (identity) => { } }; app.use(request_logger("[app]")); -wsapp.use(request_logger("[wsapp]")); app.use('/sourcemaps', sourcemapsReaderServer); app.use('/assist', peerRouter); -wsapp.use('/assist', socket.wsRouter); const server = app.listen(PORT, HOST, () => { console.log(`App listening on http://${HOST}:${PORT}`); console.log('Press Ctrl+C to quit.'); }); -const wsserver = wsapp.listen(PORT + 1, HOST, () => { - console.log(`WS App listening on http://${HOST}:${PORT + 1}`); - console.log('Press Ctrl+C to quit.'); -}); + const peerServer = ExpressPeerServer(server, { debug: true, path: '/', @@ -48,6 +42,38 @@ peerServer.on('disconnect', peerDisconnect); peerServer.on('error', peerError); app.use('/', peerServer); app.enable('trust proxy'); -wsapp.enable('trust proxy'); -socket.start(wsserver); -module.exports = {wsserver, server}; + + +const {App} = require("uWebSockets.js"); +const PREFIX = process.env.prefix || '/assist' + +const uapp = new App(); + +const healthFn = (res, req) => { + res.writeStatus('200 OK').end('ok!'); +} +uapp.get(PREFIX, healthFn); +uapp.get(`${PREFIX}/`, healthFn); + +const uWrapper = function (fn) { + return (res, req) => fn(req, res); +} +uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-list`, uWrapper(socket.handlers.socketsList)); +uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-list/:projectKey`, uWrapper(socket.handlers.socketsListByProject)); + +uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-live`, uWrapper(socket.handlers.socketsLive)); +uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-live/:projectKey`, uWrapper(socket.handlers.socketsLiveByProject)); + + +socket.start(uapp); + +uapp.listen(HOST, PORT + 1, (token) => { + if (!token) { + console.warn("port already in use"); + } + console.log(`WS App listening on http://${HOST}:${PORT + 1}`); + console.log('Press Ctrl+C to quit.'); +}); + + +module.exports = {uapp, server}; diff --git a/utilities/server_back.js b/utilities/server_back.js new file mode 100644 index 000000000..661ef081c --- /dev/null +++ b/utilities/server_back.js @@ -0,0 +1,53 @@ +var sourcemapsReaderServer = require('./servers/sourcemaps-server'); +var {peerRouter, peerConnection, peerDisconnect, peerError} = require('./servers/peerjs-server'); +var express = require('express'); +const {ExpressPeerServer} = require('peer'); +const socket = require("./servers/websocket"); + +const HOST = '0.0.0.0'; +const PORT = 9000; + +var app = express(); +var wsapp = express(); +let debug = process.env.debug === "1" || false; +const request_logger = (identity) => { + return (req, res, next) => { + debug && console.log(identity, new Date().toTimeString(), 'REQUEST', req.method, req.originalUrl); + res.on('finish', function () { + if (this.statusCode !== 200 || debug) { + console.log(new Date().toTimeString(), 'RESPONSE', req.method, req.originalUrl, this.statusCode); + } + }) + + next(); + } +}; +app.use(request_logger("[app]")); +wsapp.use(request_logger("[wsapp]")); + +app.use('/sourcemaps', sourcemapsReaderServer); +app.use('/assist', peerRouter); +wsapp.use('/assist', socket.wsRouter); + +const server = app.listen(PORT, HOST, () => { + console.log(`App listening on http://${HOST}:${PORT}`); + console.log('Press Ctrl+C to quit.'); +}); +const wsserver = wsapp.listen(PORT + 1, HOST, () => { + console.log(`WS App listening on http://${HOST}:${PORT + 1}`); + console.log('Press Ctrl+C to quit.'); +}); +const peerServer = ExpressPeerServer(server, { + debug: true, + path: '/', + proxied: true, + allow_discovery: false +}); +peerServer.on('connection', peerConnection); +peerServer.on('disconnect', peerDisconnect); +peerServer.on('error', peerError); +app.use('/', peerServer); +app.enable('trust proxy'); +wsapp.enable('trust proxy'); +socket.start(wsserver); +module.exports = {wsserver, server}; diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index ab6a2c4d5..ddc40ff0d 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -1,9 +1,7 @@ const _io = require('socket.io'); -const express = require('express'); const uaParser = require('ua-parser-js'); const geoip2Reader = require('@maxmind/geoip2-node').Reader; var {extractPeerId} = require('./peerjs-server'); -var wsRouter = express.Router(); const IDENTITIES = {agent: 'agent', session: 'session'}; const NEW_AGENT = "NEW_AGENT"; const NO_AGENTS = "NO_AGENT"; @@ -11,11 +9,11 @@ const AGENT_DISCONNECT = "AGENT_DISCONNECTED"; const AGENTS_CONNECTED = "AGENTS_CONNECTED"; const NO_SESSIONS = "SESSION_DISCONNECTED"; const SESSION_ALREADY_CONNECTED = "SESSION_ALREADY_CONNECTED"; -// const wsReconnectionTimeout = process.env.wsReconnectionTimeout | 10 * 1000; let io; + let debug = process.env.debug === "1" || false; -wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) { +const socketsList = function (req, res) { debug && console.log("[WS]looking for all available sessions"); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { @@ -25,11 +23,10 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) { liveSessions[projectKey].push(sessionId); } } - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({"data": liveSessions})); -}); -wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, res) { + res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions})); +} +const socketsListByProject = function (req, res) { + req.params = {projectKey: req.getParameter(0)}; debug && console.log(`[WS]looking for available sessions for ${req.params.projectKey}`); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { @@ -39,12 +36,9 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, r liveSessions[projectKey].push(sessionId); } } - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []})); -}); - -wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) { + res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []})); +} +const socketsLive = async function (req, res) { debug && console.log("[WS]looking for all available LIVE sessions"); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { @@ -59,12 +53,10 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) { } } } - - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({"data": liveSessions})); -}); -wsRouter.get(`/${process.env.S3_KEY}/sockets-live/:projectKey`, async function (req, res) { + res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions})); +} +const socketsLiveByProject = async function (req, res) { + req.params = {projectKey: req.getParameter(0)}; debug && console.log(`[WS]looking for available LIVE sessions for ${req.params.projectKey}`); let liveSessions = {}; for (let peerId of io.sockets.adapter.rooms.keys()) { @@ -79,10 +71,8 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-live/:projectKey`, async function ( } } } - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []})); -}); + res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []})); +} const findSessionSocketId = async (io, peerId) => { const connected_sockets = await io.in(peerId).fetchSockets(); @@ -158,16 +148,17 @@ function extractSessionInfo(socket) { } module.exports = { - wsRouter, start: (server) => { - io = _io(server, { + io = new _io.Server({ maxHttpBufferSize: 1e6, cors: { origin: "*", methods: ["GET", "POST", "PUT"] }, - path: '/socket' + path: '/ws-assist/socket' }); + io.attachApp(server); + io.on('connection', async (socket) => { debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`); @@ -207,10 +198,13 @@ module.exports = { } socket.on('disconnect', async () => { + // console.log(`${socket.id} disconnected from ${socket.peerId}, waiting ${wsReconnectionTimeout / 1000}s before checking remaining`); debug && console.log(`${socket.id} disconnected from ${socket.peerId}`); if (socket.identity === IDENTITIES.agent) { socket.to(socket.peerId).emit(AGENT_DISCONNECT, socket.id); } + // wait a little bit before notifying everyone + // setTimeout(async () => { debug && console.log("checking for number of connected agents and sessions"); let {c_sessions, c_agents} = await sessions_agents_count(io, socket); if (c_sessions === -1 && c_agents === -1) { @@ -224,6 +218,9 @@ module.exports = { debug && console.log(`notifying everyone in ${socket.peerId} about no AGENTS`); socket.to(socket.peerId).emit(NO_AGENTS); } + + + // }, wsReconnectionTimeout); }); socket.onAny(async (eventName, ...args) => { @@ -245,28 +242,13 @@ module.exports = { }); }); - console.log("WS server started") - setInterval((io) => { - try { - let count = 0; - console.log(` ====== Rooms: ${io.sockets.adapter.rooms.size} ====== `); - const arr = Array.from(io.sockets.adapter.rooms) - const filtered = arr.filter(room => !room[1].has(room[0])) - for (let i of filtered) { - let {projectKey, sessionId} = extractPeerId(i[0]); - if (projectKey !== null && sessionId !== null) { - count++; - } - } - console.log(` ====== Valid Rooms: ${count} ====== `); - if (debug) { - for (let item of filtered) { - console.log(`Room: ${item[0]} connected: ${item[1].size}`) - } - } - } catch (e) { - console.error(e); - } - }, 20000, io); + console.log("WS server started"); + debug ? console.log("Debugging enabled.") : console.log("Debugging disabled, set debug=\"1\" to enable debugging."); + }, + handlers: { + socketsList, + socketsListByProject, + socketsLive, + socketsLiveByProject } }; \ No newline at end of file diff --git a/utilities/servers/websocket_back.js b/utilities/servers/websocket_back.js new file mode 100644 index 000000000..ab6a2c4d5 --- /dev/null +++ b/utilities/servers/websocket_back.js @@ -0,0 +1,272 @@ +const _io = require('socket.io'); +const express = require('express'); +const uaParser = require('ua-parser-js'); +const geoip2Reader = require('@maxmind/geoip2-node').Reader; +var {extractPeerId} = require('./peerjs-server'); +var wsRouter = express.Router(); +const IDENTITIES = {agent: 'agent', session: 'session'}; +const NEW_AGENT = "NEW_AGENT"; +const NO_AGENTS = "NO_AGENT"; +const AGENT_DISCONNECT = "AGENT_DISCONNECTED"; +const AGENTS_CONNECTED = "AGENTS_CONNECTED"; +const NO_SESSIONS = "SESSION_DISCONNECTED"; +const SESSION_ALREADY_CONNECTED = "SESSION_ALREADY_CONNECTED"; +// const wsReconnectionTimeout = process.env.wsReconnectionTimeout | 10 * 1000; + +let io; +let debug = process.env.debug === "1" || false; +wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) { + debug && console.log("[WS]looking for all available sessions"); + let liveSessions = {}; + for (let peerId of io.sockets.adapter.rooms.keys()) { + let {projectKey, sessionId} = extractPeerId(peerId); + if (projectKey !== undefined) { + liveSessions[projectKey] = liveSessions[projectKey] || []; + liveSessions[projectKey].push(sessionId); + } + } + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({"data": liveSessions})); +}); +wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, res) { + debug && console.log(`[WS]looking for available sessions for ${req.params.projectKey}`); + let liveSessions = {}; + for (let peerId of io.sockets.adapter.rooms.keys()) { + let {projectKey, sessionId} = extractPeerId(peerId); + if (projectKey === req.params.projectKey) { + liveSessions[projectKey] = liveSessions[projectKey] || []; + liveSessions[projectKey].push(sessionId); + } + } + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []})); +}); + +wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) { + debug && console.log("[WS]looking for all available LIVE sessions"); + let liveSessions = {}; + for (let peerId of io.sockets.adapter.rooms.keys()) { + let {projectKey, sessionId} = extractPeerId(peerId); + if (projectKey !== undefined) { + let connected_sockets = await io.in(peerId).fetchSockets(); + for (let item of connected_sockets) { + if (item.handshake.query.identity === IDENTITIES.session) { + liveSessions[projectKey] = liveSessions[projectKey] || []; + liveSessions[projectKey].push(item.handshake.query.sessionInfo); + } + } + } + } + + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({"data": liveSessions})); +}); +wsRouter.get(`/${process.env.S3_KEY}/sockets-live/:projectKey`, async function (req, res) { + debug && console.log(`[WS]looking for available LIVE sessions for ${req.params.projectKey}`); + let liveSessions = {}; + for (let peerId of io.sockets.adapter.rooms.keys()) { + let {projectKey, sessionId} = extractPeerId(peerId); + if (projectKey === req.params.projectKey) { + let connected_sockets = await io.in(peerId).fetchSockets(); + for (let item of connected_sockets) { + if (item.handshake.query.identity === IDENTITIES.session) { + liveSessions[projectKey] = liveSessions[projectKey] || []; + liveSessions[projectKey].push(item.handshake.query.sessionInfo); + } + } + } + } + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []})); +}); + +const findSessionSocketId = async (io, peerId) => { + const connected_sockets = await io.in(peerId).fetchSockets(); + for (let item of connected_sockets) { + if (item.handshake.query.identity === IDENTITIES.session) { + return item.id; + } + } + return null; +}; + +async function sessions_agents_count(io, socket) { + let c_sessions = 0, c_agents = 0; + if (io.sockets.adapter.rooms.get(socket.peerId)) { + const connected_sockets = await io.in(socket.peerId).fetchSockets(); + + for (let item of connected_sockets) { + if (item.handshake.query.identity === IDENTITIES.session) { + c_sessions++; + } else { + c_agents++; + } + } + } else { + c_agents = -1; + c_sessions = -1; + } + return {c_sessions, c_agents}; +} + +async function get_all_agents_ids(io, socket) { + let agents = []; + if (io.sockets.adapter.rooms.get(socket.peerId)) { + const connected_sockets = await io.in(socket.peerId).fetchSockets(); + for (let item of connected_sockets) { + if (item.handshake.query.identity === IDENTITIES.agent) { + agents.push(item.id); + } + } + } + return agents; +} + +function extractSessionInfo(socket) { + if (socket.handshake.query.sessionInfo !== undefined) { + debug && console.log("received headers"); + debug && console.log(socket.handshake.headers); + socket.handshake.query.sessionInfo = JSON.parse(socket.handshake.query.sessionInfo); + + let ua = uaParser(socket.handshake.headers['user-agent']); + socket.handshake.query.sessionInfo.userOs = ua.os.name || null; + socket.handshake.query.sessionInfo.userBrowser = ua.browser.name || null; + socket.handshake.query.sessionInfo.userBrowserVersion = ua.browser.version || null; + socket.handshake.query.sessionInfo.userDevice = ua.device.model || null; + socket.handshake.query.sessionInfo.userDeviceType = ua.device.type || 'desktop'; + socket.handshake.query.sessionInfo.userCountry = null; + + const options = { + // you can use options like `cache` or `watchForUpdates` + }; + // console.log("Looking for MMDB file in " + process.env.MAXMINDDB_FILE); + geoip2Reader.open(process.env.MAXMINDDB_FILE, options) + .then(reader => { + debug && console.log("looking for location of "); + debug && console.log(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); + let country = reader.country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); + socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; + }) + .catch(error => { + console.error(error); + }); + } +} + +module.exports = { + wsRouter, + start: (server) => { + io = _io(server, { + maxHttpBufferSize: 1e6, + cors: { + origin: "*", + methods: ["GET", "POST", "PUT"] + }, + path: '/socket' + }); + + io.on('connection', async (socket) => { + debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`); + socket.peerId = socket.handshake.query.peerId; + socket.identity = socket.handshake.query.identity; + const {projectKey, sessionId} = extractPeerId(socket.peerId); + socket.sessionId = sessionId; + socket.projectKey = projectKey; + socket.lastMessageReceivedAt = Date.now(); + let {c_sessions, c_agents} = await sessions_agents_count(io, socket); + if (socket.identity === IDENTITIES.session) { + if (c_sessions > 0) { + debug && console.log(`session already connected, refusing new connexion`); + io.to(socket.id).emit(SESSION_ALREADY_CONNECTED); + return socket.disconnect(); + } + extractSessionInfo(socket); + if (c_agents > 0) { + debug && console.log(`notifying new session about agent-existence`); + let agents_ids = await get_all_agents_ids(io, socket); + io.to(socket.id).emit(AGENTS_CONNECTED, agents_ids); + } + + } else if (c_sessions <= 0) { + debug && console.log(`notifying new agent about no SESSIONS`); + io.to(socket.id).emit(NO_SESSIONS); + } + socket.join(socket.peerId); + if (io.sockets.adapter.rooms.get(socket.peerId)) { + debug && console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); + } + if (socket.identity === IDENTITIES.agent) { + if (socket.handshake.query.agentInfo !== undefined) { + socket.handshake.query.agentInfo = JSON.parse(socket.handshake.query.agentInfo); + } + socket.to(socket.peerId).emit(NEW_AGENT, socket.id, socket.handshake.query.agentInfo); + } + + socket.on('disconnect', async () => { + debug && console.log(`${socket.id} disconnected from ${socket.peerId}`); + if (socket.identity === IDENTITIES.agent) { + socket.to(socket.peerId).emit(AGENT_DISCONNECT, socket.id); + } + debug && console.log("checking for number of connected agents and sessions"); + let {c_sessions, c_agents} = await sessions_agents_count(io, socket); + if (c_sessions === -1 && c_agents === -1) { + debug && console.log(`room not found: ${socket.peerId}`); + } + if (c_sessions === 0) { + debug && console.log(`notifying everyone in ${socket.peerId} about no SESSIONS`); + socket.to(socket.peerId).emit(NO_SESSIONS); + } + if (c_agents === 0) { + debug && console.log(`notifying everyone in ${socket.peerId} about no AGENTS`); + socket.to(socket.peerId).emit(NO_AGENTS); + } + }); + + socket.onAny(async (eventName, ...args) => { + socket.lastMessageReceivedAt = Date.now(); + if (socket.identity === IDENTITIES.session) { + debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to room:${socket.peerId}, members: ${io.sockets.adapter.rooms.get(socket.peerId).size}`); + socket.to(socket.peerId).emit(eventName, args[0]); + } else { + debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to session of room:${socket.peerId}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); + let socketId = await findSessionSocketId(io, socket.peerId); + if (socketId === null) { + debug && console.log(`session not found for:${socket.peerId}`); + io.to(socket.id).emit(NO_SESSIONS); + } else { + debug && console.log("message sent"); + io.to(socketId).emit(eventName, socket.id, args[0]); + } + } + }); + + }); + console.log("WS server started") + setInterval((io) => { + try { + let count = 0; + console.log(` ====== Rooms: ${io.sockets.adapter.rooms.size} ====== `); + const arr = Array.from(io.sockets.adapter.rooms) + const filtered = arr.filter(room => !room[1].has(room[0])) + for (let i of filtered) { + let {projectKey, sessionId} = extractPeerId(i[0]); + if (projectKey !== null && sessionId !== null) { + count++; + } + } + console.log(` ====== Valid Rooms: ${count} ====== `); + if (debug) { + for (let item of filtered) { + console.log(`Room: ${item[0]} connected: ${item[1].size}`) + } + } + } catch (e) { + console.error(e); + } + }, 20000, io); + } +}; \ No newline at end of file From 99cabe2dda45f987b536e74ea3e6b228a4a5921a Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 15 Feb 2022 21:44:41 +0100 Subject: [PATCH 06/15] feat(utilities): WS changed build script --- utilities/build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utilities/build.sh b/utilities/build.sh index dd1413064..99be144fe 100644 --- a/utilities/build.sh +++ b/utilities/build.sh @@ -20,11 +20,11 @@ function build_api(){ [[ $1 == "ee" ]] && { cp -rf ../ee/utilities/* ./ } - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:v1.5.0 . + docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:${git_sha1} . [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/utilities:v1.5.0 - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/utilities:v1.5.0-ee . - docker push ${DOCKER_REPO:-'local'}/utilities:v1.5.0-ee + docker push ${DOCKER_REPO:-'local'}/utilities:${git_sha1} + docker tag ${DOCKER_REPO:-'local'}/utilities:${git_sha1} ${DOCKER_REPO:-'local'}/utilities:latest + docker push ${DOCKER_REPO:-'local'}/utilities:latest } } From 0395c79986a13c9d31ef6677b50b5337f5841e97 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Tue, 15 Feb 2022 21:52:22 +0100 Subject: [PATCH 07/15] feat(utilities): WS log stats --- utilities/servers/websocket.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index ddc40ff0d..f2e16884a 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -244,6 +244,29 @@ module.exports = { }); console.log("WS server started"); debug ? console.log("Debugging enabled.") : console.log("Debugging disabled, set debug=\"1\" to enable debugging."); + + setInterval((io) => { + try { + let count = 0; + console.log(` ====== Rooms: ${io.sockets.adapter.rooms.size} ====== `); + const arr = Array.from(io.sockets.adapter.rooms) + const filtered = arr.filter(room => !room[1].has(room[0])) + for (let i of filtered) { + let {projectKey, sessionId} = extractPeerId(i[0]); + if (projectKey !== null && sessionId !== null) { + count++; + } + } + console.log(` ====== Valid Rooms: ${count} ====== `); + if (debug) { + for (let item of filtered) { + console.log(`Room: ${item[0]} connected: ${item[1].size}`) + } + } + } catch (e) { + console.error(e); + } + }, 20000, io); }, handlers: { socketsList, From 24c4f7802a69039b112c10f48908506f25cf54ed Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Tue, 15 Feb 2022 22:39:11 +0100 Subject: [PATCH 08/15] fix(tracker): 3.5.1: consider messages sent during /start request --- tracker/tracker/package.json | 4 ++-- tracker/tracker/src/main/app/index.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 6eca3c877..744a2a375 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "3.5.0", + "version": "3.5.1", "keywords": [ "logging", "replay" @@ -41,6 +41,6 @@ "error-stack-parser": "^2.0.6" }, "engines": { - "node": ">=12" + "node": ">=14" } } diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index 4029b6b48..ca747411c 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -171,10 +171,18 @@ export default class App { this.debug.error("OpenReplay error: ", context, e) } + private readonly preStartMessages: Message[] = [] send(message: Message, urgent = false): void { - if (this.activityState !== ActivityState.Active) { + if (this.activityState === ActivityState.NotActive) { return; } + if (this.activityState === ActivityState.Starting) { + this.preStartMessages.push(message); + } + if (this.preStartMessages.length) { + this.messages.push(...this.preStartMessages); + this.preStartMessages.length = 0 + } this.messages.push(message); if (urgent) { this.commit(); From 0a3619c6492e9c53d68b7d91685d632b07725164 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 15 Feb 2022 18:19:54 +0100 Subject: [PATCH 09/15] chore(nginx): precedence x-forward-for ip for geo location tagging Signed-off-by: rjshrjndrn --- .../nginx-ingress/templates/configMap.yaml | 12 +++++++---- .../charts/nginx-ingress/values.yaml | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml b/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml index baba2f5e0..f5b7699cd 100644 --- a/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml +++ b/scripts/helmcharts/openreplay/charts/nginx-ingress/templates/configMap.yaml @@ -13,7 +13,7 @@ data: } location ~ ^/(mobs|sessions-assets|frontend|static|sourcemaps|ios-images)/ { proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; @@ -38,7 +38,7 @@ data: proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_set_header X-Forwarded-Host $real_ip; proxy_set_header X-Real-IP $real_ip; proxy_set_header Host $host; @@ -71,7 +71,7 @@ data: proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_pass http://utilities-openreplay.app.svc.cluster.local:9000; } location /ws-assist/ { @@ -80,7 +80,7 @@ data: proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $origin_forwarded_ip; proxy_set_header X-Real-IP $real_ip; proxy_pass http://utilities-openreplay.app.svc.cluster.local:9001; } @@ -151,6 +151,10 @@ data: default $http_x_forwarded_proto; '' $scheme; } + map $http_x_forwarded_for $origin_forwarded_ip { + default $http_x_forwarded_for; + '' $remote_addr; + } # Default server for helath check server { listen 80 default_server; diff --git a/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml b/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml index 821ad9e3c..43d6d3eae 100644 --- a/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml +++ b/scripts/helmcharts/openreplay/charts/nginx-ingress/values.yaml @@ -84,3 +84,23 @@ nodeSelector: {} tolerations: [] affinity: {} + +healthProbes: + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 From 28486977a6aba6070defb0c1279b91caea162451 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Wed, 16 Feb 2022 10:08:07 +0100 Subject: [PATCH 10/15] chore(kafka): change retention to 4 days Signed-off-by: rjshrjndrn --- scripts/helmcharts/openreplay/files/kafka.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/helmcharts/openreplay/files/kafka.sh b/scripts/helmcharts/openreplay/files/kafka.sh index 1c811eb5d..61c039601 100644 --- a/scripts/helmcharts/openreplay/files/kafka.sh +++ b/scripts/helmcharts/openreplay/files/kafka.sh @@ -22,7 +22,7 @@ function init() { echo "Creating topic: $topic" # TODO: Have to check an idempotent way of creating topics. kafka-topics.sh --create --bootstrap-server ${KAFKA_HOST}:${KAFKA_PORT} --replication-factor 2 --partitions 16 --topic ${topic} --command-config /tmp/config.txt || true - kafka-configs.sh --bootstrap-server ${KAFKA_HOST}:${KAFKA_PORT} --entity-type topics --alter --add-config retention.ms=3456000000 --entity-name=${topic} --command-config /tmp/config.txt || true + kafka-configs.sh --bootstrap-server ${KAFKA_HOST}:${KAFKA_PORT} --entity-type topics --alter --add-config retention.ms=345600000 --entity-name=${topic} --command-config /tmp/config.txt || true done } From adbd4fa6e16ab26ce3364efadcac3a6e4408e4a4 Mon Sep 17 00:00:00 2001 From: Taha Yassine Kraiem Date: Wed, 16 Feb 2022 16:58:17 +0100 Subject: [PATCH 11/15] feat(utilities): WS block polling --- utilities/servers/websocket.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index f2e16884a..6f8c4b955 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -155,7 +155,9 @@ module.exports = { origin: "*", methods: ["GET", "POST", "PUT"] }, - path: '/ws-assist/socket' + path: '/ws-assist/socket', + transports: ['websocket'], + // upgrade: false }); io.attachApp(server); From b90d2a25f9d2dc85fcc7362f73fdaf357f399ca5 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 14 Feb 2022 17:51:44 +0100 Subject: [PATCH 12/15] fix(ui) - typo --- .../app/components/shared/SaveSearchModal/SaveSearchModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx index b579652e9..1ba6b3d56 100644 --- a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx +++ b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx @@ -39,7 +39,7 @@ function SaveSearchModal(props: Props) { if (await confirm({ header: 'Confirm', confirmButton: 'Yes, Delete', - confirmation: `Are you sure you want to permanently delete this Saved serch?`, + confirmation: `Are you sure you want to permanently delete this Saved search?`, })) { props.remove(savedSearch.searchId).then(() => { closeHandler(); From 4eaee22d309c14c1561ae5eb5f45b1363a0185ba Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 16 Feb 2022 17:00:48 +0100 Subject: [PATCH 13/15] Roles and Permissions UI (#333) * feat(ui) - roles with projectId and ui improvements * feat(ui) - roles fixes * feat(ui) - roles fixes * feat(ui) - roles menu item icon change --- frontend/app/Router.js | 2 +- .../Client/ManageUsers/ManageUsers.js | 2 +- .../Client/Notifications/Notifications.js | 3 +- .../Client/PreferencesMenu/PreferencesMenu.js | 22 +-- .../app/components/Client/Roles/Roles.tsx | 30 +++- .../Roles/components/RoleForm/RoleForm.tsx | 161 +++++++++++++++--- .../Roles/components/RoleForm/roleForm.css | 4 - .../Roles/components/RoleItem/RoleItem.tsx | 56 +++--- .../Roles/components/RoleItem/roleItem.css | 14 +- frontend/app/components/Client/Sites/Sites.js | 2 +- frontend/app/components/Errors/Errors.js | 2 + frontend/app/duck/roles.js | 23 ++- frontend/app/routes.js | 6 +- frontend/app/styles/main.css | 6 + frontend/app/svg/icons/diagram-3.svg | 3 + frontend/app/types/role.js | 14 +- 16 files changed, 254 insertions(+), 96 deletions(-) create mode 100644 frontend/app/svg/icons/diagram-3.svg diff --git a/frontend/app/Router.js b/frontend/app/Router.js index edacf56c5..c3a1721a6 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -102,7 +102,7 @@ class Router extends React.Component { this.props.fetchTenants(); } - if (!prevProps.isLoggedIn && this.props.isLoggedIn && this.state.destinationPath !== routes.login() && this.state.destinationPath !== '/') { + if (this.state.destinationPath && !prevProps.isLoggedIn && this.props.isLoggedIn && this.state.destinationPath !== routes.login() && this.state.destinationPath !== '/') { this.props.history.push(this.state.destinationPath); this.setState({ destinationPath: null }); } diff --git a/frontend/app/components/Client/ManageUsers/ManageUsers.js b/frontend/app/components/Client/ManageUsers/ManageUsers.js index e157db019..9071ee46b 100644 --- a/frontend/app/components/Client/ManageUsers/ManageUsers.js +++ b/frontend/app/components/Client/ManageUsers/ManageUsers.js @@ -33,7 +33,7 @@ const LIMIT_WARNING = 'You have reached users limit.'; generateInviteLink, fetchRoles }) -@withPageTitle('Users - OpenReplay Preferences') +@withPageTitle('Team - OpenReplay Preferences') class ManageUsers extends React.PureComponent { state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false } diff --git a/frontend/app/components/Client/Notifications/Notifications.js b/frontend/app/components/Client/Notifications/Notifications.js index 08db0a3b9..31e6e3699 100644 --- a/frontend/app/components/Client/Notifications/Notifications.js +++ b/frontend/app/components/Client/Notifications/Notifications.js @@ -5,6 +5,7 @@ import { Checkbox } from 'UI' import { connect } from 'react-redux' import { withRequest } from 'HOCs' import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config' +import withPageTitle from 'HOCs/withPageTitle'; function Notifications(props) { const { config } = props; @@ -42,4 +43,4 @@ function Notifications(props) { export default connect(state => ({ config: state.getIn(['config', 'options']) -}), { fetchConfig, editConfig, saveConfig })(Notifications) +}), { fetchConfig, editConfig, saveConfig })(withPageTitle('Notifications - OpenReplay Preferences')(Notifications)); diff --git a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js index fa3d51db9..0c57c1ab7 100644 --- a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js +++ b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js @@ -68,25 +68,25 @@ function PreferencesMenu({ activeTab, appearance, history, isEnterprise }) { /> -
- setTab(CLIENT_TABS.MANAGE_USERS) } - /> -
- { isEnterprise && (
setTab(CLIENT_TABS.MANAGE_ROLES) } />
)} + +
+ setTab(CLIENT_TABS.MANAGE_USERS) } + /> +
void + resetErrors: () => void, + projectsMap: any, } function Roles(props: Props) { - const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap, removeErrors } = props + const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap, projectsMap, removeErrors } = props const [showModal, setShowmModal] = useState(false) const isAdmin = account.admin || account.superAdmin; @@ -72,16 +74,16 @@ function Roles(props: Props) { } + content={ showModal && } onClose={ closeModal } />
-

Manage Roles and Permissions

+

Roles and Access

@@ -111,11 +113,17 @@ function Roles(props: Props) { icon >
+
+
Title
+
Project Access
+
Feature Access
+
{roles.map(role => ( @@ -132,14 +140,20 @@ export default connect(state => { const permissions = state.getIn(['roles', 'permissions']) const permissionsMap = {} permissions.forEach(p => { - permissionsMap[p.value] = p.name + permissionsMap[p.value] = p.text }); + const projects = state.getIn([ 'site', 'list' ]) return { instance: state.getIn(['roles', 'instance']) || null, permissionsMap: permissionsMap, roles: state.getIn(['roles', 'list']), removeErrors: state.getIn(['roles', 'removeRequest', 'errors']), loading: state.getIn(['roles', 'fetchRequest', 'loading']), - account: state.getIn([ 'user', 'account' ]) + account: state.getIn([ 'user', 'account' ]), + projectsMap: projects.reduce((acc, p) => { + acc[ p.get('id') ] = p.get('name') + return acc + } + , {}), } -}, { init, edit, fetchList, deleteRole, resetErrors })(Roles) \ No newline at end of file +}, { init, edit, fetchList, deleteRole, resetErrors })(withPageTitle('Roles & Access - OpenReplay Preferences')(Roles)) \ No newline at end of file diff --git a/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx b/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx index a999a9edf..ce535c9dd 100644 --- a/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx +++ b/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx @@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react' import { connect } from 'react-redux' import stl from './roleForm.css' import { save, edit } from 'Duck/roles' -import { Input, Button, Checkbox } from 'UI' +import { Input, Button, Checkbox, Dropdown, Icon } from 'UI' interface Permission { name: string, @@ -16,9 +16,14 @@ interface Props { closeModal: (toastMessage?: string) => void, saving: boolean, permissions: Array[] + projectOptions: Array[], + permissionsMap: any, + projectsMap: any, + deleteHandler: (id: any) => Promise, } -const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props) => { +const RoleForm = (props: Props) => { + const { role, edit, save, closeModal, saving, permissions, projectOptions, permissionsMap, projectsMap } = props let focusElement = useRef(null) const _save = () => { save(role).then(() => { @@ -28,13 +33,33 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props) const write = ({ target: { value, name } }) => edit({ [ name ]: value }) - const onChangeOption = (e) => { + const onChangePermissions = (e) => { const { permissions } = role const index = permissions.indexOf(e) const _perms = permissions.contains(e) ? permissions.remove(index) : permissions.push(e) edit({ permissions: _perms }) } + const onChangeProjects = (e) => { + const { projects } = role + const index = projects.indexOf(e) + const _projects = index === -1 ? projects.push(e) : projects.remove(index) + edit({ projects: _projects }) + } + + const writeOption = (e, { name, value }) => { + if (name === 'permissions') { + onChangePermissions(value) + } else if (name === 'projects') { + onChangeProjects(value) + } + } + + const toggleAllProjects = () => { + const { allProjects } = role + edit({ allProjects: !allProjects }) + } + useEffect(() => { focusElement && focusElement.current && focusElement.current.focus() }, []) @@ -42,8 +67,8 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props) return (
-
- +
+
-
- { permissions.map((permission: any, index) => ( -
- onChangeOption(permission.value) } - label={permission.name} - /> +
+ + +
+ +
+
All Projects
+ + (Uncheck to select specific projects) +
- ))} +
+ { !role.allProjects && ( + <> + + { role.projects.size > 0 && ( +
+ { role.projects.map(p => ( + OptionLabel(projectsMap, p, onChangeProjects) + )) } +
+ )} + + )} +
+ +
+ + + { role.permissions.size > 0 && ( +
+ { role.permissions.map(p => ( + OptionLabel(permissionsMap, p, onChangePermissions) + )) } +
+ )}
@@ -89,13 +169,50 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props) { 'Cancel' }
+ { role.exists() && ( +
+ +
+ )}
); } -export default connect(state => ({ - role: state.getIn(['roles', 'instance']), - permissions: state.getIn(['roles', 'permissions']), - saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]), -}), { edit, save })(RoleForm); \ No newline at end of file +export default connect(state => { + const role = state.getIn(['roles', 'instance']) + const projects = state.getIn([ 'site', 'list' ]) + return { + role, + projectOptions: projects.map(p => ({ + key: p.get('id'), + value: p.get('id'), + text: p.get('name'), + disabled: role.projects.includes(p.get('id')), + })).toJS(), + permissions: state.getIn(['roles', 'permissions']) + .map(({ text, value }) => ({ text, value, disabled: role.permissions.includes(value) })).toJS(), + saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]), + projectsMap: projects.reduce((acc, p) => { + acc[ p.get('id') ] = p.get('name') + return acc + } + , {}), + } +}, { edit, save })(RoleForm); + +function OptionLabel(nameMap: any, p: any, onChangeOption: (e: any) => void) { + return
+
{nameMap[p]}
+
onChangeOption(p)}> + +
+
+} diff --git a/frontend/app/components/Client/Roles/components/RoleForm/roleForm.css b/frontend/app/components/Client/Roles/components/RoleForm/roleForm.css index a0c5934c8..eed9e22b4 100644 --- a/frontend/app/components/Client/Roles/components/RoleForm/roleForm.css +++ b/frontend/app/components/Client/Roles/components/RoleForm/roleForm.css @@ -1,9 +1,5 @@ .form { padding: 0 20px; - - & .formGroup { - margin-bottom: 15px; - } & label { display: block; margin-bottom: 5px; diff --git a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx index 2ef271a46..dc67dd42d 100644 --- a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx +++ b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx @@ -1,11 +1,19 @@ import React from 'react' -import { Icon } from 'UI' +import { Icon, Link } from 'UI' import stl from './roleItem.css' import cn from 'classnames' +import { CLIENT_TABS, client as clientRoute } from 'App/routes'; -function PermisionLabel({ permission }: any) { + +function PermisionLabel({ label }: any) { return ( -
{ permission }
+
{ label }
+ ); +} + +function PermisionLabelLinked({ label, route }: any) { + return ( +
{ label }
); } @@ -14,28 +22,33 @@ interface Props { deleteHandler?: (role: any) => void, editHandler?: (role: any) => void, permissions: any, - isAdmin: boolean + isAdmin: boolean, + projects: any, } -function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions }: Props) { +function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions, projects }: Props) { return ( -
- -
-
{ role.name }
-
- {role.permissions.map((permission: any) => ( - - // { permissions[permission].name } - ))} -
+
+
+ + { role.name }
+
+ {role.allProjects ? ( + + ) : ( + role.projects.map(p => ( + + )) + )} +
+
+ {role.permissions.map((permission: any) => ( + + ))} +
+ { isAdmin && ( -
- { !!deleteHandler && -
deleteHandler(role) } id="trash"> - -
- } +
{ !!editHandler &&
editHandler(role) }> @@ -43,7 +56,6 @@ function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions }: Pr }
)} -
); } diff --git a/frontend/app/components/Client/Roles/components/RoleItem/roleItem.css b/frontend/app/components/Client/Roles/components/RoleItem/roleItem.css index af0aab35d..e5d3224ba 100644 --- a/frontend/app/components/Client/Roles/components/RoleItem/roleItem.css +++ b/frontend/app/components/Client/Roles/components/RoleItem/roleItem.css @@ -1,13 +1,5 @@ -.wrapper { - display: flex; - align-items: center; - width: 100%; - border-bottom: solid thin #e6e6e6; - padding: 10px 0px; -} - .actions { - margin-left: auto; + /* margin-left: auto; */ /* opacity: 0; */ transition: all 0.4s; display: flex; @@ -37,11 +29,11 @@ } .label { - margin-left: 10px; + margin-right: 10px; padding: 0 5px; border-radius: 3px; background-color: $gray-lightest; - font-size: 10px; + font-size: 12px; border: solid thin $gray-light; width: fit-content; } \ No newline at end of file diff --git a/frontend/app/components/Client/Sites/Sites.js b/frontend/app/components/Client/Sites/Sites.js index 9b5e3b9b0..4769815e5 100644 --- a/frontend/app/components/Client/Sites/Sites.js +++ b/frontend/app/components/Client/Sites/Sites.js @@ -37,7 +37,7 @@ const GDPR_FORM = 'GDPR_FORM'; remove, fetchGDPR }) -@withPageTitle('Sites - OpenReplay Preferences') +@withPageTitle('Projects - OpenReplay Preferences') class Sites extends React.PureComponent { state = { showTrackingCode: false, diff --git a/frontend/app/components/Errors/Errors.js b/frontend/app/components/Errors/Errors.js index f9e7b5c9b..4eb671cf5 100644 --- a/frontend/app/components/Errors/Errors.js +++ b/frontend/app/components/Errors/Errors.js @@ -9,6 +9,7 @@ import { fetchList as fetchSlackList } from 'Duck/integrations/slack'; import { errors as errorsRoute, isRoute } from "App/routes"; import EventFilter from 'Components/BugFinder/EventFilter'; import DateRange from 'Components/BugFinder/DateRange'; +import withPageTitle from 'HOCs/withPageTitle'; import { SavedSearchList } from 'UI'; @@ -43,6 +44,7 @@ function getStatusLabel(status) { applyFilter, fetchSlackList, }) +@withPageTitle("Errors - OpenReplay") export default class Errors extends React.PureComponent { state = { status: UNRESOLVED, diff --git a/frontend/app/duck/roles.js b/frontend/app/duck/roles.js index 874bd9be6..8a8415475 100644 --- a/frontend/app/duck/roles.js +++ b/frontend/app/duck/roles.js @@ -2,6 +2,7 @@ import { List, Map } from 'immutable'; import Role from 'Types/role'; import crudDuckGenerator from './tools/crudDuck'; import { reduceDucks } from 'Duck/tools'; +import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; const crudDuck = crudDuckGenerator('client/role', Role, { idKey: 'roleId' }); export const { fetchList, init, edit, remove, } = crudDuck.actions; @@ -11,19 +12,29 @@ const RESET_ERRORS = 'roles/RESET_ERRORS'; const initialState = Map({ list: List(), permissions: List([ - { name: 'Session Replay', value: 'SESSION_REPLAY' }, - { name: 'Developer Tools', value: 'DEV_TOOLS' }, - { name: 'Errors', value: 'ERRORS' }, - { name: 'Metrics', value: 'METRICS' }, - { name: 'Assist (Live)', value: 'ASSIST_LIVE' }, - { name: 'Assist (Call)', value: 'ASSIST_CALL' }, + { text: 'Session Replay', value: 'SESSION_REPLAY' }, + { text: 'Developer Tools', value: 'DEV_TOOLS' }, + { text: 'Errors', value: 'ERRORS' }, + { text: 'Metrics', value: 'METRICS' }, + { text: 'Assist (Live)', value: 'ASSIST_LIVE' }, + { text: 'Assist (Call)', value: 'ASSIST_CALL' }, ]) }); +const name = "role"; +const idKey = "roleId"; + +const updateItemInList = createListUpdater(idKey); +const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ] + ? state.mergeIn([ "instance" ], instance) + : state; + const reducer = (state = initialState, action = {}) => { switch (action.type) { case RESET_ERRORS: return state.setIn(['removeRequest', 'errors'], null); + case crudDuck.actionTypes.SAVE.SUCCESS: + return updateItemInList(updateInstance(state, action.data), action.data); } return state; }; diff --git a/frontend/app/routes.js b/frontend/app/routes.js index 2ca5fd672..cdccc6327 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -59,8 +59,8 @@ export const forgotPassword = () => '/reset-password'; export const CLIENT_TABS = { INTEGRATIONS: 'integrations', PROFILE: 'account', - MANAGE_USERS: 'manage-users', - MANAGE_ROLES: 'manage-roles', + MANAGE_USERS: 'team', + MANAGE_ROLES: 'roles', SITES: 'projects', CUSTOM_FIELDS: 'metadata', WEBHOOKS: 'webhooks', @@ -73,7 +73,7 @@ export const client = (tab = routerClientTabString) => `/client/${ tab }`; export const OB_TABS = { INSTALLING: 'installing', IDENTIFY_USERS: 'identify-users', - MANAGE_USERS: 'manage-users', + MANAGE_USERS: 'team', INTEGRATIONS: 'integrations', }; export const OB_DEFAULT_TAB = OB_TABS.INSTALLING; diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css index f27155e36..fc366bc39 100644 --- a/frontend/app/styles/main.css +++ b/frontend/app/styles/main.css @@ -117,4 +117,10 @@ .disabled { opacity: 0.4; pointer-events: none; +} + +.hover { + &:hover { + background-color: $active-blue; + } } \ No newline at end of file diff --git a/frontend/app/svg/icons/diagram-3.svg b/frontend/app/svg/icons/diagram-3.svg new file mode 100644 index 000000000..03e448c17 --- /dev/null +++ b/frontend/app/svg/icons/diagram-3.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/types/role.js b/frontend/app/types/role.js index 52a74d400..d63afa5db 100644 --- a/frontend/app/types/role.js +++ b/frontend/app/types/role.js @@ -1,18 +1,21 @@ import Record from 'Types/Record'; -import { validateName } from 'App/validate'; +import { notEmptyString, validateName } from 'App/validate'; import { List } from 'immutable'; export default Record({ roleId: undefined, name: '', + allProjects: true, permissions: List(), + projects: List(), protected: false, - description: '' + description: '', + permissionOptions: List(), }, { idKey: 'roleId', methods: { validate() { - return validateName(this.name, { diacritics: true }); + return notEmptyString(this.name) && validateName(this.name, { diacritics: true }) && (this.allProjects || this.projects.size > 0); }, toData() { const js = this.toJS(); @@ -21,10 +24,11 @@ export default Record({ return js; }, }, - fromJS({ permissions, ...rest }) { + fromJS({ projects = [], permissions = [], ...rest }) { return { ...rest, - permissions: List(permissions) + permissions: List(permissions), + projects: List(projects), } }, }); From e8a330c053091589671da0abf1d1d8c5054456f0 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 16 Feb 2022 17:06:08 +0100 Subject: [PATCH 14/15] GraphQL UI and Navigation (#335) * chore(http): check for custom endpoint for caching Signed-off-by: --global <--global> * fix(ui) - typo * fix(migration): template file variable values * fix(install): minio download path Signed-off-by: rjshrjndrn * chore(nginx): precedence x-forward-for ip for geo location tagging Signed-off-by: rjshrjndrn * chore(kafka): change retention to 4 days Signed-off-by: rjshrjndrn * feat(ui) - graphql ui and navigation Co-authored-by: --global <--global> Co-authored-by: Mehdi Osman Co-authored-by: rjshrjndrn --- .../app/components/Session_/Fetch/Fetch.js | 4 - .../components/Session_/GraphQL/GQLDetails.js | 73 ++++++++++---- .../components/Session_/GraphQL/GraphQL.js | 97 +++++++++++++------ 3 files changed, 122 insertions(+), 52 deletions(-) diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.js index 2db36f76f..dd3c79711 100644 --- a/frontend/app/components/Session_/Fetch/Fetch.js +++ b/frontend/app/components/Session_/Fetch/Fetch.js @@ -127,10 +127,6 @@ export default class Fetch extends React.PureComponent {

Fetch

- {/*
-
Prev
-
Next
-
*/} -
- { variables && variables !== "{}" && -
-
-
{ 'Variables'}
- { jsonVars === undefined - ?
{ variables }
- : - } +
+
{ 'Operation Name'}
+
{ operationName }
+ +
+
+
Operation Kind
+
{operationKind}
+
+
+
Duration
+
{parseInt(duration)} ms
+
+
+ +
+
{ 'Response' }
+
+
+ { variables && variables !== "{}" && +
+
+
{ 'Variables'}
+ { jsonVars === undefined + ?
{ variables }
+ : + } +
+
-
-
- } -
-
-
{ 'Response' }
-
- { jsonResponse === undefined - ?
{ response }
- : } +
+ { jsonResponse === undefined + ?
{ response }
+ : + } +
+
+ +
+ +
); diff --git a/frontend/app/components/Session_/GraphQL/GraphQL.js b/frontend/app/components/Session_/GraphQL/GraphQL.js index 0fedbb85e..0b2765b58 100644 --- a/frontend/app/components/Session_/GraphQL/GraphQL.js +++ b/frontend/app/components/Session_/GraphQL/GraphQL.js @@ -1,8 +1,6 @@ -//import cn from 'classnames'; -import { Icon, NoContent, Input, SlideModal } from 'UI'; +import { Label, Icon, NoContent, Input, SlideModal, CloseButton } from 'UI'; import { getRE } from 'App/utils'; -import { connectPlayer } from 'Player'; -import Autoscroll from '../Autoscroll'; +import { connectPlayer, pause, jump } from 'Player'; import BottomBlock from '../BottomBlock'; import TimeTable from '../TimeTable'; import GQLDetails from './GQLDetails'; @@ -10,60 +8,105 @@ import GQLDetails from './GQLDetails'; function renderDefaultStatus() { return "2xx-3xx"; } - @connectPlayer(state => ({ list: state.graphqlListNow, + livePlay: state.livePlay, })) export default class GraphQL extends React.PureComponent { state = { - filter: "", + filter: "", + filteredList: this.props.list, current: null, + currentIndex: 0, + showFetchDetails: false, + hasNextError: false, + hasPreviousError: false, } - onFilterChange = (e, { value }) => this.setState({ filter: value }) - setCurrent = (item) => { - this.setState({ current: item }); + onFilterChange = (e, { value }) => { + const { list } = this.props; + const filterRE = getRE(value, 'i'); + const filtered = list + .filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status)); + this.setState({ filter: value, filteredList: value ? filtered : list, currentIndex: 0 }); + } + + setCurrent = (item, index) => { + if (!this.props.livePlay) { + pause(); + jump(item.time) + } + this.setState({ current: item, currentIndex: index }); + } + + closeModal = () => this.setState({ current: null, showFetchDetails: false }); + + static getDerivedStateFromProps(nextProps, prevState) { + const { filteredList } = prevState; + if (nextProps.timelinePointer) { + let activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time); + activeItem = activeItem || filteredList[filteredList.length - 1]; + return { + current: activeItem, + currentIndex: filteredList.indexOf(activeItem), + }; + } } - closeModal = () => this.setState({ current: null}) render() { const { list } = this.props; - const { filter, current } = this.state; - const filterRE = getRE(filter, 'i'); - const filtered = list - .filter(({ operationName = "", operationKind = "" }) => filterRE.test(operationName) || filterRE.test(operationKind)); - + const { current, currentIndex, filteredList } = this.state; + return ( {current.operationKind} {current.operationName} } + right + title = { +
+

GraphQL

+
+ +
+
+ } isDisplayed={ current != null } content={ current && - + } onClose={ this.closeModal } /> - +

GraphQL

+
+ +
{[ { From e8e87c5ff535694ae09fefb637c16ffd1de793e5 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 16 Feb 2022 17:33:20 +0100 Subject: [PATCH 15/15] fix(ui) - removed unused wrapper --- .../components/Client/Roles/components/RoleItem/RoleItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx index dc67dd42d..3fddd489c 100644 --- a/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx +++ b/frontend/app/components/Client/Roles/components/RoleItem/RoleItem.tsx @@ -27,7 +27,7 @@ interface Props { } function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions, projects }: Props) { return ( -
+
{ role.name }