diff --git a/.github/workflows/api-ee.yaml b/.github/workflows/api-ee.yaml index e41779634..9eb23910a 100644 --- a/.github/workflows/api-ee.yaml +++ b/.github/workflows/api-ee.yaml @@ -1,6 +1,11 @@ # This action will push the chalice changes to aws on: workflow_dispatch: + inputs: + skip_security_checks: + description: 'Skip Security checks if there is a unfixable vuln or error. Value: true/false' + required: false + default: 'false' push: branches: - dev @@ -46,8 +51,23 @@ jobs: IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }}-ee ENVIRONMENT: staging run: | + skip_security_checks=${{ github.event.inputs.skip_security_checks }} cd api - PUSH_IMAGE=1 bash build.sh ee + PUSH_IMAGE=0 bash -x ./build.sh ee + [[ "x$skip_security_checks" == "xtrue" ]] || { + curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ + images=("chalice" "alerts") + for image in ${images[*]};do + ./trivy image --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG + done + err_code=$? + [[ $err_code -ne 0 ]] && { + exit $err_code + } + } && { + echo "Skipping Security Checks" + } + PUSH_IMAGE=1 bash -x ./build.sh ee - name: Creating old image input run: | # @@ -94,6 +114,17 @@ jobs: IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: ee + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' + # - name: Debug Job # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index ee49ded09..e4d85ff24 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -1,6 +1,11 @@ # This action will push the chalice changes to aws on: workflow_dispatch: + inputs: + skip_security_checks: + description: 'Skip Security checks if there is a unfixable vuln or error. Value: true/false' + required: false + default: 'false' push: branches: - dev @@ -45,8 +50,23 @@ jobs: IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging run: | + skip_security_checks=${{ github.event.inputs.skip_security_checks }} cd api - PUSH_IMAGE=1 bash build.sh + PUSH_IMAGE=0 bash -x ./build.sh + [[ "x$skip_security_checks" == "xtrue" ]] || { + curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ + images=("chalice" "alerts") + for image in ${images[*]};do + ./trivy image --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG + done + err_code=$? + [[ $err_code -ne 0 ]] && { + exit $err_code + } + } && { + echo "Skipping Security Checks" + } + PUSH_IMAGE=1 bash -x ./build.sh - name: Creating old image input run: | # @@ -93,6 +113,17 @@ jobs: IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: foss + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' + # - name: Debug Job # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 diff --git a/.github/workflows/workers-ee.yaml b/.github/workflows/workers-ee.yaml index 3035148ec..35580b5a9 100644 --- a/.github/workflows/workers-ee.yaml +++ b/.github/workflows/workers-ee.yaml @@ -7,6 +7,10 @@ on: description: 'Name of a single service to build(in small letters). "all" to build everything' required: false default: 'false' + skip_security_checks: + description: 'Skip Security checks if there is a unfixable vuln or error. Value: true/false' + required: false + default: 'false' push: branches: - dev @@ -61,6 +65,7 @@ jobs: # set -x touch /tmp/images_to_build.txt + skip_security_checks=${{ github.event.inputs.skip_security_checks }} tmp_param=${{ github.event.inputs.build_service }} build_param=${tmp_param:-'false'} case ${build_param} in @@ -89,7 +94,18 @@ jobs: for image in $(cat /tmp/images_to_build.txt); do echo "Bulding $image" - PUSH_IMAGE=1 bash -x ./build.sh ee $image + PUSH_IMAGE=0 bash -x ./build.sh skip $image + [[ "x$skip_security_checks" == "xtrue" ]] || { + curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ + ./trivy image --exit-code 1 --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG + err_code=$? + [[ $err_code -ne 0 ]] && { + exit $err_code + } + } && { + echo "Skipping Security Checks" + } + PUSH_IMAGE=1 bash -x ./build.sh skip $image echo "::set-output name=image::$DOCKER_REPO/$image:$IMAGE_TAG" done @@ -140,6 +156,18 @@ jobs: # Deploy command helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true | kubectl apply -f - + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: ee + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' + + # - name: Debug Job # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 diff --git a/.github/workflows/workers.yaml b/.github/workflows/workers.yaml index 472d48a67..341a196ad 100644 --- a/.github/workflows/workers.yaml +++ b/.github/workflows/workers.yaml @@ -153,6 +153,16 @@ jobs: # Deploy command helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true | kubectl apply -f - + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: foss + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' # - name: Debug Job # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 diff --git a/api/build.sh b/api/build.sh index a7abdb877..895f9bb8e 100644 --- a/api/build.sh +++ b/api/build.sh @@ -6,8 +6,16 @@ # Default will be OSS build. # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh -set -e +# Helper function +exit_err() { + err_code=$1 + if [[ err_code != 0 ]]; then + exit $err_code + fi +} + +environment=$1 git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} envarg="default-foss" check_prereq() { @@ -45,12 +53,13 @@ function build_api(){ } check_prereq -build_api $1 +build_api $environment echo buil_complete IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_alerts.sh $1 -[[ $1 == "ee" ]] && { +[[ $environment == "ee" ]] && { cp ../ee/api/build_crons.sh . IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_crons.sh $1 + exit_err $? rm build_crons.sh -} +} || true diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 0a5e5e59d..7fd68e029 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -1,5 +1,5 @@ from os import access, R_OK -from os.path import exists as path_exists +from os.path import exists as path_exists, getsize import jwt import requests @@ -207,9 +207,11 @@ def get_raw_mob_by_id(project_id, session_id): path_to_file = efs_path + "/" + __get_mob_path(project_id=project_id, session_id=session_id) if path_exists(path_to_file): if not access(path_to_file, R_OK): - raise HTTPException(400, f"Replay file found under: {efs_path};" - f" but it is not readable, please check permissions") - + raise HTTPException(400, f"Replay file found under: {efs_path};" + + f" but it is not readable, please check permissions") + # getsize return size in bytes, UNPROCESSED_MAX_SIZE is in Kb + if (getsize(path_to_file) / 1000) >= config("UNPROCESSED_MAX_SIZE", cast=int, default=200 * 1000): + raise HTTPException(413, "Replay file too large") return path_to_file return None diff --git a/api/chalicelib/core/sourcemaps.py b/api/chalicelib/core/sourcemaps.py index 555b5b057..a404f65cb 100644 --- a/api/chalicelib/core/sourcemaps.py +++ b/api/chalicelib/core/sourcemaps.py @@ -47,7 +47,8 @@ def __frame_is_valid(f): def __format_frame(f): f["context"] = [] # no context by default - if "source" in f: f.pop("source") + if "source" in f: + f.pop("source") url = f.pop("fileName") f["absPath"] = url f["filename"] = urlparse(url).path @@ -67,8 +68,13 @@ def format_payload(p, truncate_to_first=False): def url_exists(url): - r = requests.head(url, allow_redirects=False) - return r.status_code == 200 and r.headers.get("Content-Type") != "text/html" + try: + r = requests.head(url, allow_redirects=False) + return r.status_code == 200 and r.headers.get("Content-Type") != "text/html" + except Exception as e: + print(f"!! Issue checking if URL exists: {url}") + print(e) + return False def get_traces_group(project_id, payload): @@ -90,8 +96,8 @@ def get_traces_group(project_id, payload): continue if key not in payloads: - file_exists_in_bucket = s3.exists(config('sourcemaps_bucket'), key) - if not file_exists_in_bucket: + file_exists_in_bucket = len(file_url) > 0 and s3.exists(config('sourcemaps_bucket'), key) + if len(file_url) > 0 and not file_exists_in_bucket: print(f"{u['absPath']} sourcemap (key '{key}') doesn't exist in S3 looking in server") if not file_url.endswith(".map"): file_url += '.map' diff --git a/api/routers/core.py b/api/routers/core.py index 9a0032329..80f2b6296 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -49,6 +49,7 @@ def login(data: schemas.UserLoginSchema = Body(...)): @app.post('/{projectId}/sessions/search', tags=["sessions"]) +@app.post('/{projectId}/sessions/search2', tags=["sessions"]) def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id) @@ -867,7 +868,7 @@ def delete_slack_integration(integrationId: int, context: schemas.CurrentContext return webhook.delete(context.tenant_id, integrationId) -@app.post('/webhooks', tags=["webhooks"]) +@app.put('/webhooks', tags=["webhooks"]) def add_edit_webhook(data: schemas.CreateEditWebhookSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": webhook.add_edit(tenant_id=context.tenant_id, data=data.dict(), replace_none=True)} diff --git a/ee/api/.gitignore b/ee/api/.gitignore index 4860784c3..ee23eb7ff 100644 --- a/ee/api/.gitignore +++ b/ee/api/.gitignore @@ -248,7 +248,7 @@ Pipfile /routers/core.py /routers/crons/core_crons.py /db_changes.sql -/Dockerfile.bundle +/Dockerfile_bundle /entrypoint.bundle.sh /chalicelib/core/heatmaps.py /schemas.py diff --git a/ee/api/clean.sh b/ee/api/clean.sh index 083e72cde..314321b83 100755 --- a/ee/api/clean.sh +++ b/ee/api/clean.sh @@ -70,7 +70,7 @@ rm -rf ./routers/base.py rm -rf ./routers/core.py rm -rf ./routers/crons/core_crons.py rm -rf ./db_changes.sql -rm -rf ./Dockerfile.bundle +rm -rf ./Dockerfile_bundle rm -rf ./entrypoint.bundle.sh rm -rf ./chalicelib/core/heatmaps.py rm -rf ./schemas.py diff --git a/frontend/app/components/Alerts/Notifications/Notifications.tsx b/frontend/app/components/Alerts/Notifications/Notifications.tsx index b5421fd29..05bcea06b 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.tsx +++ b/frontend/app/components/Alerts/Notifications/Notifications.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import stl from './notifications.module.css'; import { connect } from 'react-redux'; -import { Icon, Popup } from 'UI'; +import { Icon, Tooltip } from 'UI'; import { fetchList, setViewed, clearAll } from 'Duck/notifications'; import { setLastRead } from 'Duck/announcements'; import { useModal } from 'App/components/Modal'; @@ -29,7 +29,7 @@ function Notifications(props: Props) { }, []); return useObserver(() => ( - +
showModal(, { right: true })} @@ -39,7 +39,7 @@ function Notifications(props: Props) {
-
+ )); } diff --git a/frontend/app/components/Announcements/Announcements.js b/frontend/app/components/Announcements/Announcements.js index b17e44982..55306e643 100644 --- a/frontend/app/components/Announcements/Announcements.js +++ b/frontend/app/components/Announcements/Announcements.js @@ -2,7 +2,7 @@ import React from 'react'; import stl from './announcements.module.css'; import ListItem from './ListItem'; import { connect } from 'react-redux'; -import { SlideModal, Icon, NoContent, Popup } from 'UI'; +import { SlideModal, Icon, NoContent, Tooltip } from 'UI'; import { fetchList, setLastRead } from 'Duck/announcements'; import withToggle from 'Components/hocs/withToggle'; import { withRouter } from 'react-router-dom'; @@ -45,14 +45,14 @@ class Announcements extends React.Component { return (
- +
{ unReadNotificationsCount }
-
+
-
- +
{onCall && callObject && ( diff --git a/frontend/app/components/BugFinder/SessionList/Tooltip.js b/frontend/app/components/BugFinder/SessionList/Tooltip.js deleted file mode 100644 index a054b0beb..000000000 --- a/frontend/app/components/BugFinder/SessionList/Tooltip.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { Popup } from 'UI'; - -export default class Tooltip extends React.PureComponent { - state = { - open: false, - } - mouseOver = false - onMouseEnter = () => { - this.mouseOver = true; - setTimeout(() => { - if (this.mouseOver) this.setState({ open: true }); - }, 1000) - } - onMouseLeave = () => { - this.mouseOver = false; - this.setState({ - open: false, - }); - } - - render() { - const { trigger, tooltip } = this.props; - const { open } = this.state; - return ( - - - { trigger } - - - ); - } -} \ No newline at end of file diff --git a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js index 400c10435..4dde1a130 100644 --- a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js +++ b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js @@ -1,7 +1,7 @@ import React from 'react' import { connect } from 'react-redux'; import cn from 'classnames'; -import { SideMenuitem, Popup } from 'UI' +import { SideMenuitem, Tooltip } from 'UI' import stl from './sessionMenu.module.css'; import { clearEvents } from 'Duck/filters'; import { issues_types } from 'Types/session/issue' @@ -24,12 +24,11 @@ function SessionsMenu(props) { Sessions
showModal(, { right: true })}> - Configure the percentage of sessions
to be captured, timezone and more.
} + Configure the percentage of sessions
to be captured, timezone and more.} > Settings - +
diff --git a/frontend/app/components/Client/Integrations/IntegrationItem.tsx b/frontend/app/components/Client/Integrations/IntegrationItem.tsx index ec730e7c1..d04ac3b7e 100644 --- a/frontend/app/components/Client/Integrations/IntegrationItem.tsx +++ b/frontend/app/components/Client/Integrations/IntegrationItem.tsx @@ -1,6 +1,6 @@ import React from 'react'; import cn from 'classnames'; -import { Icon, Popup } from 'UI'; +import { Icon, Tooltip } from 'UI'; import stl from './integrationItem.module.css'; import { connect } from 'react-redux'; @@ -17,9 +17,9 @@ const IntegrationItem = (props: Props) => {
props.onClick(e)}> {integrated && (
- + - +
)} {integration.icon.length ? integration : ( @@ -33,9 +33,4 @@ const IntegrationItem = (props: Props) => { ); }; -export default connect((state: any, props: Props) => { - const list = state.getIn([props.integration.slug, 'list']) || []; - return { - // integrated: props.integration.slug === 'issues' ? !!(list.first() && list.first().token) : list.size > 0, - }; -})(IntegrationItem); +export default IntegrationItem; diff --git a/frontend/app/components/Client/Integrations/Integrations.js_ b/frontend/app/components/Client/Integrations/Integrations.js_ deleted file mode 100644 index b4a421980..000000000 --- a/frontend/app/components/Client/Integrations/Integrations.js_ +++ /dev/null @@ -1,633 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import withPageTitle from "HOCs/withPageTitle"; -import { Loader, IconButton, SlideModal } from "UI"; -import { fetchList as fetchListSlack } from "Duck/integrations/slack"; -import { remove as removeIntegrationConfig } from "Duck/integrations/actions"; -import { fetchList, init } from "Duck/integrations/actions"; -import cn from "classnames"; - -import IntegrationItem from "./IntegrationItem"; -import SentryForm from "./SentryForm"; -import GithubForm from "./GithubForm"; -import SlackForm from "./SlackForm"; -import DatadogForm from "./DatadogForm"; -import StackdriverForm from "./StackdriverForm"; -import RollbarForm from "./RollbarForm"; -import NewrelicForm from "./NewrelicForm"; -import BugsnagForm from "./BugsnagForm"; -import CloudwatchForm from "./CloudwatchForm"; -import ElasticsearchForm from "./ElasticsearchForm"; -import SumoLogicForm from "./SumoLogicForm"; -import JiraForm from "./JiraForm"; -import styles from "./integrations.module.css"; -import ReduxDoc from "./ReduxDoc"; -import VueDoc from "./VueDoc"; -import GraphQLDoc from "./GraphQLDoc"; -import NgRxDoc from "./NgRxDoc/NgRxDoc"; -import SlackAddForm from "./SlackAddForm"; -import FetchDoc from "./FetchDoc"; -import MobxDoc from "./MobxDoc"; -import ProfilerDoc from "./ProfilerDoc"; -import AssistDoc from "./AssistDoc"; -import AxiosDoc from "./AxiosDoc/AxiosDoc"; - -const NONE = -1; -const SENTRY = 0; -const DATADOG = 1; -const STACKDRIVER = 2; -const ROLLBAR = 3; -const NEWRELIC = 4; -const BUGSNAG = 5; -const CLOUDWATCH = 6; -const ELASTICSEARCH = 7; -const SUMOLOGIC = 8; -const JIRA = 9; -const GITHUB = 10; -const REDUX = 11; -const VUE = 12; -const GRAPHQL = 13; -const NGRX = 14; -const SLACK = 15; -const FETCH = 16; -const MOBX = 17; -const PROFILER = 18; -const ASSIST = 19; -const AXIOS = 20; - -const TITLE = { - [SENTRY]: "Sentry", - [SLACK]: "Slack", - [DATADOG]: "Datadog", - [STACKDRIVER]: "Stackdriver", - [ROLLBAR]: "Rollbar", - [NEWRELIC]: "New Relic", - [BUGSNAG]: "Bugsnag", - [CLOUDWATCH]: "CloudWatch", - [ELASTICSEARCH]: "Elastic Search", - [SUMOLOGIC]: "Sumo Logic", - [JIRA]: "Jira", - [GITHUB]: "Github", - [REDUX]: "Redux", - [VUE]: "VueX", - [GRAPHQL]: "GraphQL", - [NGRX]: "NgRx", - [FETCH]: "Fetch", - [MOBX]: "MobX", - [PROFILER]: "Profiler", - [ASSIST]: "Assist", - [AXIOS]: "Axios", -}; - -const DOCS = [REDUX, VUE, GRAPHQL, NGRX, FETCH, MOBX, PROFILER, ASSIST]; - -const integrations = [ - "sentry", - "datadog", - "stackdriver", - "rollbar", - "newrelic", - "bugsnag", - "cloudwatch", - "elasticsearch", - "sumologic", - "issues", -]; - -@connect( - (state) => { - const props = {}; - integrations.forEach((name) => { - props[`${name}Integrated`] = - name === "issues" - ? !!( - state.getIn([name, "list"]).first() && - state.getIn([name, "list"]).first().token - ) - : state.getIn([name, "list"]).size > 0; - props.loading = - props.loading || state.getIn([name, "fetchRequest", "loading"]); - }); - const site = state.getIn(["site", "instance"]); - return { - ...props, - issues: state.getIn(["issues", "list"]).first() || {}, - slackChannelListExists: state.getIn(["slack", "list"]).size > 0, - tenantId: state.getIn(["user", "account", "tenantId"]), - jwt: state.get("jwt"), - projectKey: site ? site.projectKey : "", - }; - }, - { - fetchList, - init, - fetchListSlack, - removeIntegrationConfig, - } -) -@withPageTitle("Integrations - OpenReplay Preferences") -export default class Integrations extends React.PureComponent { - state = { - modalContent: NONE, - showDetailContent: false, - }; - - componentWillMount() { - integrations.forEach((name) => this.props.fetchList(name)); - this.props.fetchListSlack(); - } - - onClickIntegrationItem = (e, url) => { - e.preventDefault(); - window.open(url); - }; - - closeModal = () => - this.setState({ modalContent: NONE, showDetailContent: false }); - - onOauthClick = (source) => { - if (source === GITHUB) { - const githubUrl = `https://auth.openreplay.com/oauth/login?provider=github&back_url=${document.location.href}`; - const options = { - method: "GET", - credentials: "include", - headers: new Headers({ - Authorization: "Bearer " + this.props.jwt.toString(), - }), - }; - fetch(githubUrl, options).then((resp) => - resp.text().then((txt) => window.open(txt, "_self")) - ); - } - }; - - renderDetailContent() { - switch (this.state.modalContent) { - case SLACK: - return ( - - this.setState({ showDetailContent: false }) - } - /> - ); - } - } - - renderModalContent() { - const { projectKey } = this.props; - - switch (this.state.modalContent) { - case SENTRY: - return ; - case GITHUB: - return ; - case SLACK: - return ( - - this.setState({ showDetailContent: true }) - } - /> - ); - case DATADOG: - return ; - case STACKDRIVER: - return ; - case ROLLBAR: - return ; - case NEWRELIC: - return ; - case BUGSNAG: - return ; - case CLOUDWATCH: - return ; - case ELASTICSEARCH: - return ; - case SUMOLOGIC: - return ; - case JIRA: - return ; - case REDUX: - return ( - - ); - case VUE: - return ( - - ); - case GRAPHQL: - return ( - - ); - case NGRX: - return ( - - ); - case FETCH: - return ( - - ); - case MOBX: - return ( - - ); - case PROFILER: - return ( - - ); - case ASSIST: - return ( - - ); - case AXIOS: - return ( - - ); - default: - return null; - } - } - - deleteHandler = (name) => { - this.props.removeIntegrationConfig(name, null).then( - function () { - this.props.fetchList(name); - }.bind(this) - ); - }; - - showIntegrationConfig = (type) => { - this.setState({ modalContent: type }); - }; - - render() { - const { - loading, - sentryIntegrated, - stackdriverIntegrated, - datadogIntegrated, - rollbarIntegrated, - newrelicIntegrated, - bugsnagIntegrated, - cloudwatchIntegrated, - elasticsearchIntegrated, - sumologicIntegrated, - hideHeader = false, - plugins = false, - jiraIntegrated, - issuesIntegrated, - tenantId, - slackChannelListExists, - issues, - } = this.props; - const { modalContent, showDetailContent } = this.state; - return ( -
- -
{TITLE[modalContent]}
- {modalContent === SLACK && ( - - this.setState({ - showDetailContent: true, - }) - } - /> - )} -
- } - isDisplayed={modalContent !== NONE} - onClose={this.closeModal} - size={ - DOCS.includes(this.state.modalContent) - ? "middle" - : "small" - } - content={this.renderModalContent()} - detailContent={ - showDetailContent && this.renderDetailContent() - } - /> - - {!hideHeader && ( -
-

- {"Integrations"} -

-

- Power your workflow with your favourite tools. -

-
-
- )} - - {plugins && ( -
-
- Use plugins to better debug your application's - store, monitor queries and track performance issues. -
-
- - this.showIntegrationConfig(REDUX) - } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(VUE)} - // integrated={ sentryIntegrated } - /> - - this.showIntegrationConfig(GRAPHQL) - } - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(NGRX)} - // integrated={ sentryIntegrated } - /> - this.showIntegrationConfig(MOBX)} - // integrated={ sentryIntegrated } - /> - - this.showIntegrationConfig(FETCH) - } - // integrated={ sentryIntegrated } - /> - - this.showIntegrationConfig(PROFILER) - } - // integrated={ sentryIntegrated } - /> - - this.showIntegrationConfig(AXIOS) - } - // integrated={ sentryIntegrated } - /> - - this.showIntegrationConfig(ASSIST) - } - // integrated={ sentryIntegrated } - /> -
-
- )} - - {!plugins && ( - -
-
-
- How are you monitoring errors and crash - reporting? -
-
- {(!issues.token || - issues.provider !== "github") && ( - - this.showIntegrationConfig(JIRA) - } - integrated={issuesIntegrated} - /> - )} - {(!issues.token || - issues.provider !== "jira") && ( - - this.showIntegrationConfig( - GITHUB - ) - } - integrated={issuesIntegrated} - deleteHandler={ - issuesIntegrated - ? () => - this.deleteHandler( - "issues" - ) - : null - } - /> - )} - - - this.showIntegrationConfig(SLACK) - } - integrated={sentryIntegrated} - /> - - this.showIntegrationConfig(SENTRY) - } - integrated={sentryIntegrated} - /> - - - this.showIntegrationConfig(BUGSNAG) - } - integrated={bugsnagIntegrated} - /> - - - this.showIntegrationConfig(ROLLBAR) - } - integrated={rollbarIntegrated} - /> - - - this.showIntegrationConfig( - ELASTICSEARCH - ) - } - integrated={elasticsearchIntegrated} - /> - - - this.showIntegrationConfig(DATADOG) - } - integrated={datadogIntegrated} - /> - - this.showIntegrationConfig( - SUMOLOGIC - ) - } - integrated={sumologicIntegrated} - /> - - this.showIntegrationConfig( - STACKDRIVER - ) - } - integrated={stackdriverIntegrated} - /> - - - this.showIntegrationConfig( - CLOUDWATCH - ) - } - integrated={cloudwatchIntegrated} - /> - - - this.showIntegrationConfig(NEWRELIC) - } - integrated={newrelicIntegrated} - /> -
-
-
-
- )} -
- ); - } -} diff --git a/frontend/app/components/Client/ManageUsers/ManageUsers.js b/frontend/app/components/Client/ManageUsers/ManageUsers.js deleted file mode 100644 index dd703813e..000000000 --- a/frontend/app/components/Client/ManageUsers/ManageUsers.js +++ /dev/null @@ -1,273 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import cn from 'classnames'; -import withPageTitle from 'HOCs/withPageTitle'; -import { - Form, IconButton, SlideModal, Input, Button, Loader, - NoContent, Popup, CopyButton } from 'UI'; -import Select from 'Shared/Select'; -import { init, save, edit, remove as deleteMember, fetchList, generateInviteLink } from 'Duck/member'; -import { fetchList as fetchRoles } from 'Duck/roles'; -import styles from './manageUsers.module.css'; -import UserItem from './UserItem'; -import { confirm } from 'UI'; -import { toast } from 'react-toastify'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; - -const PERMISSION_WARNING = 'You don’t have the permissions to perform this action.'; -const LIMIT_WARNING = 'You have reached users limit.'; - -@connect(state => ({ - account: state.getIn([ 'user', 'account' ]), - members: state.getIn([ 'members', 'list' ]).filter(u => u.id), - member: state.getIn([ 'members', 'instance' ]), - errors: state.getIn([ 'members', 'saveRequest', 'errors' ]), - loading: state.getIn([ 'members', 'loading' ]), - saving: state.getIn([ 'members', 'saveRequest', 'loading' ]), - roles: state.getIn(['roles', 'list']).filter(r => !r.protected).map(r => ({ label: r.name, value: r.roleId })).toJS(), - isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee', -}), { - init, - save, - edit, - deleteMember, - fetchList, - generateInviteLink, - fetchRoles -}) -@withPageTitle('Team - OpenReplay Preferences') -class ManageUsers extends React.PureComponent { - state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false } - - // writeOption = (e, { name, value }) => this.props.edit({ [ name ]: value }); - onChange = ({ name, value }) => this.props.edit({ [ name ]: value.value }); - onChangeCheckbox = ({ target: { checked, name } }) => this.props.edit({ [ name ]: checked }); - setFocus = () => this.focusElement && this.focusElement.focus(); - closeModal = () => this.setState({ showModal: false }); - componentWillMount = () => { - this.props.fetchList(); - if (this.props.isEnterprise) { - this.props.fetchRoles(); - } - } - - adminLabel = (user) => { - if (user.superAdmin) return null; - return user.admin ? 'Admin' : ''; - }; - - editHandler = user => { - this.init(user) - } - - deleteHandler = async (user) => { - if (await confirm({ - header: 'Users', - confirmation: `Are you sure you want to remove this user?` - })) { - this.props.deleteMember(user.id).then(() => { - const { remaining } = this.state; - if (remaining <= 0) return; - this.setState({ remaining: remaining - 1 }) - }); - } - } - - save = (e) => { - e.preventDefault(); - this.props.save(this.props.member) - .then(() => { - const { errors } = this.props; - if (errors && errors.size > 0) { - errors.forEach(e => { - toast.error(e); - }) - } - this.setState({ invited: true }) - // this.closeModal() - }); - } - - formContent = () => { - const { member, account, isEnterprise, roles } = this.props; - - return ( -
-
-
- - { this.focusElement = ref; } } - name="name" - value={ member.name } - onChange={ this.onChange } - className={ styles.input } - id="name-field" - /> -
- - - - - - { !account.smtp && -
- SMTP is not configured (see here how to set it up). You can still add new users, but you’d have to manually copy then send them the invitation link. -
- } - - -
{ 'Can manage Projects and team members.' }
-
- - { isEnterprise && ( - - -
- + of the sessions