From 285bf7d2d282d38900072730f8e0d748aaf6c9ae Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 21 Jul 2022 23:32:03 +0200 Subject: [PATCH] Added welcome form and redirect, as well as docker autobuilds --- .github/workflows/dockerbuild.yaml | 23 +- .github/workflows/snyk-container-analysis.yml | 1 + backend/app_sdk/app_base.py | 14 +- frontend/src/App.jsx | 17 + frontend/src/components/WelcomeForm.jsx | 634 ++++++++++++++++++ frontend/src/views/Welcome.jsx | 91 +++ 6 files changed, 773 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/WelcomeForm.jsx create mode 100644 frontend/src/views/Welcome.jsx diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index 7dfc5e1a..e2195a71 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -52,9 +52,8 @@ jobs: # registry: ghcr.io # username: ${{ github.repository_owner }} # password: ${{ secrets.CR_PAT }} - - - name: Build and push - id: docker_build + - name: Dockerhub Build and push + id: docker_build_dockerhub uses: docker/build-push-action@v2 env: BUILDX_NO_DEFAULT_LOAD: true @@ -65,6 +64,20 @@ jobs: #,linux/386 - no node image I guess? push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ matrix.version }} - - - name: Image digest + - name: Ghcr Build and push + id: docker_build + uses: docker/build-push-action@v2 + env: + BUILDX_NO_DEFAULT_LOAD: true + with: + registry: ghcr.io + username: "frikky" + password: ${{ secrets.GHCR_PW }} + context: ${{ matrix.path }}/ + file: ${{ matrix.path }}/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ matrix.version }} + + - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/snyk-container-analysis.yml b/.github/workflows/snyk-container-analysis.yml index 11885d2b..f9a406b2 100644 --- a/.github/workflows/snyk-container-analysis.yml +++ b/.github/workflows/snyk-container-analysis.yml @@ -43,6 +43,7 @@ jobs: with: image: your/image-to-test args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning uses: github/codeql-action/upload-sarif@v1 with: diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index db34e0e2..4005bde9 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -3391,12 +3391,22 @@ class AppBase: except TypeError as e: self.logger.info("[ERROR] TypeError issue: %s" % e) self.action_result["status"] = "FAILURE" - self.action_result["result"] = "TypeError: %s" % str(e) + self.action_result["result"] = json.dumps({ + "success": False, + "reason": f"Typeerror. Most likely due to a list that should've been a string. See details for more info.", + "details": e, + }) + #self.action_result["result"] = "TypeError: %s" % str(e) else: self.logger.info("[DEBUG] Function %s doesn't exist?" % action["name"]) self.logger.error(f"[ERROR] App {self.__class__.__name__}.{action['name']} is not callable") self.action_result["status"] = "FAILURE" - self.action_result["result"] = "Function %s is not callable." % actionname + #self.action_result["result"] = "Function %s is not callable." % actionname + + self.action_result["result"] = json.dumps({ + "success": False, + "reason": f"Function %s doesn't exist." % actionname, + }) # https://ptb.discord.com/channels/747075026288902237/882017498550112286/882043773138382890 except (requests.exceptions.RequestException, TimeoutError) as e: diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9858567c..ab77202f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -15,6 +15,7 @@ import theme from "./theme"; import Apps from "./views/Apps"; import AppCreator from "./views/AppCreator"; +import Welcome from "./views/Welcome.jsx"; import Dashboard from "./views/Dashboard.jsx"; import DashboardView from "./views/DashboardViews.jsx"; import AdminSetup from "./views/AdminSetup"; @@ -671,6 +672,22 @@ const App = (message, props) => { /> } /> + + } + /> { + const { userdata, globalUrl, discoveryWrapper, setDiscoveryWrapper } = props + + const [activeStep, setActiveStep] = React.useState(0); + const [skipped, setSkipped] = React.useState(new Set()); + const [discoveryData, setDiscoveryData] = React.useState({}) + const [name, setName] = React.useState("") + const [orgName, setOrgName] = React.useState("") + const [role, setRole] = React.useState("") + const [orgType, setOrgType] = React.useState("") + const [finishedApps, setFinishedApps] = React.useState([]) + + let navigate = useNavigate(); + + const onNodeSelect = (label) => { + if (setDiscoveryWrapper !== undefined) { + setDiscoveryWrapper( + {"id": label} + ) + } + + setSelectionOpen(true) + setDefaultSearch(label) + } + + useEffect(() => { + if (userdata.id === undefined) { + return + } + + if (userdata.name !== undefined && userdata.name !== null && userdata.name.length > 0) { + setName(userdata.name) + } + + if (userdata.active_org !== undefined && userdata.active_org.name !== undefined && userdata.active_org.name !== null && userdata.active_org.name.length > 0) { + setOrgName(userdata.active_org.name) + } + }, [userdata]) + + useEffect(() => { + if (discoveryWrapper === undefined || discoveryWrapper.id === undefined) { + setDefaultSearch("") + var newfinishedApps = finishedApps + newfinishedApps.push(defaultSearch) + setFinishedApps(finishedApps) + } + }, [discoveryWrapper]) + + useEffect(() => { + + if ( + window.location.search !== undefined && + window.location.search !== null + ) { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundTab = params["tab"]; + if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) { + setActiveStep(foundTab-1) + } else { + navigate(`/welcome?tab=1`) + } + } + + }, []) + + const steps = getSteps(); + + const isStepOptional = step => { + return step === 1 + } + + const isStepSkipped = step => { + return skipped.has(step) + } + + const sendUserUpdate = (name, role, userId) => { + const data = { + "tutorial": "welcome", + "firstname": name, + "role": role, + "user_id": userId, + } + + const url = `${globalUrl}/api/v1/updateuser` + fetch(url, { + mode: "cors", + method: "PUT", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + console.log("Update user success") + //alert.error("Failed updating org: ", responseJson.reason); + } else { + console.log("Update success!") + //alert.success("Successfully edited org!"); + } + }) + ) + .catch((error) => { + console.log("Update err: ", error.toString()) + //alert.error("Err: " + error.toString()); + }); + } + + const sendOrgUpdate = (orgname, company_type, orgId) => { + var data = { + org_id: orgId, + }; + + if (orgname.length > 0) { + data.name = orgname + } + + if (company_type.length > 0) { + data.company_type = company_type + } + + const url = globalUrl + `/api/v1/orgs/${orgId}`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + console.log("Update of org failed") + //alert.error("Failed updating org: ", responseJson.reason); + } else { + console.log("Update success!") + //alert.success("Successfully edited org!"); + } + }) + ) + .catch((error) => { + console.log("Update err: ", error.toString()) + //alert.error("Err: " + error.toString()); + }); + } + + var workflowDelay = -50 + const NewHits = ({ hits }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + var counted = 0 + + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + marginTop: 5, + } + + return ( + + {hits.map((data, index) => { + workflowDelay += 50 + + if (index > 3) { + return null + } + + return ( + + + + + + ) + })} + + ) + } + + const handleNext = () => { + setDefaultSearch("") + + if (activeStep === 0) { + console.log("Should send basic information about org (fetch)") + navigate(`/welcome?tab=2`) + + if (userdata.active_org !== undefined && userdata.active_org.id !== undefined && userdata.active_org.id !== null && userdata.active_org.id.length > 0) { + sendOrgUpdate(orgName, orgType, userdata.active_org.id) + } + + if (userdata.id !== undefined && userdata.id !== null && userdata.id.length > 0) { + sendUserUpdate(name, role, userdata.id) + } + + } else if (activeStep === 1) { + console.log("Should send secondary info about apps and other things") + setDiscoveryWrapper({}) + + navigate(`/welcome?tab=3`) + } else if (activeStep === 2) { + console.log("Should send third page with workflows activated and the like") + } + + let newSkipped = skipped; + if (isStepSkipped(activeStep)) { + newSkipped = new Set(newSkipped.values()); + newSkipped.delete(activeStep); + } + + setActiveStep(prevActiveStep => prevActiveStep + 1); + setSkipped(newSkipped); + }; + const handleBack = () => { + setActiveStep(prevActiveStep => prevActiveStep - 1); + }; + const handleSkip = () => { + if (!isStepOptional(activeStep)) { + throw new Error("You can't skip a step that isn't optional."); + } + setActiveStep(prevActiveStep => prevActiveStep + 1); + setSkipped(prevSkipped => { + const newSkipped = new Set(prevSkipped.values()); + newSkipped.add(activeStep); + return newSkipped; + }); + }; + const handleReset = () => { + setActiveStep(0); + }; + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const [selectionOpen, setSelectionOpen] = React.useState(false) + const [newSelectedApp, setNewSelectedApp] = React.useState({}) + const [defaultSearch, setDefaultSearch] = React.useState("") + + useEffect(() => { + console.log("Selected app changed (effect)") + }, [newSelectedApp]) + + function getSteps() { + return [ + "Help us get to know you", + "Discover integrations", + "Personalize Usecases", + ] + } + + //const buttonWidth = 145 + const buttonWidth = 450 + const buttonMargin = 10 + const sizing = 435 + const buttonStyle = { + flex: 1, + width: "100%", + padding: 25, + margin: buttonMargin, + fontSize: 18, + } + + const newButtonStyle = { + padding: 25, + flex: 1, + margin: buttonMargin, + minWidth: buttonWidth, + maxWidth: buttonWidth, + } + + const getStepContent = (step) => { + switch (step) { + case 0: + return ( + + + We need some more information in order to understand how we best can help you find relevant Usecases in Shuffle. Giving us this info is optional, but encouraged. + + + { + setName(e.target.value) + }} + /> + + + { + setOrgName(e.target.value) + }} + /> + + + + Your Role + + + + + + Company Type + + + + + ) + case 1: + return ( +
+ + Find the most relevant apps, then we'll help you find the most relevant workflows + + {/* + + + What is your development experience? + + + + */} + + {/*Find your integrations!*/} +
+ + +
+
+ + +
+ {/* + What do you want to automate first ? + + { onNodeSelect("Email") }} />} + label="Email" + labelPlacement="Email" + /> + { onNodeSelect("SIEM") }} />} + label="SIEM" + labelPlacement="SIEM" + /> + { onNodeSelect("EDR") }} />} + label="EDR" + labelPlacement="EDR" + /> + + */} +
+ {/* + + + What tools do you use? + + + + */} +
+ ) + case 2: + return ( +
+ + + What processes are you interested in? This will help us suggest relevant Workflows. + + + + + + + {/* + + */} + + + {/* + + + + */} +
+ {selectionOpen === true ? + + : null} +
+
+ ) + default: + return "unknown step" + } + } + + return ( +
+ + {steps.map((label, index) => { + const stepProps = {} + const labelProps = {} + //if (isStepOptional(index)) { + // labelProps.optional = "optional" + //} + + if (isStepSkipped(index)) { + stepProps.completed = false; + } + + return ( + + {label} + + ) + })} + + {/*selectionOpen ? + + : null*/} +
+ {isCloud ? null : + + This data will be used within the product and NOT be shared unless cloud synchronization is configured. + + } + {activeStep === steps.length ? ( +
+ You Will be Redirected to getting Start Page Wait for 5-sec. + + + +
+ ) : ( +
+ {getStepContent(activeStep)} +
+ + {/*isStepOptional(activeStep) && ( + + )*/} + +
+
+ )} +
+
+ ); +} + +export default WelcomeForm diff --git a/frontend/src/views/Welcome.jsx b/frontend/src/views/Welcome.jsx new file mode 100644 index 00000000..c32c4934 --- /dev/null +++ b/frontend/src/views/Welcome.jsx @@ -0,0 +1,91 @@ +import React, { useState, useEffect } from 'react'; +import WelcomeForm from "../components/WelcomeForm.jsx"; +import DetectionFramework from "../components/DetectionFramework.jsx"; +import { + Grid, + Container, + Fade, +} from '@mui/material'; +import theme from '../theme'; +import { useNavigate, Link } from "react-router-dom"; + +// Should be different if logged in :| +const Welcome = (props) => { + const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded } = props; + const [inputUsecase, setInputUsecase] = useState({}); + const [frameworkData, setFrameworkData] = useState(undefined); + const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined); + + let navigate = useNavigate(); + + const getFramework = () => { + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + setFrameworkData({}) + if (responseJson.reason !== undefined) { + //alert.error("Failed loading: " + responseJson.reason) + } else { + //alert.error("Failed to load framework for your org.") + } + } else { + setFrameworkData(responseJson) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + }) + } + + useEffect(() => { + getFramework() + }, []); + + return ( + + +
+ +
+
+ {frameworkData === undefined || window.location.href.includes("tab=3") ? null : + + + + } +
+ ) +} + +export default Welcome;