diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index f7452ab4..298be08f 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -3515,7 +3515,7 @@ class AppBase: timeout = 30 # Check if current app is Shuffle Tools, then set to 55 due to certain actions being slow (ioc parser..) - #uu In general, this should be disabled for onprem + # In general, this should be disabled for onprem if self.action["app_name"].lower() == "shuffle tools": timeout = 55 diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index bd4b4760..a69a127b 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -18,8 +18,8 @@ require ( github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.4.66 - golang.org/x/crypto v0.14.0 + github.com/shuffle/shuffle-shared v0.4.79 + golang.org/x/crypto v0.9.0 google.golang.org/api v0.125.0 google.golang.org/grpc v1.55.0 gopkg.in/src-d/go-git.v4 v4.13.1 diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index aa5d5e97..32fb67ef 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1049,15 +1049,25 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request, 10) if err != nil { + err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) + if err != nil { + log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err) + } + if strings.Contains(fmt.Sprintf("%s", err), "User Input") { // Special for user input callbacks return workflowExecution, fmt.Sprintf("%s", err), nil } else { - log.Printf("[WARNING] Failed in prepareExecution: %s", err) + log.Printf("[ERROR] Failed in prepareExecution: %s", err) return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err } } + err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) + if err != nil { + log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err) + } + err = imageCheckBuilder(execInfo.ImageNames) if err != nil { log.Printf("[ERROR] Failed building the required images from %#v: %s", execInfo.ImageNames, err) @@ -1643,7 +1653,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request return shuffle.WorkflowExecution{}, "Failed building missing Docker images", err } - //Org []Org `json:"org,omitempty" datastore:"org"` err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) if err != nil { log.Printf("[WARNING] Error saving workflow execution for updates %s", err) diff --git a/frontend/package.json b/frontend/package.json index 1ab7f422..0cb2e764 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,6 +60,7 @@ "react-driftjs": "^1.2.2", "react-dropzone": "^14.2.3", "react-ga4": "^2.0.0", + "react-hotkeys": "^2.0.0", "react-instantsearch-dom": "^6.28.0", "react-json-pretty": "^2.2.0", "react-json-view": "^1.21.3", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4d96250a..4e4fc9c9 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,7 @@ import Workflows from "./views/Workflows"; import GettingStarted from "./views/GettingStarted"; import AngularWorkflow from "./views/AngularWorkflow.jsx"; +//import Header from "./components/NewHeader.jsx"; import Header from "./components/Header.jsx"; import theme from "./theme"; import Apps from "./views/Apps"; @@ -38,6 +39,7 @@ import ScrollToTop from "./components/ScrollToTop"; import AlertTemplate from "./components/AlertTemplate"; import { useAlert, positions, Provider } from "react-alert"; import { isMobile } from "react-device-detect"; +import RuntimeDebugger from "./components/RuntimeDebugger.jsx" import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; @@ -405,6 +407,8 @@ const App = (message, props) => { /> } /> + } /> + } /> { - const { userdata, globalUrl, appFramework, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, } = props + const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, } = props const ref = useRef() let navigate = useNavigate(); const [isHover, setIsHover] = useState(false); const [localSearchOpen, setLocalSearchOpen] = useState(false) - const [newSelectedApp, setNewSelectedApp] = useState(undefined) + const [newSelectedApp, setNewSelectedApp] = useState(undefined) - useEffect(() => { - console.log("AppSearchButtons: newSelectedApp: " + JSON.stringify(newSelectedApp)) + useEffect(() => { + console.log("AppSearchButtons: newSelectedApp: " + JSON.stringify(newSelectedApp)) - if (newSelectedApp !== undefined && setMissing != undefined) { - console.log("AppSearchButtons: setMissing is defined!") + if (newSelectedApp !== undefined && setMissing != undefined) { + console.log("AppSearchButtons: setMissing is defined!") - const submitAppFramework = { - "description": newSelectedApp.description, - "id": newSelectedApp.objectID, - "name": newSelectedApp.name, - "type": appType, - "large_image": newSelectedApp.image_url, - } + const submitAppFramework = { + "description": newSelectedApp.description, + "id": newSelectedApp.objectID, + "name": newSelectedApp.name, + "type": appType, + "large_image": newSelectedApp.image_url, + } - setFrameworkItem(submitAppFramework) - setMissing(newSelectedApp) - } - }, [newSelectedApp]) + setFrameworkItem(submitAppFramework) + setMissing(newSelectedApp) + } + }, [newSelectedApp]) - if (appType === undefined) { - console.log("Apptype is required in AppSearchButtons") - return null; - } + if (appType === undefined) { + console.log("Apptype is required in AppSearchButtons") + return null; + } const buttonWidth = 450; const buttonMargin = 10; const handleMouseEnter = () => { setIsHover(true); - }; - - const handleMouseLeave = () => { + }; + + const handleMouseLeave = () => { setIsHover(false); - }; + }; - const foundApp = findSpecificApp(appFramework, appType) - if (foundApp === undefined || foundApp === null) { - console.log("AppSearchButtons: App not found in appFramework: " + appType) - return null - } + const foundApp = findSpecificApp(appFramework, appType) + if (foundApp === undefined || foundApp === null) { + console.log("AppSearchButtons: App not found in appFramework: " + appType) + return null + } - const setFrameworkItem = (data) => { + const setFrameworkItem = (data) => { fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { method: "POST", @@ -99,187 +99,207 @@ const AppSearchButtons = (props) => { body: JSON.stringify(data), credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for framework!"); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } - if (checkLogin !== undefined) { - checkLogin() - } + if (checkLogin !== undefined) { + checkLogin() + } - return response; - }) - .then((responseJson) => { - if (responseJson.success === false) { - if (responseJson.reason !== undefined) { - toast("Failed updating: " + responseJson.reason) - } else { - toast("Failed to update framework for your org.") + return response; + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Failed updating: " + responseJson.reason) + } else { + toast("Failed to update framework for your org.") - } - } - //setFrameworkLoaded(true) - //setFrameworkData(responseJson) - }) - .catch((error) => { - if (checkLogin !== undefined) { - checkLogin() - } + } + } + //setFrameworkLoaded(true) + //setFrameworkData(responseJson) + }) + .catch((error) => { + if (checkLogin !== undefined) { + checkLogin() + } - toast(error.toString()); - //setFrameworkLoaded(true) - }) + toast(error.toString()); + //setFrameworkLoaded(true) + }) } + const icon = foundApp.large_image + console.log("index:", moreButton) + console.log("totalApps:", totalApps) - const icon = foundApp.large_image let xsValue = 12; - if (index !== undefined && index !== 0) { + if (index === totalApps - 1 || index === totalApps - 2 || index === totalApps - 3 || index === totalApps - 4) { xsValue = 6; - } - - if (index === totalApps - 4 && totalApps % 2 === 1) { + } + if (index === totalApps - 5) { xsValue = 12; } - - if (index === totalApps - 1 || index === totalApps - 2 || index === totalApps - 3) { - xsValue = 4; - } + if (moreButton) { + switch (index) { + case totalApps - 1: + case totalApps - 2: + case totalApps - 3: + xsValue = 4; + break; + case totalApps - 4: + case totalApps - 5: + case totalApps - 6: + case totalApps - 7: + xsValue = 6; + break; + case totalApps - 8: + xsValue = 12; + break; + default: + // Handle other cases if needed + } + } + return ( - - {localSearchOpen ? ( -
-
-
- {discoveryData} -
-
- - { - setLocalSearchOpen(false) - }} - > - - - - - { - e.preventDefault(); - setLocalSearchOpen(false) - setDefaultSearch("") - const submitDeletedApp = { - "description": "", - "id": "remove", - "name": "", - "type": discoveryData - } - setFrameworkItem(submitDeletedApp) - setNewSelectedApp({}) - setTimeout(() => { - setDiscoveryData({}) - setFrameworkItem(submitDeletedApp) - //setNewSelectedApp({}) - }, 1000) - //setAppName(discoveryData.cases.name) - }} - > - - - -
-
-
- -
- ) : null} -
- + ) : null} + +
{}} + > + {image} +
+
+
+ + ); + }; + + const notificationMenu = ( + + { + setAnchorEl(event.currentTarget); + }} + > + + + + + { + handleClose(); + }} + > + +
+ + Your Notifications ({notifications.length}) + + {notifications.length > 1 ? ( + + ) : null} +
+ + Notifications are made by Shuffle to help you discover issues or + improvements. + +
+ {notifications.map((data, index) => { + return ; + })} +
+
+ ); + + const handleClickChangeOrg = (orgId) => { + // Don't really care about the logout + //name: org.name, + //orgId = "asd" + const data = { + org_id: orgId, + }; + + localStorage.setItem("globalUrl", ""); + localStorage.setItem("getting_started_sidebar", "open"); + + fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { + mode: "cors", + credentials: "include", + crossDomain: true, + method: "POST", + body: JSON.stringify(data), + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + if ( + responseJson.region_url !== undefined && + responseJson.region_url !== null && + responseJson.region_url.length > 0 + ) { + console.log("Region Change: ", responseJson.region_url); + localStorage.setItem("globalUrl", responseJson.region_url); + //globalUrl = responseJson.region_url + } + + setTimeout(() => { + window.location.reload(); + }, 2000); + toast("Successfully changed active organization - refreshing!"); + } else { + toast("Failed changing org: ", responseJson.reason); + } + }) + .catch((error) => { + console.log("error changing: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; + + const supportMenu = ( + +
+ + {}} + > + Discord Community Join + + + + + ); + + // Should be based on some path + const parsedAvatar = + userdata.avatar !== undefined && + userdata.avatar !== null && + userdata.avatar.length > 0 + ? userdata.avatar + : ""; + + const avatarMenu = ( + + { + setAnchorElAvatar(event.currentTarget); + }} + > + + + { + handleClose(); + }} + > + + { + handleClose(); + }} + > + Admin + + + + + { + handleClose(); + }} + > + About + + + {/* + + { + handleClose(); + }} + > + Get Started + + + */} + + { + handleClose(); + }} + > + Use Cases + + + + + { + handleClose(); + }} + > + Creator page + + + + { + handleClose(); + }} + > + Settings + + + + { + handleClickLogout(); + event.preventDefault(); + handleClose(); + }} + > +  Logout + + + + ); + + const listItemStyle = { + textAlign: "center", + marginTop: "auto", + marginBottom: "auto", + marginRight: 10, + }; + + // Handle top bar or something + const defaultTop = isCloud ? 0 : 7; + const loginTextBrowser = !isLoggedIn ? ( +
+ + + + { + if (isCloud) { + ReactGA.event({ + category: "header", + action: "home_click", + label: "", + }); + } + }} + > + shuffle logo + + + + + + + + + + + {isCloud ? ( + + + + + + ) : null} + + + + + + + +
+ + + + + + + + + + + +
+ ) : ( +
+
+
+ + + +
+ + + logo + + +
+ + + +
+ {/* + + */} + + Workflows + +
+ +
+ + +
+ {/* + + */} + + Apps + +
+ +
+ {/* + + +
Dashboard
+ +
+ */} + + +
+ {/* + + */} + + Docs + +
+ +
+
+
+
+
+ +
+
+ + + {avatarMenu} + {notificationMenu} + {/*supportMenu*/} + {logoCheck} + + + {userdata === undefined || + userdata.orgs === undefined || + userdata.orgs === null || + userdata.orgs.length <= 1 ? null : ( + + + + )} + + {/* Show on cloud, if not suborg and if not customer/pov/internal */} + {isCloud && + (userdata.org_status === undefined || + userdata.org_status === null || + userdata.org_status.length === 0) ? ( + + + + + + ) : null} + + {userdata === undefined || + userdata.app_execution_limit === undefined || + userdata.app_execution_usage === undefined || + userdata.app_execution_usage < 1000 ? null : ( + +
= + 0.9 + ? "#f86a3e" + : null, + }} + onClick={() => { + console.log( + userdata.appe_execution_usage / + userdata.app_execution_limit + ); + if (window.drift !== undefined) { + window.drift.api.startInteraction({ + interactionId: 326905, + }); + navigate("/pricing"); + } else { + console.log( + "Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", + window.drift + ); + } + }} + > + + + {( + (userdata.app_execution_usage / + userdata.app_execution_limit) * + 100 + ).toFixed(0)} + % + + + +
+
+ )} +
+
+
+
+ ); + + const loginTextMobile = !isLoggedIn ? ( +
+ + + +
+ + + logo + + +
+ +
+ + +
+ About +
+ +
+ + + + + + + + + + +
+
+ ) : ( +
+
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + +
+
+
+ + +
+ Logout +
+
+ {logoCheck} + +
+
+
+ ); + + // + return !isMobile ? + isLoggedIn ? +
+ + {loginTextBrowser} + +
+ : + + + {loginTextBrowser} + + + : + {loginTextMobile} +}; + +export default Header; diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 204af1c6..3e834341 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -93,10 +93,10 @@ const AuthenticationOauth2 = (props) => { appAuthentication, setSelectedAction, setNewAppAuth, - isCloud, - autoAuth, - authButtonOnly, - isLoggedIn, + isCloud, + autoAuth, + authButtonOnly, + isLoggedIn, } = props; let navigate = useNavigate(); @@ -125,7 +125,7 @@ const AuthenticationOauth2 = (props) => { const allscopes = authenticationType.scope !== undefined ? authenticationType.scope : []; - const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length === 1 ? [allscopes[0]] : []) + const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length > 0 && allscopes.length <= 3 ? [allscopes[0]] : []) const [manuallyConfigure, setManuallyConfigure] = React.useState( defaultConfigSet ? false : true ); @@ -297,15 +297,63 @@ const AuthenticationOauth2 = (props) => { const handleOauth2Request = (client_id, client_secret, oauth_url, scopes, admin_consent, prompt) => { - setButtonClicked(true); - //console.log("SCOPES: ", scopes); + + if ((authenticationType.redirect_uri === undefined || authenticationType.redirect_uri === null || authenticationType.redirect_uri.length === 0) && (authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0)) { + console.log("No redirect URI found, and token URI found. Assuming client credentials flow and saving directly in the database") + + // Find app.configuration=true fields in the app.paramters + var parsedFields = [{ + "key": "client_id", + "value": client_id, + }, + { + "key": "client_secret", + "value": client_secret, + }, + { + "key": "scope", + "value": scopes.join(","), + }, + { + "key": "token_uri", + "value": authenticationType.token_uri, + }] + + const appAuthData = { + "label": "OAuth2 for " + selectedApp.name, + "app": { + "id": selectedApp.id, + "name": selectedApp.name, + "version": selectedApp.version, + "large_image": selectedApp.large_image, + }, + "fields": parsedFields, + "type": "oauth2-app", + "reference_workflow": workflowId, + } + + setNewAppAuth(appAuthData) + // Wait 1 second, then get app auth with update + // + if (getAppAuthentication !== undefined) { + setTimeout(() => { + getAppAuthentication(true, true, true); + }, 1000) + } + + return + } + + + setButtonClicked(true); + //console.log("SCOPES: ", scopes); client_id = client_id.trim() client_secret = client_secret.trim() oauth_url = oauth_url.trim() - var resources = ""; - if (scopes !== undefined && (scopes !== null) & (scopes.length > 0)) { + var resources = ""; + if (scopes !== undefined && (scopes !== null) & (scopes.length > 0)) { console.log("IN scope 1") if (offlineAccess === true && !scopes.includes("offline_access")) { @@ -324,26 +372,26 @@ const AuthenticationOauth2 = (props) => { //console.log("AUTH: ", authenticationType) //console.log("SCOPES2: ", resources) const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`; - const workflowId = workflow !== undefined ? workflow.id : ""; + const workflowId = workflow !== undefined ? workflow.id : ""; var state = `workflow_id%3D${workflowId}%26reference_action_id%3d${selectedAction.app_id}%26app_name%3d${selectedAction.app_name}%26app_id%3d${selectedAction.app_id}%26app_version%3d${selectedAction.app_version}%26authentication_url%3d${authentication_url}%26scope%3d${resources}%26client_id%3d${client_id}%26client_secret%3d${client_secret}`; - // This is to make sure authorization can be handled WITHOUT being logged in, - // kind of making it act like an api key - // https://shuffler.io/authorization -> 3rd party integration auth - const urlParams = new URLSearchParams(window.location.search); - const userAuth = urlParams.get("authorization"); - if (userAuth !== undefined && userAuth !== null && userAuth.length > 0) { - console.log("Adding authorization from user side") - state += `%26authorization%3d${userAuth}`; - } + // This is to make sure authorization can be handled WITHOUT being logged in, + // kind of making it act like an api key + // https://shuffler.io/authorization -> 3rd party integration auth + const urlParams = new URLSearchParams(window.location.search); + const userAuth = urlParams.get("authorization"); + if (userAuth !== undefined && userAuth !== null && userAuth.length > 0) { + console.log("Adding authorization from user side") + state += `%26authorization%3d${userAuth}`; + } - // Check for org_id - const orgId = urlParams.get("org_id"); - if (orgId !== undefined && orgId !== null && orgId.length > 0) { - console.log("Adding org_id from user side") - state += `%26org_id%3d${orgId}`; - } + // Check for org_id + const orgId = urlParams.get("org_id"); + if (orgId !== undefined && orgId !== null && orgId.length > 0) { + console.log("Adding org_id from user side") + state += `%26org_id%3d${orgId}`; + } if (oauth_url !== undefined && oauth_url !== null && oauth_url.length > 0) { state += `%26oauth_url%3d${oauth_url}`; @@ -363,7 +411,7 @@ const AuthenticationOauth2 = (props) => { // No prompt forcing //var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`; - var defaultPrompt = "login" + var defaultPrompt = "login" if (prompt !== undefined && prompt !== null && prompt.length > 0) { defaultPrompt = prompt } @@ -404,9 +452,9 @@ const AuthenticationOauth2 = (props) => { //alert('"Secure Payment" window closed!'); // - if (getAppAuthentication !== undefined) { - getAppAuthentication(true, true, true); - } + if (getAppAuthentication !== undefined) { + getAppAuthentication(true, true, true); + } } else { console.log("Not closed") } @@ -798,6 +846,7 @@ const AuthenticationOauth2 = (props) => { }} fullWidth color="primary" + label={"Client ID"} placeholder={"Client ID"} onChange={(event) => { setClientId(event.target.value); @@ -816,20 +865,22 @@ const AuthenticationOauth2 = (props) => { }} fullWidth color="primary" + label={"Client Secret"} placeholder={"Client Secret"} onChange={(event) => { setClientSecret(event.target.value); //authenticationOption.label = event.target.value }} /> + {allscopes.length === 0 ? null : "Scopes (access rights)"} {allscopes.length === 0 ? null : (
- Scopes - - - { - setOfflineAccess(!offlineAccess) - }}/> - - + + {((authenticationType.redirect_uri === undefined || authenticationType.redirect_uri === null || authenticationType.redirect_uri.length === 0) && (authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0)) ? null : + + + { + setOfflineAccess(!offlineAccess) + }}/> + + + }
)} @@ -882,19 +936,14 @@ const AuthenticationOauth2 = (props) => { variant="contained" fullWidth onClick={() => { - handleOauth2Request( - clientId, - clientSecret, - oauthUrl, - selectedScopes - ); + handleOauth2Request(clientId, clientSecret, oauthUrl, selectedScopes); }} color="primary" > {buttonClicked ? ( ) : ( - "Manually Authenticate" + "Authenticate" )} diff --git a/frontend/src/components/SearchData.jsx b/frontend/src/components/SearchData.jsx new file mode 100644 index 00000000..cc9c1551 --- /dev/null +++ b/frontend/src/components/SearchData.jsx @@ -0,0 +1,800 @@ +import React, { useState, useEffect, useRef } from 'react'; + +import theme from '../theme.jsx'; +import { useNavigate, Link, useParams } from "react-router-dom"; + +import { + Chip, + IconButton, + TextField, + InputAdornment, + List, + Card, + ListItem, + ListItemAvatar, + ListItemText, + Avatar, + Grid, + Typography, + Tooltip, + Divider, + Button, +} from '@mui/material'; +import ArticleIcon from '@mui/icons-material/Article'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import ManageSearchIcon from '@mui/icons-material/ManageSearch'; +import { + AvatarGroup, +} from "@mui/material" + +import { Search as SearchIcon, Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material' + +import algoliasearch from 'algoliasearch/lite'; +import aa from 'search-insights' +import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; +//import { InstantSearch, SearchBox, Hits, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; + +// https://www.algolia.com/doc/api-reference/widgets/search-box/react/ +const chipStyle = { + backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", +} + +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const SearchData = props => { + const { serverside, userdata, setModalOpen, modalOpen } = props + + let navigate = useNavigate(); + const borderRadius = 3 + const node = useRef() + const [searchOpen, setSearchOpen] = useState(true) + const [value, setValue] = useState(""); + const [userTyped, setUserTyped] = useState(false) + + if (serverside === true) { + return null + } + + //if (window !== undefined && window.location !== undefined && window.location.pathname === "/search") { + // return null + //} + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + + //if (window.location.pathname !== oldPath) { + // setSearchOpen(false) + // setOldPath(window.location.pathname) + //} + + //if (window.location.pathname === "/search") { + // setModalOpen(true) + //} + // if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) { + // setModalOpen(false) + // } + + //useEffect(() => { + // if (searchOpen) { + // var tarfield = document.getElementById("shuffle_search_field") + // tarfield.focus() + // } + //}, searchOpen) + + const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => { + const keyPressHandler = (e) => { + // e.preventDefault(); + if (e.which === 13) { + // alert("You pressed enter!"); + navigate("/search?q=" + currentRefinement, { state: value, replace: true }); + setSearchOpen(false) + setModalOpen(false) + return + } + }; + /* + endAdornment: ( + { + event.preventDefault() + }}> + { + setSearchOpen(false) + }} /> + + ), + */ + + return ( +
- {/* - - - - See all workflows - - - - */} - - ) - } - - const AppHits = ({ hits }) => { - const [mouseHoverIndex, setMouseHoverIndex] = useState(0) - - var tmp = searchOpen - if (!searchOpen) { - return null - } - - const positionInfo = document.activeElement.getBoundingClientRect() - const outerlistitemStyle = { - width: "100%", - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - } - - if (hits.length > 4) { - hits = hits.slice(0, 4) - } - - var type = "app" - const baseImage = - - return ( - - { - setSearchOpen(false) - }}> - - - - Apps - - - - {hits.length === 0 ? - - console.log(hits)}> - - - - - - - : - hits.map((hit, index) => { - const innerlistitemStyle = { - width: positionInfo.width+35, - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", - cursor: "pointer", - marginLeft: 5, - marginRight: 5, - maxHeight: 75, - minHeight: 75, - maxWidth: 420, - minWidth: "100%", - } - - const name = hit.name === undefined ? - hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title : - (hit.name.charAt(0).toUpperCase()+hit.name.slice(1)).replaceAll("_", " ") - - var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40)+"..." : "" - const avatar = hit.image_url === undefined ? - baseImage - : - - - //console.log(hit) - if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) { - secondaryText = hit.categories.slice(0,3).map((data, index) => { - if (index === 0) { - return data - } - - return ", "+data - - /* - { - //handleChipClick - }} - variant="outlined" - color="primary" - /> - */ - }) - } - - var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}` - parsedUrl += `?queryID=${hit.__queryID}` - - return ( - { - console.log("CLICK") - setSearchOpen(true) - - aa('init', { - appId: searchClient.appId, - apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] - }) - - const timestamp = new Date().getTime() - aa('sendEvents', [ - { - eventType: 'click', - eventName: 'App Clicked', - index: 'appsearch', - objectIDs: [hit.objectID], - timestamp: timestamp, - queryID: hit.__queryID, - positions: [hit.__position], - userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, - } - ]) - - if (!isCloud) { - event.preventDefault() - window.open(parsedUrl, '_blank'); - } - }}> - { - setMouseHoverIndex(index) - }}> - - {avatar} - - - {/* - - - - - - */} - - - )}) - } - - - - - See more - - - - - ) - } - - const DocHits = ({ hits }) => { - const [mouseHoverIndex, setMouseHoverIndex] = useState(0) - - var tmp = searchOpen - if (!searchOpen) { - return null - } - - - const positionInfo = document.activeElement.getBoundingClientRect() - const outerlistitemStyle = { - width: "100%", - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - } - - if (hits.length > 4) { - hits = hits.slice(0, 4) - } - - const type = "documentation" - const baseImage = - - //console.log(type, hits.length, hits) - - return ( - - { - setSearchOpen(false) - }}> - - - - Documentation - - {/* - { - setSearchOpen(false) - }}> - - - */} - - {hits.length === 0 ? - - console.log(hits)}> - - - - - - - : - hits.map((hit, index) => { - const innerlistitemStyle = { - width: positionInfo.width+35, - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", - cursor: "pointer", - marginLeft: 5, - marginRight: 5, - maxHeight: 75, - minHeight: 75, - maxWidth: 420, - minWidth: "100%", - } - - var name = hit.name === undefined ? - hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title - : - (hit.name.charAt(0).toUpperCase()+hit.name.slice(1)).replaceAll("_", " ") - - if (name.length > 30) { - name = name.slice(0, 30)+"..." - } - const secondaryText = hit.data !== undefined ? hit.data.slice(0, 40)+"..." : "" - const avatar = hit.image_url === undefined ? - baseImage - : - - - var parsedUrl = hit.urlpath !== undefined ? hit.urlpath : "" - parsedUrl += `?queryID=${hit.__queryID}` - if (parsedUrl.includes("/apps/")) { - const extraHash = hit.url_hash === undefined ? "" : `#${hit.url_hash}` - - parsedUrl = `/apps/${hit.filename}` - parsedUrl += `?tab=docs&queryID=${hit.__queryID}${extraHash}` - } - - return ( - { - aa('init', { - appId: searchClient.appId, - apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] - }) - - const timestamp = new Date().getTime() - aa('sendEvents', [ - { - eventType: 'click', - eventName: 'Document Clicked', - index: 'documentation', - objectIDs: [hit.objectID], - timestamp: timestamp, - queryID: hit.__queryID, - positions: [hit.__position], - userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, - } - ]) - - console.log("CLICK") - setSearchOpen(true) - }}> - { - setMouseHoverIndex(index) - }}> - - {avatar} - - - {/* - - - - - - */} - - - )}) - } - - {type === "documentation" ? - - - Search by - - - Algolia logo - - + const fieldWidth = small === true ? 120 : 310 + const modalView = ( + // console.log("key:", dataValue.key), + //console.log("value:",dataValue.value), + { + setModalOpen(false); + }} + PaperProps={{ + style: { + color: "white", + minWidth: 750, + height: 785, + borderRadius: 16, + border: "1px solid var(--Container-Stroke, #494949)", + background: "var(--Container, #000000)", + boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)", + zIndex: 13000, + }, + }} + > + {isHeader ?
+ Search Shuffle + +
: null} - - ) - } - - const CustomSearchBox = connectSearchBox(SearchBox) - const CustomAppHits = connectHits(AppHits) - const CustomWorkflowHits = connectHits(WorkflowHits) - const CustomDocHits = connectHits(DocHits) + + + + + +
+ {/* + Discord + */} + {/* + + Algolia logo + +
+
+ + Search by + + + Algolia logo + + */} +
+
+
+ ); return ( -
- { - console.log("CLICKED") - }}> - - - - - - - - - - - - +
+ {modalView} + + + + ), + endAdornment: ( + + ) + }} + variant="standard" + autoComplete='off' + color="primary" + placeholder="Search Apps, Workflows, Docs..." + onClick={(event) => { + setModalOpen(true) + }} + limit={5} + />
) } diff --git a/frontend/src/components/WorkflowTemplatePopup.jsx b/frontend/src/components/WorkflowTemplatePopup.jsx index 9b283da9..ff20ad36 100644 --- a/frontend/src/components/WorkflowTemplatePopup.jsx +++ b/frontend/src/components/WorkflowTemplatePopup.jsx @@ -22,6 +22,7 @@ import { Check as CheckIcon, TrendingFlat as TrendingFlatIcon, Close as CloseIcon, + East as EastIcon, } from '@mui/icons-material'; import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx"; @@ -36,6 +37,7 @@ const WorkflowTemplatePopup = (props) => { const [errorMessage, setErrorMessage] = useState(""); const [workflowLoading, setWorkflowLoading] = useState(false); const [workflow, setWorkflow] = useState({}); + const [showLoginButton, setShowLoginButton] = useState(false); const [appAuthentication, setAppAuthentication] = React.useState(undefined); const [missingSource, setMissingSource] = React.useState(undefined) @@ -119,7 +121,8 @@ const WorkflowTemplatePopup = (props) => { const loadAppAuth = () => { // Check if it exists, and has keys if (userdata === undefined || userdata === null || Object.keys(userdata).length === 0) { - setErrorMessage("You need to be logged in to try usecases. Redirecting in 5 seconds...") + setErrorMessage("You need to be logged in to try the pre-built Workflow Templates.") + setShowLoginButton(true) // Send the user to the login screen after 3 seconds setTimeout(() => { @@ -350,12 +353,35 @@ const WorkflowTemplatePopup = (props) => { {errorMessage !== "" ? errorMessage : ""} + {showLoginButton ? + + + Sign up + + + + : null}
} - {isLoggedIn && (missingSource !== undefined || missingDestination !== undefined) ? - - {"The following app category is required generate this workflow: "} + {(missingSource !== undefined || missingDestination !== undefined) ? + + {"Find relevevant Apps for this Usecase"} : null} @@ -502,7 +528,7 @@ const WorkflowTemplatePopup = (props) => { {parsedTitle} - + {parsedDescription}
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 5f65e35a..9631fd1e 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -291,8 +291,6 @@ const Admin = (props) => { //const alert = useAlert(); const handleStatusChange = (event) => { const { value } = event.target; - console.log("value: ", value) - setSelectedStatus(value); @@ -331,7 +329,7 @@ const Admin = (props) => { var your_apps = "- Connecting " var subject_add = 0 - var subject = "Want to automate " + var subject = "POC to automate " if (org.security_framework !== undefined && org.security_framework !== null) { if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") { @@ -400,7 +398,7 @@ const Admin = (props) => { // Remove comma - subject += "?" + //subject += "?" your_apps = your_apps.substring(0, your_apps.length - 2) } @@ -432,12 +430,25 @@ const Admin = (props) => { var admins = "" // Loop users + var lastLogin = 0 for (var i = 0; i < users.length; i++) { + if (users[i].username.includes("shuffler")) { + continue + } + if (users[i].role === "admin") { admins += users[i].username + "," } + + const data = users[i] + for (var i = 0; i < data.login_info.length; i++) { + if (data.login_info[i].timestamp > lastLogin) { + lastLogin = data.login_info[i].timestamp + } + } } + // Remove last comma admins = admins.substring(0, admins.length - 1) @@ -452,15 +463,24 @@ const Admin = (props) => { // Get drift username from userdata.username before @ in email const username = userdata.username.substring(0, userdata.username.indexOf("@")) - var body = `Hey,%0D%0A%0D%0AI saw you trying to use Shuffle, and thought we may be able to help. Right now, it looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting the most out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A + // Check if timestamp is more than 2 weeks ago and add "a while back" to the message + const timeComparison = 1209600 + const extra_timestamp_text = lastLogin === 0 ? 0 : (Date.now()/1000 - lastLogin) > timeComparison ? " a while back" : "" + console.log("LAST LOGIN: " + lastLogin, extra_timestamp_text) + + // Check if cloud sync is active, and if so, add a message about it + const cloudSyncInfo = selectedOrganization.cloud_sync === true ? "- Scale your onprem installation" : "" + + var body = `Hey,%0D%0A%0D%0AI noticed you tried to use Shuffle${extra_timestamp_text}, and thought you may be interested in a POC. It looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting what you wanted out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A Some of the things we can help with:%0D%0A ${your_apps} - Configuring and authenticating your apps%0D%0A ${usecases} -- Creating special usecases and apps%0D%0A%0D%0A +- Multi-Tenancy and creating special usecases%0D%0A +${cloudSyncInfo}%0D%0A -Let me know if you're interested, or set up a call here: https://drift.me/${username}` +If you're interested, please let me know a time that works for you, or set up a call here: https://drift.me/${username}` return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}` } @@ -984,6 +1004,10 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user leads.push("old customer") } + if (responseJson.lead_info.old_lead) { + leads.push("old lead") + } + if (responseJson.lead_info.tech_partner) { leads.push("tech partner") } @@ -2428,7 +2452,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user renderValue={(selected) => selected.join(', ')} MenuProps={MenuProps} > - {["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "old customer", "creator", "tech partner"].map((name) => ( + {["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "old customer", "old lead", ].map((name) => ( -1} /> @@ -3124,7 +3148,6 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user } if (loginInfo > 0) { - console.log("SETTING LAST LOGIN: " + loginInfo) lastLogin = new Date(loginInfo * 1000).toISOString().slice(0, 10) + " (" + data.login_info.length + ")" } } @@ -4382,7 +4405,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user - App Authentication + App Auth /> { } useEffect(() => { - console.log("In useeffect for loopRunning: ", loopRunning) + //console.log("In useeffect for loopRunning: ", loopRunning) if (loopRunning) { const intervalId = setInterval(() => { if (!loopRunning) { @@ -1244,7 +1244,7 @@ const AngularWorkflow = (defaultprops) => { // Doesn't work because this is some async garbage if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) { if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) { - console.log("Updating data!") + //console.log("Updating data!") setExecutionData(responseJson) } else { if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") { @@ -1713,7 +1713,9 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - toast("Failed to start: " + responseJson.reason); + //toast("Failed to start: " + responseJson.reason); + toast(responseJson.reason); + //toast.error(responseJson.reason); setExecutionRunning(false); setExecutionRequestStarted(false); stop(); @@ -4593,7 +4595,6 @@ const AngularWorkflow = (defaultprops) => { } } - console.log("NODE: ", nodedata) if (nodedata.decorator !== true && nodedata.attachedTo === undefined) { var newdata = JSON.parse(JSON.stringify(nodedata)) newdata.large_image = "" @@ -14591,7 +14592,7 @@ const AngularWorkflow = (defaultprops) => { onClose={() => { setExecutionModalOpen(false) }} - style={{ resize: "both", overflow: "auto", zIndex: 10005 }} + style={{ resize: "both", overflow: "auto", }} hideBackdrop={false} variant="temporary" BackdropProps={{ @@ -14607,7 +14608,6 @@ const AngularWorkflow = (defaultprops) => { maxWidth: isMobile ? "100%" : 420, color: "white", fontSize: 18, - zIndex: 10005, borderLeft: theme.palette.defaultBorder, }, }} @@ -14631,16 +14631,32 @@ const AngularWorkflow = (defaultprops) => { : null} {executionModalView === 0 ? (
- -

- - All Workflow Runs -

-
+
+ +

+ + All Workflow Runs +

+
+ + + + + +
) : null; @@ -2878,56 +2911,64 @@ const AppCreator = (defaultprops) => { color="textSecondary" style={{ marginTop: 10 }} > - Find the Authorization URL, Token URL and scopes in question for the API. Ensure the app in question is pointed at https://shuffler.io/set_authentication + {oauth2Type === "delegated" ? + "Find the Authorization URL, Token URL and scopes in question for the API. Ensure your app in the service uses redirect url https://shuffler.io/set_authentication" + : + "Find the Token URL and scopes in question for the API" + } - - Base Authorization URL for Oauth2 - - setParameterName(e.target.value)} + {oauth2Type === "delegated" ? + + + Base Authorization URL for Oauth2 + + setParameterName(e.target.value)} onBlur={(event) => { - var tmpstring = event.target.value.trim(); + var tmpstring = event.target.value.trim(); - if ( - tmpstring.length > 4 && - !tmpstring.startsWith("http") && - !tmpstring.startsWith("ftp") - ) { - toast("Auth URL must start with http(s)://"); - } + if ( + tmpstring.length > 4 && + !tmpstring.startsWith("http") && + !tmpstring.startsWith("ftp") + ) { + toast("Auth URL must start with http(s)://"); + } - if (tmpstring.includes("?")) { - var newtmp = tmpstring.split("?") - if (tmpstring.length > 1) { - tmpstring = newtmp[0] - } - } + if (tmpstring.includes("?")) { + var newtmp = tmpstring.split("?") + if (tmpstring.length > 1) { + tmpstring = newtmp[0] + } + } - setParameterName(tmpstring) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> + setParameterName(tmpstring) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + : null} { }, }} /> - - Refresh-token URL for Oauth2 (Optional) - - setRefreshUrl(e.target.value)} - onBlur={(event) => { - var tmpstring = event.target.value.trim(); + {oauth2Type === "delegated" ? + + + Refresh-token URL for Oauth2 (Optional) + + setRefreshUrl(e.target.value)} + onBlur={(event) => { + var tmpstring = event.target.value.trim(); - if ( - tmpstring.length > 4 && - !tmpstring.startsWith("http") && - !tmpstring.startsWith("ftp") - ) { - toast("Refresh URL must start with http(s)://"); - } + if ( + tmpstring.length > 4 && + !tmpstring.startsWith("http") && + !tmpstring.startsWith("ftp") + ) { + toast("Refresh URL must start with http(s)://"); + } - if (tmpstring.includes("?")) { - var newtmp = tmpstring.split("?") - if (tmpstring.length > 1) { - tmpstring = newtmp[0] - } - } + if (tmpstring.includes("?")) { + var newtmp = tmpstring.split("?") + if (tmpstring.length > 1) { + tmpstring = newtmp[0] + } + } - setRefreshUrl(tmpstring) - }} - InputProps={{ - style: { - color: "white", - }, - }} - /> - - Scopes for Oauth2 - - + + : null} + + Scopes for Oauth2 + + { setOauth2Scopes(chips) setUpdate(Math.random()) }} - /> + />
) : null; @@ -3125,7 +3170,7 @@ const AppCreator = (defaultprops) => {
- Value prefix + Prefix { value={refreshUrl} onChange={(e) => { // Just reusing this state - setRefreshUrl(e.target.value); + setRefreshUrl(e.target.value); }} />
@@ -3618,13 +3663,13 @@ const AppCreator = (defaultprops) => { { @@ -5865,6 +5910,7 @@ const AppCreator = (defaultprops) => { }} />
+ Authentication { setParameterLocation("") } - setExtraAuth([]) + setExtraAuth([]) } }} value={authenticationOption} @@ -5910,6 +5956,42 @@ const AppCreator = (defaultprops) => { ))} + {authenticationOption === "Oauth2" ? + + {/* + + - Delegated: The user will get a popup for access their personal data. + - Application: Permissions are set by the app creator in the 3rd party platform. + + */} + + + : null} + + +
{basicAuth} {bearerAuth} diff --git a/frontend/src/views/Welcome.jsx b/frontend/src/views/Welcome.jsx index 1b2085f4..991cd898 100644 --- a/frontend/src/views/Welcome.jsx +++ b/frontend/src/views/Welcome.jsx @@ -419,7 +419,7 @@ const Welcome = (props) => {
: -
+
Help us get to know you diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index bf32b52e..2bcd3b38 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -68,11 +68,12 @@ import { CloudDownload as CloudDownloadIcon, ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, - Done as DoneIcon, - CheckCircle as CheckCircleIcon, - RadioButtonUnchecked as RadioButtonUncheckedIcon, + Done as DoneIcon, + CheckCircle as CheckCircleIcon, + RadioButtonUnchecked as RadioButtonUncheckedIcon, ArrowLeft as ArrowLeftIcon, ArrowRight as ArrowRightIcon, + QueryStats as QueryStatsIcon, } from "@mui/icons-material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; @@ -3061,6 +3062,18 @@ const Workflows = (props) => { const workflowButtons = ( + + + {view === "list" && (