fix(ui) - main to dev conflicts
This commit is contained in:
commit
f7f70589c3
65 changed files with 623 additions and 193 deletions
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '30 6 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go', 'javascript', 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
75
CLA.md
Normal file
75
CLA.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
## Individual and Entity Contributor License Agreement (CLA)
|
||||
|
||||
Thank you for your interest in contributing to software projects managed by Asayer, Inc. (“We” or “Us”). This Contributor License Agreement (“Agreement”) documents the rights granted by contributors to Us. This Agreement is for your protection as a contributor as well as for our protection; it does not change your rights to use your own Contributions for any other purpose. To make this document effective, please read the Agreement carefully and then sign it. By signing this Agreement (including by clicking “I agree” or "Sign in with GitHub to agree" and submitting it to us electronically), You are creating a legally binding contract which becomes effective upon your signature or agreement. If You are less than eighteen years old, please have Your parents or guardian sign the Agreement. This Agreement covers your present, and all future, Contributions from You, and may cover more than one software project managed by Us.
|
||||
|
||||
### 1. Definitions
|
||||
|
||||
“Affiliates” means any Legal Entities that control, are controlled by, or under common control with another Legal Entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such Legal Entity, whether by contract or otherwise, (ii) ownership of fifty percent (50%) or more of the outstanding shares or securities which vote to elect the management or other persons who direct such Legal Entity or (iii) beneficial ownership of such entity.
|
||||
|
||||
“Contribution” means any work of authorship that is Submitted by You to Us in which You own or assert ownership of the Copyright. If You do not own the Copyright in the entire work of authorship, please contact us before Submitting the Contribution.
|
||||
|
||||
“Copyright” means all rights protecting works of authorship owned or controlled by You or your Affiliates (as may be applicable), including copyright, moral and related (or neighboring) rights, as appropriate, for the full term of their existence, including any extensions by You.
|
||||
|
||||
“Legal Entity” means an entity which is not a natural person.
|
||||
|
||||
“Media” means any portion of a Contribution which is not software.
|
||||
|
||||
“Submit” means any form of electronic or written communication, or recorded verbal communication, sent to Us or our representatives at a destination (including websites) that we own or control or that is otherwise registered to us, including but not limited to electronic mailing lists, source code control systems, instant messages or similar communications, and issue tracking systems that are managed by, or on behalf of, Us for the purpose of discussing and improving the Work, but excluding any communication that is conspicuously marked or otherwise designated in writing by You as “Not a Contribution.”
|
||||
|
||||
“Work” means any of the products or projects owned or managed by Us, and any work of authorship which is made available by Us to third parties. When this Agreement covers more than one software project, Work means the work of authorship to which your Contribution was Submitted. After You Submit the Contribution, it may be included in the Work.
|
||||
|
||||
“You” means (i) the individual who Submits a Contribution to Us, if You are an individual acting on your own behalf, or (ii) the Legal Entity on behalf of whom you Submit a Contribution to Us if you are are Submitting any Contribution on behalf of any entity.
|
||||
|
||||
### 2. Grant of Rights
|
||||
|
||||
#### 2.1 Copyright License
|
||||
|
||||
(a) Except for the license granted to Us in this Agreement, You reserve all right, title, and interest in and to Your Contributions. That means that you can keep doing whatever you want with your Contribution, and you can license it to anyone you want under any terms you want.
|
||||
|
||||
(b) To the maximum extent permitted by the relevant law, You grant to Us a perpetual, worldwide, non-exclusive, transferable, no charge and royalty-free, irrevocable license under the Copyright covering the Contribution, with the right to sublicense such rights through multiple tiers of sublicensees, to reproduce, modify, display, perform, sublicense and distribute the Contribution as part of the Work; provided that this license is conditioned upon compliance with Section 2.3.
|
||||
|
||||
#### 2.2 Patent License
|
||||
|
||||
For patent claims, including without limitation method, process, and apparatus claims which You (or Your Affiliates, as may be applicable) own, control or have the right to grant, now or in the future, You grant to Us, and to recipients of software distributed by the Us, a perpetual, worldwide, non-exclusive, transferable, no charge and royalty-free, irrevocable patent license, with the right to sublicense these rights to multiple tiers of sublicensees, to make, have made, use, sell, offer for sale, import and otherwise transfer the Contribution (and the Contribution in combination with the Work, and portions of such combination). This license is granted only to the extent that the exercise of the licensed rights infringes such patent claims; and provided that this license is conditioned upon compliance with Section 2.3. If any person or entity institutes patent litigation against Contributor or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Contributions, or the Work to which the Contributions were submitted, constitutes direct or contributory patent infringement, then any patent licenses granted under this Agreement for that Contribution to the person or entity instituting the litigation, or the Work to which the Contributions were submitted, shall terminate as of the date such litigation is filed.
|
||||
|
||||
#### 2.3 Outbound License
|
||||
|
||||
Based on the grant of rights in Sections 2.1 (meaning, no matter what, you can keep licensing your Contribution to others however you want) and 2.2, if We include Your Contribution in any Work, and if We determine that it is appropriate for the purpose of commercializing any Work or any project under Our control, we may license the Contribution under any license, including copyleft, permissive, commercial, or proprietary licenses. As a condition on the exercise of this right, We agree to also use reasonable efforts to continue to license Your Contribution (in the same or other projects or Works) under the terms of the license or licenses under which you Submitted Your Contribution.
|
||||
|
||||
#### 2.4 Moral Rights
|
||||
|
||||
We agree to comply with applicable laws regarding your Contribution, including copyright laws and law related to moral rights. If moral rights apply to the Contribution, to the maximum extent permitted by law, You waive and agree not to assert such moral rights against Us or our successors in interest, or any of our licensees, either direct or indirect.
|
||||
|
||||
#### 2.5 Our Rights
|
||||
|
||||
You acknowledge that We are not obligated to use Your Contribution as part of any Work, and that we and may decide to include any Contribution We consider appropriate.
|
||||
|
||||
#### 2.6 Reservation of Rights
|
||||
|
||||
Any rights in Your Contribution not expressly licensed under this Agreement are expressly reserved by You.
|
||||
|
||||
### 3. Representations
|
||||
|
||||
You represent (promise) that You are legally entitled to grant the above licenses. If Your employer(s) has rights to intellectual property that you create that includes your Contributions, You represent that You have received permission to make Contributions on behalf of that employer, that Your employer has waived such rights for your Contributions to Us, or that Your employer has executed a separate Corporate Contributor License Agreement with Us.
|
||||
|
||||
You further represent that each of Your Contributions is Your original creation. You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
|
||||
|
||||
You agree to notify Us of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
|
||||
|
||||
### 4. Disclaimer
|
||||
|
||||
EXCEPT FOR THE EXPRESS WARRANTIES IN SECTION 3, YOUR CONTRIBUTION IS PROVIDED "AS IS". YOU EXPRESSLY DISCLAIM ALL OTHER EXPRESS WARRANTIES AND ALL IMPLIED WARRANTIES. TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION TO THE MINIMUM PERIOD PERMITTED BY LAW.
|
||||
|
||||
### 5. Miscellaneous
|
||||
|
||||
5.1 This Agreement will be governed by and construed in accordance with the laws of the State of Delaware, without regard to conflicts of law provisions. If any provision of this Agreement shall be adjudged by any court of competent jurisdiction to be unenforceable or invalid, that provision shall be limited or eliminated to the minimum extent necessary so that this Agreement shall otherwise remain in full force and effect and enforceable. The sole venue for all disputes relating to this Agreement shall be in the New Castle County, Delaware (USA). The rights and obligations of the parties under this Agreement shall not be governed by the 1980 U.N. Convention on Contracts for the International Sale of Goods.
|
||||
|
||||
5.2 This Agreement may be amended only by a written document signed by the party against whom enforcement is sought.
|
||||
|
||||
5.3 The failure of either party to require performance by the other party of any provision of this Agreement in one situation shall not affect the right of a party to require such performance at any time in the future. A waiver of performance under a provision in one situation shall not be considered a waiver of the performance of the provision in the future or a waiver of the provision in its entirety.
|
||||
|
||||
5.4 If any provision of this Agreement is found void and unenforceable, such provision will be replaced to the extent possible with a provision that comes closest to the meaning of the original provision and which is enforceable. The terms and conditions set forth in this Agreement shall apply notwithstanding any failure of essential purpose of this Agreement or any limited remedy to the maximum extent possible under law.
|
||||
|
||||
**This Agreement contains the entire understanding of the parties regarding the subject matter of this Agreement and supersedes all prior and contemporaneous negotiations and agreements, whether written or oral, between the parties with respect to the subject matter of this Agreement.**
|
||||
|
||||
By signing this agreement, Contributor accepts and agrees to the preceding terms and conditions for Contributor’s present and future Contributions submitted to Us.
|
||||
|
|
@ -6,7 +6,7 @@ By participating in this project, you are expected to uphold our [Code of Conduc
|
|||
|
||||
## First-time Contributors
|
||||
|
||||
We appreciate all contributions, especially those coming from first time contributors. Good first issues is the best way start. If you're not sure how to help, feel free to reach out anytime for assistance via [email](mailto:hey@openreplay.com) or [Slack](https://slack.openreplay.com).
|
||||
We appreciate all contributions, especially those coming from first time contributors. Good first issues is the best way start. If you're not sure how to help, feel free to reach out anytime for assistance via [email](mailto:hey@openreplay.com) or [Slack](https://slack.openreplay.com). All contributors must approve our [Contributor License Agreement](https://cla-assistant.io/openreplay/openreplay).
|
||||
|
||||
## Areas for Contributing
|
||||
|
||||
|
|
@ -70,5 +70,6 @@ We try to answer the below questions when reviewing a PR:
|
|||
- How will it perform with millions of sessions and users events?
|
||||
- Has it been tested?
|
||||
- Is it introducing any security flaws?
|
||||
- Did the contributor approve our CLA?
|
||||
|
||||
Once your PR passes, we will merge it. Otherwise, we'll politely ask you to make a change.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
"S3_SECRET": "",
|
||||
"invitation_link": "/api/users/invitation?token=%s",
|
||||
"change_password_link": "/reset-password?invitation=%s&&pass=%s",
|
||||
"version_number": "1.2.0"
|
||||
"version_number": "1.3.5"
|
||||
},
|
||||
"lambda_timeout": 150,
|
||||
"lambda_memory_size": 400,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
"invitation_link": "/api/users/invitation?token=%s",
|
||||
"change_password_link": "/reset-password?invitation=%s&&pass=%s",
|
||||
"iosBucket": "openreplay-ios-images",
|
||||
"version_number": "1.2.0"
|
||||
"version_number": "1.3.6"
|
||||
},
|
||||
"lambda_timeout": 150,
|
||||
"lambda_memory_size": 400,
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
from chalice import Blueprint, Response
|
||||
|
||||
from chalicelib import _overrides
|
||||
from chalicelib.core import metadata, errors_favorite_viewed, slack, alerts, sessions, integration_github, \
|
||||
integrations_manager
|
||||
from chalicelib.utils import captcha
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.utils.helper import environ
|
||||
|
||||
from chalicelib.core import tenants
|
||||
from chalicelib.core import signup
|
||||
from chalicelib.core import users
|
||||
from chalicelib.core import projects
|
||||
from chalicelib.core import errors
|
||||
from chalicelib.core import notifications
|
||||
from chalicelib.core import boarding
|
||||
from chalicelib.core import errors
|
||||
from chalicelib.core import license
|
||||
from chalicelib.core import metadata, errors_favorite_viewed, slack, alerts, sessions, integrations_manager, assist
|
||||
from chalicelib.core import notifications
|
||||
from chalicelib.core import projects
|
||||
from chalicelib.core import signup
|
||||
from chalicelib.core import tenants
|
||||
from chalicelib.core import users
|
||||
from chalicelib.core import webhook
|
||||
from chalicelib.core import license
|
||||
from chalicelib.core import assist
|
||||
from chalicelib.core.collaboration_slack import Slack
|
||||
from chalicelib.utils import captcha
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.utils.helper import environ
|
||||
|
||||
app = Blueprint(__name__)
|
||||
_overrides.chalice_app(app)
|
||||
|
|
@ -48,7 +47,7 @@ def login():
|
|||
c["projects"] = projects.get_projects(tenant_id=tenant_id, recording_state=True, recorded=True,
|
||||
stack_integrations=True, version=True)
|
||||
c["smtp"] = helper.has_smtp()
|
||||
c["iceServers"]= assist.get_ice_servers()
|
||||
c["iceServers"] = assist.get_ice_servers()
|
||||
return {
|
||||
'jwt': r.pop('jwt'),
|
||||
'data': {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import requests
|
||||
from chalicelib.core import projects, sessions, sessions_metas
|
||||
from chalicelib.utils import pg_client, helper
|
||||
from chalicelib.core import projects, sessions, sessions_metas
|
||||
from chalicelib.utils import pg_client, helper
|
||||
from chalicelib.utils.helper import environ
|
||||
|
||||
SESSION_PROJECTION_COLS = """s.project_id,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import json
|
||||
import secrets
|
||||
|
||||
from chalicelib.core import authorizers, metadata, projects
|
||||
|
||||
from chalicelib.core import authorizers, metadata, projects, assist
|
||||
from chalicelib.core import tenants
|
||||
from chalicelib.utils import dev
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.utils import pg_client
|
||||
from chalicelib.utils import dev
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from chalicelib.utils.helper import environ
|
||||
|
||||
|
|
|
|||
1
ee/api/.gitignore
vendored
1
ee/api/.gitignore
vendored
|
|
@ -210,6 +210,7 @@ Pipfile
|
|||
/chalicelib/core/sessions_favorite_viewed.py
|
||||
/chalicelib/core/sessions_metas.py
|
||||
/chalicelib/core/sessions_mobs.py
|
||||
/chalicelib/core/sessions.py
|
||||
/chalicelib/core/significance.py
|
||||
/chalicelib/core/slack.py
|
||||
/chalicelib/core/socket_ios.py
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def login():
|
|||
tenant_id = r.pop("tenantId")
|
||||
# change this in open-source
|
||||
r["limits"] = {
|
||||
"teamMember": -1,
|
||||
"teamMember": int(environ.get("numberOfSeats", 0)),
|
||||
"projects": -1,
|
||||
"metadata": metadata.get_remaining_metadata_with_count(tenant_id)}
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ def get_account(context):
|
|||
'data': {
|
||||
**r,
|
||||
"limits": {
|
||||
"teamMember": -1,
|
||||
"teamMember": int(environ.get("numberOfSeats", 0)),
|
||||
"projects": -1,
|
||||
"metadata": metadata.get_remaining_metadata_with_count(context['tenantId'])
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from chalicelib.utils.helper import environ
|
||||
from chalicelib.utils import pg_client
|
||||
from chalicelib.core import unlock
|
||||
|
||||
|
|
@ -14,7 +15,8 @@ def get_status(tenant_id):
|
|||
"edition": r.get("edition", "").upper(),
|
||||
"versionNumber": r.get("version_number", ""),
|
||||
"license": license[0:2] + "*" * (len(license) - 4) + license[-2:],
|
||||
"expirationDate": unlock.get_expiration_date()
|
||||
"expirationDate": unlock.get_expiration_date(),
|
||||
"teamMember": int(environ.get("numberOfSeats", 0))
|
||||
},
|
||||
"count": {
|
||||
"teamMember": r.get("t_users"),
|
||||
|
|
|
|||
|
|
@ -13,9 +13,14 @@ def get_license():
|
|||
|
||||
|
||||
def check():
|
||||
license=get_license()
|
||||
license = get_license()
|
||||
if license is None or len(license) == 0:
|
||||
print("!! license key not found, please provide a LICENSE_KEY env var")
|
||||
environ["expiration"] = "-1"
|
||||
environ["numberOfSeats"] = "0"
|
||||
return
|
||||
print(f"validating: {license}")
|
||||
r = requests.post('https://parrot.asayer.io/os/license', json={"mid": __get_mid(), "license": get_license()})
|
||||
r = requests.post('https://api.openreplay.com/os/license', json={"mid": __get_mid(), "license": get_license()})
|
||||
if r.status_code != 200 or "errors" in r.json() or not r.json()["data"].get("valid"):
|
||||
print("license validation failed")
|
||||
print(r.text)
|
||||
|
|
@ -23,6 +28,8 @@ def check():
|
|||
else:
|
||||
environ["expiration"] = str(r.json()["data"].get("expiration"))
|
||||
environ["lastCheck"] = str(TimeUTC.now())
|
||||
if r.json()["data"].get("numberOfSeats") is not None:
|
||||
environ["numberOfSeats"] = str(r.json()["data"]["numberOfSeats"])
|
||||
|
||||
|
||||
def get_expiration_date():
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import json
|
||||
import secrets
|
||||
|
||||
from chalicelib.core import assist
|
||||
from chalicelib.core import authorizers, metadata, projects
|
||||
from chalicelib.core import authorizers, metadata, projects, assist
|
||||
from chalicelib.core import tenants
|
||||
from chalicelib.utils import dev, SAML2_helper
|
||||
from chalicelib.utils import helper
|
||||
|
|
|
|||
10
ee/scripts/helm/db/init_dbs/postgresql/1.3.5/1.3.5.sql
Normal file
10
ee/scripts/helm/db/init_dbs/postgresql/1.3.5/1.3.5.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
BEGIN;
|
||||
CREATE INDEX projects_tenant_id_idx ON projects (tenant_id);
|
||||
CREATE INDEX webhooks_tenant_id_idx ON webhooks (tenant_id);
|
||||
|
||||
CREATE INDEX pages_session_id_timestamp_idx ON events.pages (session_id, timestamp);
|
||||
|
||||
CREATE INDEX issues_project_id_idx ON issues (project_id);
|
||||
CREATE INDEX jobs_project_id_idx ON jobs (project_id);
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -267,6 +267,7 @@ $$
|
|||
);
|
||||
|
||||
CREATE INDEX ON public.projects (project_key);
|
||||
CREATE INDEX projects_tenant_id_idx ON projects (tenant_id);
|
||||
|
||||
-- --- alerts.sql ---
|
||||
|
||||
|
|
@ -318,6 +319,8 @@ $$
|
|||
name varchar(100)
|
||||
);
|
||||
|
||||
CREATE INDEX webhooks_tenant_id_idx ON webhooks (tenant_id);
|
||||
|
||||
-- --- notifications.sql ---
|
||||
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 67 KiB |
|
|
@ -7,9 +7,11 @@ import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream
|
|||
|
||||
interface Props {
|
||||
stream: LocalStream | null,
|
||||
endCall: () => void
|
||||
endCall: () => void,
|
||||
videoEnabled: boolean,
|
||||
setVideoEnabled: (boolean) => void
|
||||
}
|
||||
function ChatControls({ stream, endCall } : Props) {
|
||||
function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled } : Props) {
|
||||
const [audioEnabled, setAudioEnabled] = useState(true)
|
||||
const [videoEnabled, setVideoEnabled] = useState(false)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, FC } from 'react'
|
||||
import React, { useState, FC, useEffect } from 'react'
|
||||
import VideoContainer from '../components/VideoContainer'
|
||||
import { Icon, Popup, Button } from 'UI'
|
||||
import cn from 'classnames'
|
||||
|
|
@ -10,14 +10,32 @@ import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream
|
|||
|
||||
|
||||
export interface Props {
|
||||
incomeStream: MediaStream | null,
|
||||
remoteStream: MediaStream | null,
|
||||
localStream: LocalStream | null,
|
||||
userId: String,
|
||||
endCall: () => void
|
||||
}
|
||||
|
||||
const ChatWindow: FC<Props> = function ChatWindow({ userId, incomeStream, localStream, endCall }) {
|
||||
const [minimize, setMinimize] = useState(false)
|
||||
const ChatWindow: FC<Props> = function ChatWindow({ userId, remoteStream, localStream, endCall }) {
|
||||
const [localVideoEnabled, setLocalVideoEnabled] = useState(false)
|
||||
const [remoteVideoEnabled, setRemoteVideoEnabled] = useState(false)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!remoteStream) { return }
|
||||
const iid = setInterval(() => {
|
||||
const settings = remoteStream.getVideoTracks()[0]?.getSettings()
|
||||
const isDummyVideoTrack = !!settings ? (settings.width === 2 || settings.frameRate === 0) : true
|
||||
console.log(isDummyVideoTrack, settings)
|
||||
const shouldBeEnabled = !isDummyVideoTrack
|
||||
if (shouldBeEnabled !== localVideoEnabled) {
|
||||
setRemoteVideoEnabled(shouldBeEnabled)
|
||||
}
|
||||
}, 1000)
|
||||
return () => clearInterval(iid)
|
||||
}, [ remoteStream, localVideoEnabled ])
|
||||
|
||||
const minimize = !localVideoEnabled && !remoteVideoEnabled
|
||||
|
||||
return (
|
||||
<Draggable handle=".handle" bounds="body">
|
||||
|
|
@ -30,12 +48,12 @@ const ChatWindow: FC<Props> = function ChatWindow({ userId, incomeStream, localS
|
|||
<Counter startTime={new Date().getTime() } className="text-sm ml-auto" />
|
||||
</div>
|
||||
<div className={cn(stl.videoWrapper, {'hidden' : minimize}, 'relative')}>
|
||||
<VideoContainer stream={ incomeStream } />
|
||||
<VideoContainer stream={ remoteStream } />
|
||||
<div className="absolute bottom-0 right-0 z-50">
|
||||
<VideoContainer stream={ localStream ? localStream.stream : null } muted width={50} />
|
||||
</div>
|
||||
</div>
|
||||
<ChatControls stream={localStream} endCall={endCall} />
|
||||
<ChatControls videoEnabled={localVideoEnabled} setVideoEnabled={setLocalVideoEnabled} stream={localStream} endCall={endCall} />
|
||||
</div>
|
||||
</Draggable>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import RequestLocalStream from 'Player/MessageDistributor/managers/LocalStream';
|
|||
import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
import stl from './AassistActions.css'
|
||||
|
||||
function onClose(stream) {
|
||||
|
|
@ -36,8 +37,8 @@ interface Props {
|
|||
isEnterprise: boolean,
|
||||
}
|
||||
|
||||
function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus, remoteControlEnabled, hasPermission, isEnterprise }: Props) {
|
||||
const [ incomeStream, setIncomeStream ] = useState<MediaStream | null>(null);
|
||||
function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus, remoteControlEnabled, hasPermission, isEnterprise }: Props) {
|
||||
const [ remoteStream, setRemoteStream ] = useState<MediaStream | null>(null);
|
||||
const [ localStream, setLocalStream ] = useState<LocalStream | null>(null);
|
||||
const [ callObject, setCallObject ] = useState<{ end: ()=>void, toggleRemoteControl: ()=>void } | null >(null);
|
||||
|
||||
|
|
@ -51,7 +52,6 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
|
|||
}
|
||||
}, [peerConnectionStatus])
|
||||
|
||||
|
||||
function call() {
|
||||
RequestLocalStream().then(lStream => {
|
||||
setLocalStream(lStream);
|
||||
|
|
@ -63,6 +63,15 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
|
|||
onError
|
||||
));
|
||||
}).catch(onError)
|
||||
|
||||
const confirmCall = async () => {
|
||||
if (await confirm({
|
||||
header: 'Start Call',
|
||||
confirmButton: 'Call',
|
||||
confirmation: `Are you sure you want to call ${userId ? userId : 'User'}?`
|
||||
})) {
|
||||
call()
|
||||
}
|
||||
}
|
||||
|
||||
const inCall = calling !== CallingState.False;
|
||||
|
|
@ -80,7 +89,7 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
|
|||
{[stl.disabled]: cannotCall}
|
||||
)
|
||||
}
|
||||
onClick={ inCall ? callObject?.end : call}
|
||||
onClick={ inCall ? callObject?.end : confirmCall}
|
||||
role="button"
|
||||
>
|
||||
<Icon
|
||||
|
|
@ -91,7 +100,7 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
|
|||
<span className={cn("ml-2", { 'color-red' : inCall })}>{ inCall ? 'End Call' : 'Call' }</span>
|
||||
</div>
|
||||
}
|
||||
content={ `Call ${userId}` }
|
||||
content={ `Call ${userId ? userId : 'User'}` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top right"
|
||||
|
|
@ -115,7 +124,7 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
|
|||
</div>
|
||||
}
|
||||
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
|
||||
{ inCall && callObject && <ChatWindow endCall={callObject.end} userId={userId} incomeStream={incomeStream} localStream={localStream} /> }
|
||||
{ inCall && callObject && <ChatWindow endCall={callObject.end} userId={userId} remoteStream={remoteStream} localStream={localStream} /> }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
fetchFavoriteList as fetchFavoriteSessionList
|
||||
} from 'Duck/sessions';
|
||||
import { countries } from 'App/constants';
|
||||
import { applyFilter, clearEvents } from 'Duck/filters';
|
||||
import { applyFilter, clearEvents, addAttribute } from 'Duck/filters';
|
||||
import { fetchList as fetchFunnelsList } from 'Duck/funnels';
|
||||
import { defaultFilters, preloadedFilters } from 'Types/filter';
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
|
|
@ -34,6 +34,21 @@ const weakEqual = (val1, val2) => {
|
|||
return `${ val1 }` === `${ val2 }`;
|
||||
}
|
||||
|
||||
const allowedQueryKeys = [
|
||||
'userOs',
|
||||
'userId',
|
||||
'userBrowser',
|
||||
'userDevice',
|
||||
'userCountry',
|
||||
'startDate',
|
||||
'endDate',
|
||||
'minDuration',
|
||||
'maxDuration',
|
||||
'referrer',
|
||||
'sort',
|
||||
'order',
|
||||
];
|
||||
|
||||
@withLocationHandlers()
|
||||
@connect(state => ({
|
||||
filter: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
|
|
@ -50,6 +65,7 @@ const weakEqual = (val1, val2) => {
|
|||
}), {
|
||||
fetchFavoriteSessionList,
|
||||
applyFilter,
|
||||
addAttribute,
|
||||
fetchFilterVariables,
|
||||
fetchIntegrationVariables,
|
||||
fetchSources,
|
||||
|
|
@ -89,10 +105,14 @@ export default class BugFinder extends React.PureComponent {
|
|||
};
|
||||
});
|
||||
|
||||
this.props.resetFunnel();
|
||||
this.props.resetFunnelFilters();
|
||||
|
||||
props.resetFunnel();
|
||||
props.resetFunnelFilters();
|
||||
props.fetchFunnelsList(LAST_7_DAYS)
|
||||
|
||||
const queryFilter = this.props.query.all(allowedQueryKeys);
|
||||
if (queryFilter.hasOwnProperty('userId')) {
|
||||
props.addAttribute({ label: 'User Id', key: KEYS.USERID, type: KEYS.USERID, operator: 'is', value: queryFilter.userId })
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
|
|||
|
|
@ -5,20 +5,25 @@ import { NoContent, Loader } from 'UI';
|
|||
import { List, Map } from 'immutable';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import withPermissions from 'HOCs/withPermissions'
|
||||
import { KEYS } from 'Types/filter/customFilter';
|
||||
import { applyFilter, addAttribute } from 'Duck/filters';
|
||||
import Filter from 'Types/filter';
|
||||
|
||||
const AUTOREFRESH_INTERVAL = 1 * 60 * 1000
|
||||
const AUTOREFRESH_INTERVAL = .5 * 60 * 1000
|
||||
|
||||
interface Props {
|
||||
loading: Boolean,
|
||||
list?: List<any>,
|
||||
fetchList: (params) => void,
|
||||
applyFilter: () => void,
|
||||
filters: List<any>
|
||||
filters: Filter
|
||||
addAttribute: (obj) => void,
|
||||
}
|
||||
|
||||
function LiveSessionList(props: Props) {
|
||||
const { loading, list, filters } = props;
|
||||
var timeoutId;
|
||||
const hasUserFilter = filters && filters.filters.map(i => i.key).includes(KEYS.USERID);
|
||||
|
||||
useEffect(() => {
|
||||
props.fetchList(filters.toJS());
|
||||
|
|
@ -28,6 +33,16 @@ function LiveSessionList(props: Props) {
|
|||
}
|
||||
}, [])
|
||||
|
||||
const onUserClick = (userId, userAnonymousId) => {
|
||||
if (userId) {
|
||||
props.addAttribute({ label: 'User Id', key: KEYS.USERID, type: KEYS.USERID, operator: 'is', value: userId })
|
||||
} else {
|
||||
props.addAttribute({ label: 'Anonymous ID', key: 'USERANONYMOUSID', type: "USERANONYMOUSID", operator: 'is', value: userAnonymousId })
|
||||
}
|
||||
|
||||
props.applyFilter()
|
||||
}
|
||||
|
||||
const timeout = () => {
|
||||
timeoutId = setTimeout(() => {
|
||||
props.fetchList(filters.toJS());
|
||||
|
|
@ -52,7 +67,9 @@ function LiveSessionList(props: Props) {
|
|||
<SessionItem
|
||||
key={ session.sessionId }
|
||||
session={ session }
|
||||
live
|
||||
live
|
||||
hasUserFilter={hasUserFilter}
|
||||
onUserClick={onUserClick}
|
||||
/>
|
||||
))}
|
||||
</Loader>
|
||||
|
|
@ -68,6 +85,5 @@ export default withPermissions(['ASSIST_LIVE'])(connect(
|
|||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
}),
|
||||
{
|
||||
fetchList
|
||||
}
|
||||
fetchList, applyFilter, addAttribute }
|
||||
)(LiveSessionList));
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { SideMenuitem, SavedSearchList, Progress, Popup } from 'UI'
|
||||
import { SideMenuitem, SavedSearchList, Progress, Popup, Icon, CircularLoader } from 'UI'
|
||||
import stl from './sessionMenu.css';
|
||||
import { fetchWatchdogStatus } from 'Duck/watchdogs';
|
||||
import { setActiveFlow, clearEvents } from 'Duck/filters';
|
||||
import { setActiveTab } from 'Duck/sessions';
|
||||
import { issues_types } from 'Types/session/issue'
|
||||
import NewBadge from 'Shared/NewBadge';
|
||||
import { fetchList as fetchSessionList } from 'Duck/sessions';
|
||||
|
||||
function SessionsMenu(props) {
|
||||
const {
|
||||
activeFlow, activeTab, watchdogs = [], keyMap, wdTypeCount,
|
||||
fetchWatchdogStatus, toggleRehydratePanel } = props;
|
||||
fetchWatchdogStatus, toggleRehydratePanel, filters, sessionsLoading } = props;
|
||||
|
||||
const onMenuItemClick = (filter) => {
|
||||
props.onMenuItemClick(filter)
|
||||
|
|
@ -76,10 +76,19 @@ function SessionsMenu(props) {
|
|||
<div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
<SideMenuitem
|
||||
title={ <div className="flex items-center">
|
||||
<div>Assist</div>
|
||||
<div className="ml-2">{ <NewBadge />}</div>
|
||||
</div> }
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<div>Assist</div>
|
||||
{ activeTab.type === 'live' && (
|
||||
<div
|
||||
className="ml-4 h-5 w-6 flex items-center justify-center"
|
||||
onClick={() => !sessionsLoading && props.fetchSessionList(filters.toJS())}
|
||||
>
|
||||
{ sessionsLoading ? <CircularLoader className="ml-1" /> : <Icon name="sync-alt" size="14" />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
iconName="person"
|
||||
active={activeTab.type === 'live'}
|
||||
onClick={() => onMenuItemClick({ name: 'Assist', type: 'live' })}
|
||||
|
|
@ -109,6 +118,8 @@ export default connect(state => ({
|
|||
wdTypeCount: state.getIn([ 'sessions', 'wdTypeCount' ]),
|
||||
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
|
||||
captureRate: state.getIn(['watchdogs', 'captureRate']),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
sessionsLoading: state.getIn([ 'sessions', 'loading' ]),
|
||||
}), {
|
||||
fetchWatchdogStatus, setActiveFlow, clearEvents, setActiveTab
|
||||
fetchWatchdogStatus, setActiveFlow, clearEvents, setActiveTab, fetchSessionList
|
||||
})(SessionsMenu);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function AssistScript(props) {
|
|||
r.issue=function(k,p){r.push([6,k,p])};
|
||||
r.isActive=function(){return false};
|
||||
r.getSessionToken=function(){};
|
||||
})(0, "${props.projectKey}", "//static.openreplay.com/3.3.1/openreplay-assist.js",1,28);
|
||||
})(0, "${props.projectKey}", "//static.openreplay.com/3.4.0/openreplay-assist.js",1,28);
|
||||
</script>`}
|
||||
</Highlight>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const BugsnagForm = (props) => (
|
|||
<>
|
||||
<div className="p-5 border-b mb-4">
|
||||
<div>How to integrate Bugsnag with OpenReplay and see backend errors alongside session recordings.</div>
|
||||
<DocLink className="mt-4" label="Integrate Bugsnag" url="https://docs.openreplay.com/integrations/datadog" />
|
||||
<DocLink className="mt-4" label="Integrate Bugsnag" url="https://docs.openreplay.com/integrations/bugsnag" />
|
||||
</div>
|
||||
<IntegrationForm
|
||||
{ ...props }
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class ManageUsers extends React.PureComponent {
|
|||
<React.Fragment>
|
||||
<Loader loading={ loading }>
|
||||
<SlideModal
|
||||
title="Inivte People"
|
||||
title="Invite People"
|
||||
size="small"
|
||||
isDisplayed={ showModal }
|
||||
content={ this.formContent() }
|
||||
|
|
|
|||
|
|
@ -196,9 +196,9 @@ class Sites extends React.PureComponent {
|
|||
<Icon name="trash" size="16" color="teal" />
|
||||
</button>
|
||||
<button
|
||||
className={cn({'hidden' : !canDeleteSites})}
|
||||
disabled={ !canDeleteSites }
|
||||
onClick={ () => canDeleteSites && this.edit(_site) }
|
||||
className={cn({'hidden' : !isAdmin})}
|
||||
disabled={ !isAdmin }
|
||||
onClick={ () => isAdmin && this.edit(_site) }
|
||||
data-clickable
|
||||
>
|
||||
<Icon name="edit" size="16" color="teal"/>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
&:hover {
|
||||
& .actions button {
|
||||
display: unset;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +40,10 @@
|
|||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
& button:not(:last-child) {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export default class ForgotPassword extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
<div className="w-6/12 flex items-center justify-center">
|
||||
<form onSubmit={ this.onSubmit } style={{ minWidth: '50%'}}>
|
||||
<form onSubmit={ this.onSubmit } style={{ minWidth: '50%', textAlign: 'center'}}>
|
||||
<div className="mb-8">
|
||||
<h2 className="text-center text-3xl mb-6">{`${resetting ? 'Create' : 'Reset'} Password`}</h2>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import NewSiteForm from '../Client/Sites/NewSiteForm';
|
|||
@connect(state => ({
|
||||
sites: state.getIn([ 'site', 'list' ]),
|
||||
siteId: state.getIn([ 'user', 'siteId' ]),
|
||||
account: state.getIn([ 'user', 'account' ]),
|
||||
}), {
|
||||
setSiteId,
|
||||
pushNewSite,
|
||||
|
|
@ -32,11 +33,13 @@ export default class SiteDropdown extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { sites, siteId, location: { pathname } } = this.props;
|
||||
const { sites, siteId, account, location: { pathname } } = this.props;
|
||||
const { showProductModal } = this.state;
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
const activeSite = sites.find(s => s.id == siteId);
|
||||
const disabled = !siteChangeAvaliable(pathname);
|
||||
const showCurrent = hasSiteId(pathname) || siteChangeAvaliable(pathname);
|
||||
const canAddSites = isAdmin && account.limits.projects && account.limits.projects.remaining !== 0;
|
||||
return (
|
||||
<div className={ styles.wrapper }>
|
||||
{
|
||||
|
|
@ -64,7 +67,7 @@ export default class SiteDropdown extends React.PureComponent {
|
|||
}
|
||||
</ul>
|
||||
<div
|
||||
className={cn(styles.btnNew, 'flex items-center justify-center py-3 cursor-pointer')}
|
||||
className={cn(styles.btnNew, 'flex items-center justify-center py-3 cursor-pointer', { [styles.disabled] : !canAddSites })}
|
||||
onClick={this.newSite}
|
||||
>
|
||||
<Icon
|
||||
|
|
|
|||
|
|
@ -117,4 +117,9 @@
|
|||
background-color: $gray-lightest;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
import React from 'react'
|
||||
import { Icon, Popup } from 'UI'
|
||||
import { connectPlayer, toggleEvents } from 'Player';
|
||||
import { connectPlayer, toggleEvents, scale } from 'Player';
|
||||
import cn from 'classnames'
|
||||
import stl from './EventsToggleButton.css'
|
||||
|
||||
function EventsToggleButton({ showEvents, toggleEvents }) {
|
||||
const toggle = () => {
|
||||
toggleEvents()
|
||||
scale()
|
||||
}
|
||||
return (
|
||||
<Popup
|
||||
trigger={
|
||||
<button
|
||||
className={cn("absolute right-0 z-50", stl.wrapper)}
|
||||
onClick={toggleEvents}
|
||||
onClick={toggle}
|
||||
>
|
||||
<Icon
|
||||
name={ showEvents ? 'chevron-double-right' : 'chevron-double-left' }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
|||
import { Loader } from 'UI';
|
||||
import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
|
||||
import { withRequest } from 'HOCs'
|
||||
import {
|
||||
import {
|
||||
PlayerProvider,
|
||||
connectPlayer,
|
||||
init as initPlayer,
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ function PlayerContent({ live, fullscreen, showEvents }) {
|
|||
)
|
||||
}
|
||||
|
||||
function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt }) {
|
||||
function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, config }) {
|
||||
useEffect(() => {
|
||||
initPlayer(session, jwt);
|
||||
initPlayer(session, jwt, config);
|
||||
return () => cleanPlayer()
|
||||
}, [ session.sessionId ]);
|
||||
|
||||
|
|
@ -60,6 +60,7 @@ function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscr
|
|||
export default connect(state => ({
|
||||
session: state.getIn([ 'sessions', 'current' ]),
|
||||
jwt: state.get('jwt'),
|
||||
config: state.getIn([ 'user', 'account', 'iceServers' ]),
|
||||
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
|
||||
}), {
|
||||
toggleFullscreen,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//import cn from 'classnames';
|
||||
import { getRE } from 'App/utils';
|
||||
import { Label, NoContent, Input, SlideModal, CloseButton } from 'UI';
|
||||
import { connectPlayer } from 'Player';
|
||||
import { connectPlayer, pause } from 'Player';
|
||||
import Autoscroll from '../Autoscroll';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import TimeTable from '../TimeTable';
|
||||
|
|
@ -20,6 +20,7 @@ export default class Fetch extends React.PureComponent {
|
|||
onFilterChange = (e, { value }) => this.setState({ filter: value })
|
||||
|
||||
setCurrent = (item, index) => {
|
||||
pause()
|
||||
this.setState({ current: item, currentIndex: index });
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +54,6 @@ export default class Fetch extends React.PureComponent {
|
|||
return (
|
||||
<React.Fragment>
|
||||
<SlideModal
|
||||
overlay={false}
|
||||
right
|
||||
size="middle"
|
||||
title={
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { JSONTree, Label, Button, Tabs } from 'UI'
|
||||
import { JSONTree, NoContent, Button, Tabs } from 'UI'
|
||||
import cn from 'classnames';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import stl from './fetchDetails.css';
|
||||
import ResultTimings from '../../shared/ResultTimings/ResultTimings';
|
||||
import Headers from './components/Headers'
|
||||
|
||||
const HEADERS = 'HEADERS';
|
||||
const REQUEST = 'REQUEST';
|
||||
const RESPONSE = 'RESPONSE';
|
||||
const TIMINGS = 'TIMINGS';
|
||||
|
||||
const TABS = [ REQUEST, RESPONSE, TIMINGS ].map(tab => ({ text: tab, key: tab }));
|
||||
const TABS = [ HEADERS, REQUEST, RESPONSE ].map(tab => ({ text: tab, key: tab }));
|
||||
|
||||
export default class FetchDetails extends React.PureComponent {
|
||||
state = { activeTab: REQUEST, tabs: [] };
|
||||
|
|
@ -20,53 +20,64 @@ export default class FetchDetails extends React.PureComponent {
|
|||
}
|
||||
|
||||
renderActiveTab = tab => {
|
||||
const { resource: { duration, timings }, isResult } = this.props;
|
||||
const { resource: { payload, response = this.props.resource.body} } = this.props;
|
||||
let jsonPayload, jsonResponse, requestHeaders, responseHeaders = undefined;
|
||||
|
||||
try {
|
||||
jsonPayload = typeof payload === 'string' ? JSON.parse(payload) : payload
|
||||
requestHeaders = jsonPayload.headers
|
||||
jsonPayload.body = typeof jsonPayload.body === 'string' ? JSON.parse(jsonPayload.body) : jsonPayload.body
|
||||
delete jsonPayload.headers
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
jsonResponse = typeof response === 'string' ? JSON.parse(response) : response;
|
||||
responseHeaders = jsonResponse.headers
|
||||
jsonResponse.body = typeof jsonResponse.body === 'string' ? JSON.parse(jsonResponse.body) : jsonResponse.body
|
||||
delete jsonResponse.headers
|
||||
} catch (e) {}
|
||||
|
||||
switch(tab) {
|
||||
case REQUEST:
|
||||
const { resource: { payload } } = this.props;
|
||||
let jsonPayload = undefined;
|
||||
try {
|
||||
jsonPayload = typeof payload === 'string' ? JSON.parse(payload) : payload
|
||||
} catch (e) {}
|
||||
|
||||
return !!payload ? (
|
||||
<div>
|
||||
<div className="mt-6">
|
||||
{/* <h5>{ 'Payload '}</h5> */}
|
||||
{ jsonPayload === undefined
|
||||
? <div className="ml-3 break-words my-3"> { payload } </div>
|
||||
: <JSONTree src={ jsonPayload } collapsed={ false } enableClipboard />
|
||||
}
|
||||
</div>
|
||||
<div className="divider"/>
|
||||
</div>
|
||||
) : ''
|
||||
break;
|
||||
return (
|
||||
<NoContent
|
||||
title="Body is Empty."
|
||||
size="small"
|
||||
show={ !payload }
|
||||
icon="exclamation-circle"
|
||||
>
|
||||
<div>
|
||||
<div className="mt-6">
|
||||
{ jsonPayload === undefined
|
||||
? <div className="ml-3 break-words my-3"> { payload } </div>
|
||||
: <JSONTree src={ jsonPayload } collapsed={ false } enableClipboard />
|
||||
}
|
||||
</div>
|
||||
<div className="divider"/>
|
||||
</div>
|
||||
</NoContent>
|
||||
)
|
||||
case RESPONSE:
|
||||
const { resource: { response = this.props.resource.body } } = this.props; // for IOS compat.
|
||||
let jsonResponse = undefined;
|
||||
try {
|
||||
jsonResponse = JSON.parse(response);
|
||||
} catch (e) {}
|
||||
|
||||
return !!response ? (
|
||||
<div>
|
||||
<div className="mt-6">
|
||||
{/* <h5>{ 'Response '}</h5> */}
|
||||
{ jsonResponse === undefined
|
||||
? <div className="ml-3 break-words my-3"> { response } </div>
|
||||
: <JSONTree src={ jsonResponse } collapsed={ false } enableClipboard />
|
||||
}
|
||||
</div>
|
||||
<div className="divider"/>
|
||||
</div>
|
||||
// jsonResponse === undefined
|
||||
// ? <div className="ml-3 break-words my-3"> { response } </div>
|
||||
// : <JSONTree src={ jsonResponse } collapsed={ false } enableClipboard />
|
||||
) : ''
|
||||
break;
|
||||
case TIMINGS:
|
||||
return <ResultTimings duration={duration} timing={timings} />
|
||||
return (
|
||||
<NoContent
|
||||
title="Body is Empty."
|
||||
size="small"
|
||||
show={ !response }
|
||||
icon="exclamation-circle"
|
||||
>
|
||||
<div>
|
||||
<div className="mt-6">
|
||||
{ jsonResponse === undefined
|
||||
? <div className="ml-3 break-words my-3"> { response } </div>
|
||||
: <JSONTree src={ jsonResponse } collapsed={ false } enableClipboard />
|
||||
}
|
||||
</div>
|
||||
<div className="divider"/>
|
||||
</div>
|
||||
</NoContent>
|
||||
)
|
||||
case HEADERS:
|
||||
return <Headers requestHeaders={requestHeaders} responseHeaders={responseHeaders} />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,21 +89,18 @@ export default class FetchDetails extends React.PureComponent {
|
|||
|
||||
checkTabs() {
|
||||
const { resource: { payload, response, body }, isResult } = this.props;
|
||||
const _tabs = TABS.filter(t => {
|
||||
if (t.key == REQUEST && !!payload) {
|
||||
return true
|
||||
}
|
||||
const _tabs = TABS
|
||||
// const _tabs = TABS.filter(t => {
|
||||
// if (t.key == REQUEST && !!payload) {
|
||||
// return true
|
||||
// }
|
||||
|
||||
if (t.key == RESPONSE && !!response) {
|
||||
return true;
|
||||
}
|
||||
// if (t.key == RESPONSE && !!response) {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
if (t.key == TIMINGS && isResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
// return false;
|
||||
// })
|
||||
this.setState({ tabs: _tabs, activeTab: _tabs.length > 0 ? _tabs[0].key : null })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import React from 'react'
|
||||
import { NoContent, TextEllipsis } from 'UI'
|
||||
import stl from './headers.css'
|
||||
|
||||
function Headers(props) {
|
||||
return (
|
||||
<div>
|
||||
<NoContent
|
||||
title="No data available."
|
||||
size="small"
|
||||
show={ !props.requestHeaders && !props.responseHeaders }
|
||||
icon="exclamation-circle"
|
||||
>
|
||||
{ props.requestHeaders && (
|
||||
<>
|
||||
<div className="mb-4 mt-4">
|
||||
<div className="my-2 font-medium">Request Headers</div>
|
||||
{
|
||||
Object.keys(props.requestHeaders).map(h => (
|
||||
<div className={stl.row}>
|
||||
<span className="mr-2 font-medium">{h}:</span>
|
||||
<span>{props.requestHeaders[h]}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<hr />
|
||||
</>
|
||||
)}
|
||||
|
||||
{ props.responseHeaders && (
|
||||
<div className="mt-4">
|
||||
<div className="my-2 font-medium">Response Headers</div>
|
||||
{
|
||||
Object.keys(props.responseHeaders).map(h => (
|
||||
<div className={stl.row}>
|
||||
<span className="mr-2 font-medium">{h}:</span>
|
||||
<span>{props.responseHeaders[h]}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</NoContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Headers;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
.row {
|
||||
/* display: flex; */
|
||||
padding: 5px 0px;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
/* padding-left: 20px; */
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
|
||||
& div:last-child {
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './Headers'
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import cn from 'classnames';
|
||||
import { connectPlayer } from 'Player';
|
||||
import { connectPlayer, jump, pause } from 'Player';
|
||||
import { QuestionMarkHint, Popup, Tabs, Input } from 'UI';
|
||||
import { getRE } from 'App/utils';
|
||||
import { TYPES } from 'Types/session/resource';
|
||||
|
|
@ -101,7 +101,7 @@ function renderSize(r) {
|
|||
export function renderDuration(r) {
|
||||
if (!r.success) return 'x';
|
||||
|
||||
const text = `${ r.duration }ms`;
|
||||
const text = `${ Math.round(r.duration) }ms`;
|
||||
if (!r.isRed() && !r.isYellow()) return text;
|
||||
|
||||
let tooltipText;
|
||||
|
|
@ -131,6 +131,7 @@ export function renderDuration(r) {
|
|||
domContentLoadedTime: state.domContentLoadedTime,
|
||||
loadTime: state.loadTime,
|
||||
time: state.time,
|
||||
playing: state.playing,
|
||||
domBuildingTime: state.domBuildingTime,
|
||||
fetchPresented: state.fetchList.length > 0,
|
||||
}))
|
||||
|
|
@ -138,6 +139,13 @@ export default class Network extends React.PureComponent {
|
|||
state = {
|
||||
filter: '',
|
||||
activeTab: ALL,
|
||||
currentIndex: 0
|
||||
}
|
||||
|
||||
onRowClick = (e, index) => {
|
||||
pause();
|
||||
jump(e.time);
|
||||
this.setState({ currentIndex: index })
|
||||
}
|
||||
|
||||
onTabClick = activeTab => this.setState({ activeTab })
|
||||
|
|
@ -151,9 +159,10 @@ export default class Network extends React.PureComponent {
|
|||
loadTime,
|
||||
domBuildingTime,
|
||||
fetchPresented,
|
||||
time
|
||||
time,
|
||||
playing
|
||||
} = this.props;
|
||||
const { filter, activeTab } = this.state;
|
||||
const { filter, activeTab, currentIndex } = this.state;
|
||||
const filterRE = getRE(filter, 'i');
|
||||
let filtered = resources.filter(({ type, name }) =>
|
||||
filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
|
||||
|
|
@ -200,6 +209,8 @@ export default class Network extends React.PureComponent {
|
|||
fetchPresented = { fetchPresented }
|
||||
resourcesSize={resourcesSize}
|
||||
transferredSize={transferredSize}
|
||||
onRowClick={ this.onRowClick }
|
||||
currentIndex={playing ? null : currentIndex}
|
||||
/>
|
||||
{/* <BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
|
|
|
|||
|
|
@ -157,13 +157,14 @@ export default class NetworkContent extends React.PureComponent {
|
|||
additionalHeight = 0,
|
||||
resourcesSize,
|
||||
transferredSize,
|
||||
time
|
||||
time,
|
||||
currentIndex
|
||||
} = this.props;
|
||||
const { filter, activeTab } = this.state;
|
||||
const filterRE = getRE(filter, 'i');
|
||||
let filtered = resources.filter(({ type, name }) =>
|
||||
filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[ activeTab ]));
|
||||
const lastIndex = filtered.filter(item => item.time <= time).length - 1;
|
||||
const lastIndex = currentIndex || filtered.filter(item => item.time <= time).length - 1;
|
||||
|
||||
const referenceLines = [];
|
||||
if (domContentLoadedTime != null) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { NoContent } from 'UI'
|
|||
import { connectPlayer } from 'Player/store';
|
||||
import SelectorCard from '../SelectorCard/SelectorCard';
|
||||
import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/StatedScreen';
|
||||
import stl from './selectorList.css'
|
||||
|
||||
interface Props {
|
||||
targets: Array<MarkedTarget>,
|
||||
|
|
@ -16,9 +17,11 @@ function SelectorsList({ targets, activeTargetIndex }: Props) {
|
|||
size="small"
|
||||
show={ targets && targets.length === 0 }
|
||||
>
|
||||
{ targets && targets.map((target, index) => (
|
||||
<SelectorCard target={target} index={index} showContent={activeTargetIndex === index} />
|
||||
))}
|
||||
<div className={stl.wrapper}>
|
||||
{ targets && targets.map((target, index) => (
|
||||
<SelectorCard target={target} index={index} showContent={activeTargetIndex === index} />
|
||||
))}
|
||||
</div>
|
||||
</NoContent>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
.wrapper {
|
||||
height: calc(100vh - 190px);
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
background: transparent !important;
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: transparent !important;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent !important;
|
||||
}
|
||||
&:hover {
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
background: rgba(0,0,0,0.1)
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(0,0,0,0.1)
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0,0,0,0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
.text {
|
||||
color: $gray-light;
|
||||
font-size: 40px;
|
||||
max-width: 680px;
|
||||
line-height: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -7,5 +7,5 @@ interface Props {
|
|||
}
|
||||
|
||||
export default function LiveStatusText({ text }: Props) {
|
||||
return <div className={ovStl.overlay}><span className={stl.text}>{text}</span></div>
|
||||
return <div className={ovStl.overlay}><div className={stl.text}>{text}</div></div>
|
||||
}
|
||||
|
|
@ -80,12 +80,12 @@ function NetworkTab(props) {
|
|||
onRowClick={ (e, index) => { setCurrent(e); setCurrentIndex(index)} }
|
||||
additionalHeight={vh}
|
||||
fetchPresented = { run.fetchPresented }
|
||||
|
||||
loadTime = { run.loadTime }
|
||||
domBuildingTime = { run.domBuildingTime }
|
||||
resourcesSize = { run.resourcesSize }
|
||||
transferredSize = { run.transferredSize }
|
||||
domContentLoadedTime = { run.domContentLoadedTime }
|
||||
currentIndex={currentIndex}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ const TrackerUpdateMessage= (props) => {
|
|||
const { site, sites, match: { params: { siteId } } } = props;
|
||||
const activeSite = sites.find(s => s.id == siteId);
|
||||
const hasSessions = !!activeSite && !activeSite.recorded;
|
||||
const needUpdate = !hasSessions && site.trackerVersion !== window.ENV.TRACKER_VERSION;
|
||||
const appVersionInt = parseInt(window.ENV.TRACKER_VERSION.split(".").join(""))
|
||||
const trackerVersionInt = site.trackerVersion ? parseInt(site.trackerVersion.split(".").join("")) : 0
|
||||
const needUpdate = !hasSessions && trackerVersionInt > appVersionInt;
|
||||
return needUpdate ? (
|
||||
<>
|
||||
{(
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ const reducer = (state = initialState, action = {}) => {
|
|||
};
|
||||
|
||||
export default withRequestState({
|
||||
_: [ FETCH, FETCH_LIST ],
|
||||
_: [ FETCH, FETCH_LIST, FETCH_LIVE_LIST ],
|
||||
fetchFavoriteListRequest: FETCH_FAVORITE_LIST,
|
||||
toggleFavoriteRequest: TOGGLE_FAVORITE,
|
||||
fetchErrorStackList: FETCH_ERROR_STACK,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import type { Message } from '../messages'
|
|||
import { ID_TP_MAP } from '../messages';
|
||||
import store from 'App/store';
|
||||
import type { LocalStream } from './LocalStream';
|
||||
|
||||
import { update, getState } from '../../store';
|
||||
import { iceServerConfigFromString } from 'App/utils'
|
||||
|
||||
|
||||
export enum CallingState {
|
||||
|
|
@ -118,7 +118,7 @@ function resolveCSS(baseURL: string, css: string): string {
|
|||
}
|
||||
|
||||
export default class AssistManager {
|
||||
constructor(private session, private md: MessageDistributor, private config) {}
|
||||
constructor(private session, private config, private md: MessageDistributor, private config) {}
|
||||
|
||||
private setStatus(status: ConnectionStatus) {
|
||||
if (status === ConnectionStatus.Connecting) {
|
||||
|
|
@ -162,7 +162,7 @@ export default class AssistManager {
|
|||
iceTransportPolicy: 'relay',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const peer = new Peer(_config);
|
||||
this.peer = peer;
|
||||
peer.on('error', e => {
|
||||
|
|
@ -380,7 +380,7 @@ export default class AssistManager {
|
|||
const data = this.md.getInternalCoordinates(e);
|
||||
// const el = this.md.getElementFromPoint(e); // requires requestiong node_id from domManager
|
||||
const el = this.md.getElementFromInternalPoint(data)
|
||||
if (el instanceof HTMLElement) {
|
||||
if (el instanceof HTMLElement) {
|
||||
el.focus()
|
||||
el.oninput = e => e.preventDefault();
|
||||
el.onkeydown = e => e.preventDefault();
|
||||
|
|
@ -403,6 +403,13 @@ export default class AssistManager {
|
|||
}
|
||||
}
|
||||
|
||||
private onMouseClick = (e: MouseEvent): void => {
|
||||
const conn = this.dataConnection;
|
||||
if (!conn) { return; }
|
||||
const data = this.md.getInternalCoordinates(e);
|
||||
// const el = this.md.getElementFromPoint(e); // requires requestiong node_id from domManager
|
||||
conn.send({ type: "click", x: Math.round(data.x), y: Math.round(data.y) });
|
||||
}
|
||||
|
||||
private localCallData: {
|
||||
localStream: LocalStream,
|
||||
|
|
@ -420,6 +427,7 @@ export default class AssistManager {
|
|||
onCallEnd();
|
||||
this.toggleRemoteControl(false);
|
||||
this.md.overlay.removeEventListener("mousemove", this.onMouseMove);
|
||||
this.md.overlay.removeEventListener("click", this.onMouseClick);
|
||||
update({ calling: CallingState.False });
|
||||
this.localCallData = null;
|
||||
},
|
||||
|
|
@ -442,7 +450,7 @@ export default class AssistManager {
|
|||
|
||||
const call = this.peer.call(this.peerID, this.localCallData.localStream.stream);
|
||||
this.localCallData.localStream.onVideoTrack(vTrack => {
|
||||
const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video")
|
||||
const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video")
|
||||
if (!sender) {
|
||||
//logger.warn("No video sender found")
|
||||
return
|
||||
|
|
@ -459,7 +467,7 @@ export default class AssistManager {
|
|||
});
|
||||
|
||||
this.md.overlay.addEventListener("mousemove", this.onMouseMove)
|
||||
|
||||
this.md.overlay.addEventListener("click", this.onMouseClick)
|
||||
});
|
||||
//call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track))
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export default Member.extend({
|
|||
license: '',
|
||||
expirationDate: undefined,
|
||||
permissions: [],
|
||||
iceServers: undefined
|
||||
}, {
|
||||
fromJS: ({ current = {}, ...account})=> ({
|
||||
...account,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default Record({
|
|||
recorded: undefined,
|
||||
stackIntegrations: false,
|
||||
projectKey: undefined,
|
||||
trackerVersion: undefined,
|
||||
}, {
|
||||
idKey: 'id',
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -203,4 +203,27 @@ export const colorScale = (values, colors) => {
|
|||
.range([colors[0], colors[colors.length - 1]]);
|
||||
}
|
||||
|
||||
export const truncate = (input, max = 10) => input.length > max ? `${input.substring(0, max)}...` : input;
|
||||
export const truncate = (input, max = 10) => input.length > max ? `${input.substring(0, max)}...` : input;
|
||||
|
||||
export const iceServerConfigFromString = (str) => {
|
||||
if (!str || typeof str !== 'string'|| str.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str.split("|").map(function(c) {
|
||||
let server = null
|
||||
const arr = c.split(",")
|
||||
|
||||
if (!!arr[0] !== "") {
|
||||
server = {}
|
||||
server.urls = arr[0]
|
||||
if (!!arr[1]) {
|
||||
server.username = arr[1]
|
||||
if (!!arr[2]) {
|
||||
server.credential = arr[2]
|
||||
}
|
||||
}
|
||||
return server
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -20,9 +20,10 @@ const oss = {
|
|||
MINIO_USE_SSL: process.env.MINIO_USE_SSL,
|
||||
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||
ICE_SERVERS: process.env.ICE_SERVERS,
|
||||
TRACKER_VERSION: '3.4.10', // trackerInfo.version,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
oss,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,11 +57,12 @@ env:
|
|||
# Enable logging for python app
|
||||
# Ref: https://stackoverflow.com/questions/43969743/logs-in-kubernetes-pod-not-showing-up
|
||||
PYTHONUNBUFFERED: '0'
|
||||
version_number: '1.3.0'
|
||||
version_number: '1.3.6'
|
||||
SAML2_MD_URL: ''
|
||||
idp_entityId: ''
|
||||
idp_sso_url: ''
|
||||
idp_x509cert: ''
|
||||
idp_sls_url: ''
|
||||
idp_name: ''
|
||||
assist_secret: ''
|
||||
assist_secret: ''
|
||||
iceServers: ''
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ env:
|
|||
AWS_SECRET_ACCESS_KEY: "m1n10s3CretK3yPassw0rd"
|
||||
AWS_REGION: us-east-1
|
||||
POSTGRES_STRING: postgres://postgres:asayerPostgres@postgresql.db.svc.cluster.local:5432
|
||||
CACHE_ASSETS: true
|
||||
#
|
||||
REDIS_STRING: redis-master.db.svc.cluster.local:6379
|
||||
KAFKA_SERVERS: kafka.db.svc.cluster.local:9092
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ spec:
|
|||
type: DirectoryOrCreate
|
||||
{{- else }}
|
||||
volumeMounts:
|
||||
- name: {{ .Values.pvc.name }}
|
||||
- name: datadir
|
||||
mountPath: {{ .Values.pvc.mountPath }}
|
||||
volumes:
|
||||
- name: {{ .Values.pvc.name }}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
BEGIN;
|
||||
CREATE INDEX pages_session_id_timestamp_idx ON events.pages (session_id, timestamp);
|
||||
|
||||
CREATE INDEX projects_tenant_id_idx ON projects(tenant_id);
|
||||
CREATE INDEX webhooks_tenant_id_idx ON webhooks(tenant_id);
|
||||
CREATE INDEX issues_project_id_idx ON issues(project_id);
|
||||
CREATE INDEX jobs_project_id_idx ON jobs(project_id);
|
||||
CREATE INDEX issues_project_id_idx ON issues (project_id);
|
||||
CREATE INDEX jobs_project_id_idx ON jobs (project_id);
|
||||
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -255,6 +255,15 @@ $$
|
|||
"defaultInputMode": "plain"
|
||||
}'::jsonb -- ??????
|
||||
);
|
||||
CREATE INDEX ON public.projects (project_key);
|
||||
|
||||
CREATE OR REPLACE FUNCTION notify_project() RETURNS trigger AS
|
||||
$$
|
||||
BEGIN
|
||||
PERFORM pg_notify('project', row_to_json(NEW)::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER on_insert_or_update
|
||||
AFTER INSERT OR UPDATE
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ data:
|
|||
proxy_set_header Connection "";
|
||||
chunked_transfer_encoding off;
|
||||
|
||||
client_max_body_size 50M;
|
||||
|
||||
proxy_pass http://minio.db.svc.cluster.local:9000;
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +43,9 @@ data:
|
|||
proxy_set_header X-Real-IP $real_ip;
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://http-openreplay.app.svc.cluster.local;
|
||||
proxy_read_timeout 300;
|
||||
proxy_connect_timeout 120;
|
||||
proxy_send_timeout 300;
|
||||
}
|
||||
location /grafana {
|
||||
set $target http://monitoring-grafana.monitoring.svc.cluster.local;
|
||||
|
|
@ -138,6 +139,7 @@ data:
|
|||
# server_name _;
|
||||
# return 301 https://$host$request_uri;
|
||||
include /etc/nginx/conf.d/location.list;
|
||||
client_max_body_size 10M;
|
||||
}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
|
|
@ -146,4 +148,5 @@ data:
|
|||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA HIGH !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
|
||||
include /etc/nginx/conf.d/location.list;
|
||||
client_max_body_size 10M;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,10 +54,6 @@
|
|||
delay: 60
|
||||
register: result
|
||||
until: result.rc == 0
|
||||
retries: 3
|
||||
delay: 60
|
||||
register: result
|
||||
until: result.rc == 0
|
||||
- name: Creating kafka topics
|
||||
shell: |
|
||||
# Creating topic
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ domain_name: ""
|
|||
docker_registry_username: ""
|
||||
docker_registry_password: ""
|
||||
docker_registry_url: "rg.fr-par.scw.cloud/foss"
|
||||
image_tag: "v1.3.0"
|
||||
openreplay_version: "v1.3.0"
|
||||
image_tag: "v1.3.5"
|
||||
openreplay_version: "v1.3.5"
|
||||
|
||||
# Nginx ssl certificates.
|
||||
# in cert format
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ domain_name: "{{ domain_name }}"
|
|||
docker_registry_username: "{{ docker_registry_username }}"
|
||||
docker_registry_password: "{{ docker_registry_password }}"
|
||||
docker_registry_url: "{{ docker_registry_url }}"
|
||||
image_tag: "v1.3.0"
|
||||
openreplay_version: "v1.3.0"
|
||||
image_tag: "v1.3.5"
|
||||
openreplay_version: "v1.3.5"
|
||||
|
||||
# Nginx ssl certificates.
|
||||
# in cert format
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
## Licenses (as of April 30, 2021)
|
||||
## Licenses (as of October 28, 2021)
|
||||
|
||||
Below is the list of dependencies used in OpenReplay software. Licenses may change between versions, so please keep this up to date with every new library you use.
|
||||
|
||||
|
|
@ -85,4 +85,7 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan
|
|||
| croniter | MIT | Python |
|
||||
| lib/pq | MIT | Go |
|
||||
| peerjs | MIT | JavaScript |
|
||||
| antonmedv/finder | MIT | JavaScript |
|
||||
| antonmedv/finder | MIT | JavaScript |
|
||||
| elasticsearch-py | Apache2 | Python |
|
||||
| sentry-python | BSD2 | Python |
|
||||
| jira | BSD2 | Python |
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# OpenReplay Tracker Axios plugin
|
||||
# OpenReplay Tracker Assist plugin
|
||||
|
||||
Tracker plugin for WebRTC video support at your site.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { LocalStream } from './LocalStream.js';
|
||||
import type { LocalStream } from './LocalStream';
|
||||
|
||||
const SS_START_TS_KEY = "__openreplay_assist_call_start_ts"
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ export default class CallWindow {
|
|||
})
|
||||
}
|
||||
|
||||
private aRemote: HTMLAudioElement | null = null;
|
||||
private aRemote: HTMLAudioElement | null = null;
|
||||
private checkRemoteVideoInterval: ReturnType<typeof setInterval>
|
||||
setRemoteStream(rStream: MediaStream) {
|
||||
this.load.then(() => {
|
||||
|
|
@ -178,7 +178,7 @@ export default class CallWindow {
|
|||
private localStream: LocalStream | null = null;
|
||||
|
||||
// TODO: on construction?
|
||||
setLocalStream(lStream: LocalStream) {
|
||||
setLocalStream(lStream: LocalStream) {
|
||||
this.localStream = lStream
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ export default class CallWindow {
|
|||
private toggleAudio() {
|
||||
const enabled = this.localStream?.toggleAudio() || false
|
||||
this.toggleAudioUI(enabled)
|
||||
// if (!this.audioBtn) { return; }
|
||||
// if (!this.audioBtn) { return; }
|
||||
// if (enabled) {
|
||||
// this.audioBtn.classList.remove("muted");
|
||||
// this.audioBtn.childNodes[1].textContent = "Mute";
|
||||
|
|
@ -218,7 +218,7 @@ export default class CallWindow {
|
|||
}
|
||||
|
||||
private toggleVideoUI(enabled: boolean) {
|
||||
if (!this.videoBtn || !this.videoContainer) { return; }
|
||||
if (!this.videoBtn || !this.videoContainer) { return; }
|
||||
if (enabled) {
|
||||
this.videoContainer.classList.add("local")
|
||||
this.videoBtn.classList.remove("off");
|
||||
|
|
@ -239,7 +239,7 @@ export default class CallWindow {
|
|||
this.vLocal.srcObject = this.localStream.stream
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
remove() {
|
||||
|
|
@ -247,10 +247,10 @@ export default class CallWindow {
|
|||
clearInterval(this.tsInterval)
|
||||
clearInterval(this.checkRemoteVideoInterval)
|
||||
if (this.iframe.parentElement) {
|
||||
document.body.removeChild(this.iframe)
|
||||
document.body.removeChild(this.iframe)
|
||||
}
|
||||
if (this.aRemote && this.aRemote.parentElement) {
|
||||
document.body.removeChild(this.aRemote)
|
||||
document.body.removeChild(this.aRemote)
|
||||
}
|
||||
sessionStorage.removeItem(SS_START_TS_KEY)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ enum CallingState {
|
|||
//@ts-ignore peerjs hack for webpack5 (?!) TODO: ES/node modules;
|
||||
Peer = Peer.default || Peer;
|
||||
|
||||
// type IncomeMessages =
|
||||
// "call_end" |
|
||||
// { type: "agent_name", name: string } |
|
||||
// type IncomeMessages =
|
||||
// "call_end" |
|
||||
// { type: "agent_name", name: string } |
|
||||
// { type: "click", x: number, y: number } |
|
||||
// { x: number, y: number }
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ export default function(opts?: Partial<Options>) {
|
|||
peer.on('call', function(call) {
|
||||
log("Call: ", call)
|
||||
if (!peer) { return; }
|
||||
const dataConn: DataConnection | undefined =
|
||||
const dataConn: DataConnection | undefined =
|
||||
openDataConnections[call.peer]?.conn;
|
||||
if (callingState !== CallingState.False || !dataConn || !dataConn.open) {
|
||||
call.close();
|
||||
|
|
@ -128,7 +128,7 @@ export default function(opts?: Partial<Options>) {
|
|||
sessionStorage.removeItem(options.session_calling_peer_key);
|
||||
}
|
||||
callingState = newState;
|
||||
}
|
||||
}
|
||||
|
||||
const notifyCallEnd = () => {
|
||||
dataConn.open && dataConn.send("call_end");
|
||||
|
|
@ -145,7 +145,7 @@ export default function(opts?: Partial<Options>) {
|
|||
confirmAnswer = confirm.mount();
|
||||
dataConn.on('data', (data) => { // if call cancelled by a caller before confirmation
|
||||
if (data === "call_end") {
|
||||
log("Recieved call_end during confirm window opened")
|
||||
log("Received call_end during confirm window opened")
|
||||
confirm.remove();
|
||||
setCallingState(CallingState.False);
|
||||
}
|
||||
|
|
@ -209,7 +209,7 @@ export default function(opts?: Partial<Options>) {
|
|||
return;
|
||||
}
|
||||
if (data.name === 'string') {
|
||||
log("name recieved: ", data)
|
||||
log("Name received: ", data)
|
||||
callUI.setAssistentName(data.name);
|
||||
}
|
||||
if (data.type === "scroll" && Array.isArray(data.delta)) {
|
||||
|
|
@ -222,11 +222,11 @@ export default function(opts?: Partial<Options>) {
|
|||
if(el.scrollWidth > el.clientWidth) {
|
||||
el.scrollLeft += data.delta[0]
|
||||
scrolled = true
|
||||
}
|
||||
}
|
||||
if (el && el.scrollHeight > el.clientHeight) {
|
||||
el.scrollTop += data.delta[1]
|
||||
scrolled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!scrolled) {
|
||||
window.scroll(scrEl.scrollLeft + data.delta[0], scrEl.scrollTop + data.delta[1])
|
||||
|
|
@ -246,7 +246,7 @@ export default function(opts?: Partial<Options>) {
|
|||
});
|
||||
|
||||
lStream.onVideoTrack(vTrack => {
|
||||
const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video")
|
||||
const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video")
|
||||
if (!sender) {
|
||||
warn("No video sender found")
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue