diff --git a/frontend/src/components/AppFramework.jsx b/frontend/src/components/AppFramework.jsx index 3d786443..093c9550 100644 --- a/frontend/src/components/AppFramework.jsx +++ b/frontend/src/components/AppFramework.jsx @@ -53,13 +53,15 @@ export const findSpecificApp = (framework, inputcategory) => { } const category = inputcategory.toLowerCase().split(":")[0].trim() - - //console.log("findSpecificApp: ", category, framework) if (category === "edr" || category === "eradication" || category === "edr & av") { - if (framework["EDR & AV"] !== undefined && framework["EDR & AV"].name !== undefined) { + if (framework["EDR & AV"] !== undefined && framework["EDR & AV"].name !== undefined && framework["EDR & AV"].name !== "") { return framework["EDR & AV"] } + if (framework["edr"] !== undefined && framework["edr"].name !== undefined && framework["edr"].name !== "") { + return framework["edr"] + } + return { name: "EDR :default", large_image: parsedDatatypeImages()["EDR & AV"], @@ -68,10 +70,14 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "communication" || category === "comms") { - if (framework["Comms"] !== undefined && framework["Comms"].name !== undefined) { + if (framework["Comms"] !== undefined && framework["Comms"].name !== undefined && framework["Comms"].name !== "") { return framework["Comms"] } + if (framework["communication"] !== undefined && framework["communication"].name !== undefined && framework["communication"].name !== "") { + return framework["communication"] + } + return { name: "COMMS :default", large_image: parsedDatatypeImages()["COMMS"], @@ -80,10 +86,14 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "email") { - if (framework["Email"] !== undefined && framework["Email"].name !== undefined) { + if (framework["Email"] !== undefined && framework["Email"].name !== undefined && framework["Email"].name !== "") { return framework["Email"] } + if (framework["email"] !== undefined && framework["email"].name !== undefined && framework["email"].name !== "") { + return framework["email"] + } + return { name: "COMMS :default", large_image: parsedDatatypeImages()["COMMS"], @@ -92,10 +102,14 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "assets") { - if (framework["Assets"] !== undefined && framework["Assets"].name !== undefined) { + if (framework["Assets"] !== undefined && framework["Assets"].name !== undefined && framework["Assets"].name !== "") { return framework["Assets"] } + if (framework["assets"] !== undefined && framework["assets"].name !== undefined && framework["assets"].name !== "") { + return framework["assets"] + } + return { name: "ASSETS :default", large_image: parsedDatatypeImages()["ASSETS"], @@ -104,10 +118,14 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "cases") { - if (framework["Cases"] !== undefined && framework["Cases"].name !== undefined) { + if (framework["Cases"] !== undefined && framework["Cases"].name !== undefined && framework["Cases"].name !== "") { return framework["Cases"] } + if (framework["cases"] !== undefined && framework["cases"].name !== undefined && framework["cases"].name !== "") { + return framework["cases"] + } + return { name: "CASES :default", large_image: parsedDatatypeImages()["CASES"], @@ -116,10 +134,14 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "iam") { - if (framework["IAM"] !== undefined && framework["IAM"].name !== undefined) { + if (framework["IAM"] !== undefined && framework["IAM"].name !== undefined && framework["IAM"].name !== "") { return framework["IAM"] } + if (framework["iam"] !== undefined && framework["iam"].name !== undefined && framework["iam"].name !== "") { + return framework["iam"] + } + return { name: "IAM :default", large_image: parsedDatatypeImages()["IAM"], @@ -128,10 +150,14 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "network") { - if (framework["Network"] !== undefined && framework["Network"].name !== undefined) { + if (framework["Network"] !== undefined && framework["Network"].name !== undefined && framework["Network"].name !== "") { return framework["Network"] } + if (framework["network"] !== undefined && framework["network"].name !== undefined && framework["network"].name !== "") { + return framework["network"] + } + return { name: "Network :default", large_image: parsedDatatypeImages()["NETWORK"], @@ -140,10 +166,14 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "intel") { - if (framework["Intel"] !== undefined && framework["Intel"].name !== undefined) { + if (framework["Intel"] !== undefined && framework["Intel"].name !== undefined && framework["Intel"].name !== "") { return framework["Intel"] } + if (framework["intel"] !== undefined && framework["intel"].name !== undefined && framework["intel"].name !== "") { + return framework["intel"] + } + return { name: "INTEL :default", large_image: parsedDatatypeImages()["INTEL"], @@ -152,9 +182,13 @@ export const findSpecificApp = (framework, inputcategory) => { id: "", } } else if (category === "siem") { - if (framework["SIEM"] !== undefined && framework["SIEM"].name !== undefined) { + if (framework["SIEM"] !== undefined && framework["SIEM"].name !== undefined && framework["SIEM"].name !== "") { return framework["SIEM"] } + + if (framework["siem"] !== undefined && framework["siem"].name !== undefined && framework["siem"].name !== "") { + return framework["siem"] + } return { name: "SIEM :default", diff --git a/frontend/src/components/AppSearchButtons.jsx b/frontend/src/components/AppSearchButtons.jsx index 6bc3179a..4a4eef4e 100644 --- a/frontend/src/components/AppSearchButtons.jsx +++ b/frontend/src/components/AppSearchButtons.jsx @@ -47,6 +47,8 @@ const AppSearchButtons = (props) => { const [newSelectedApp, setNewSelectedApp] = useState(undefined) useEffect(() => { + console.log("UPDATED APP: ", newSelectedApp) + if (newSelectedApp !== undefined && setMissing != undefined) { const submitAppFramework = { "description": newSelectedApp.description, @@ -136,8 +138,14 @@ const AppSearchButtons = (props) => { const icon = foundApp.large_image var foundAppImage = AppImage - if (foundApp.name !== undefined && foundApp.name !== null && !foundApp.name.includes(":default")) { - foundAppImage = foundApp.large_image + if (foundApp.name !== undefined && foundApp.name !== null && foundApp.name.length > 0 && !foundApp.name.includes(":default")) { + + if (AppImage === undefined || AppImage === null || AppImage.length < 10) { + foundAppImage = foundApp.large_image + } + } else { + const newapp = findSpecificApp(appFramework, appType) + // const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, getAppFramework, } = props } let xsValue = 12; @@ -204,9 +212,14 @@ const AppSearchButtons = (props) => { { + {/* { + */}
{ userdata, globalUrl, appFramework, + setAppFramework, setActiveStep, defaultSearch, setDefaultSearch, checkLogin, + } = props; const [discoveryData, setDiscoveryData] = React.useState({}) const [selectionOpen, setSelectionOpen] = React.useState(false) @@ -57,6 +60,7 @@ const AppSelection = props => { const [moreButton, setMoreButton] = useState(false); // const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) + document.title = "Choose your apps" const ref = useRef() let navigate = useNavigate(); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; @@ -98,74 +102,75 @@ const AppSelection = props => { else if (discoveryData === "IAM") { appFramework.iam = submitNewApp } - setFrameworkItem(submitNewApp); - setSelectionOpen(false); - console.log("Selected app changed (effect)"); + + setFrameworkItem(submitNewApp) + setSelectionOpen(false) + + if (setAppFramework !== undefined) { + setAppFramework(appFramework) + } + GetApps() }, [newSelectedApp]); + const reloadAppButtons = (framework) => { + var tempApps = [] + const lastApps = {} + let endTypes = ["network", "assets", "iam"] + + if (framework === undefined || framework === null || Object.keys(framework).length === 0) { + //window.location.href = "/welcome" + return + } + + Object.entries(framework).forEach(([key, value]) => { + // Overwrrite email properly + if (key.toLowerCase() === "communication") { + value["type"] = "email" + framework["email"] = value + return + } + + if (key.toLowerCase() === "other") { + return + } + + value.type = key; + if (endTypes.includes(value.type.toLowerCase())) { + lastApps[value.type] = value + return + } + + if (lastPosted.type === value.type) { + value = lastPosted + } + + tempApps.push(JSON.parse(JSON.stringify(value))); + }); + + tempApps.sort((a, b) => { + if (a.type.length > b.type.length) { + return -1; + } else if (a.type.length < b.type.length) { + return 1; + } + }); + + let lastType = lastPosted.type === undefined ? "" : lastPosted.type.toLowerCase() + if (endTypes.includes(lastType)) { + lastApps[lastPosted.type] = lastPosted + } + + if (moreButton) { + tempApps.push(JSON.parse(JSON.stringify(lastApps["network"]))) + tempApps.push(JSON.parse(JSON.stringify(lastApps["assets"]))) + tempApps.push(JSON.parse(JSON.stringify(lastApps["iam"]))) + } + + setAppButtons(tempApps) + } + useEffect(() => { - var tempApps = [] - if (tempApps.length === 0) { - // Object.entries(appFramework).forEach(([key, value]) => { - // value.type = key; - // tempApps.push(value); - // }); - - // // Define the custom sorting order - // const customSortingOrder = ["CASES", "SIEM", "ENDPOINT", "INTEL", "EMAIL"]; - - const lastApps = {} - let endTypes = ["network", "assets", "iam"] - - if (appFramework === undefined || appFramework === null || Object.keys(appFramework).length === 0) { - //window.location.href = "/welcome" - return - } - - - Object.entries(appFramework).forEach(([key, value]) => { - if (key.toLowerCase() === "other" || key.toLowerCase() === "communication") { - return - } - - value.type = key; - - if (endTypes.includes(value.type.toLowerCase())) { - lastApps[value.type] = value - return - } - - if (lastPosted.type === value.type) { - value = lastPosted - } - - tempApps.push(JSON.parse(JSON.stringify(value))); - }); - - tempApps.sort((a, b) => { - if (a.type.length > b.type.length) { - return -1; - } else if (a.type.length < b.type.length) { - return 1; - } - }); - - let lastType = lastPosted.type === undefined ? "" : lastPosted.type.toLowerCase() - - if (endTypes.includes(lastType)) { - lastApps[lastPosted.type] = lastPosted - } - - if (moreButton) { - tempApps.push(JSON.parse(JSON.stringify(lastApps["network"]))) - tempApps.push(JSON.parse(JSON.stringify(lastApps["assets"]))) - tempApps.push(JSON.parse(JSON.stringify(lastApps["iam"]))) - } - - setAppButtons(tempApps) - console.log("Updated appButtons: ", appButtons) - GetApps() - } + reloadAppButtons(appFramework) }, [lastPosted, moreButton]) if (appFramework === undefined || appFramework === null || Object.keys(appFramework).length === 0) { @@ -174,11 +179,6 @@ const AppSelection = props => { } const setFrameworkItem = (data) => { - console.log("Setting framework item: ", data, isCloud) - // if (!isCloud) { - // activateApp(data.id) - // } - fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { method: "POST", headers: { @@ -241,31 +241,39 @@ const AppSelection = props => { body: JSON.stringify(data), credentials: "include", }) - .then((responseJson) => { - if (responseJson === null) { - console.log("null-response from server") - const pretend_apps = [{ - "description": "TBD", - "id": "TBD", - "large_image": "", - "name": "TBD", - "type": "TBD" - }] + .then((response) => { + return response.json() + }) + .then((responseJson) => { + if (responseJson === null || responseJson === undefined) { + console.log("null-response from server") + const pretend_apps = [{ + "description": "TBD", + "id": "TBD", + "large_image": "", + "name": "TBD", + "type": "TBD" + }] - setApps(pretend_apps) - return - } + setApps(pretend_apps) + return + } - if (responseJson.success === false) { - console.log("error loading apps: ", responseJson) - return - } + if (responseJson.success === false) { + console.log("error loading apps: ", responseJson) + return + } - setApps(responseJson); - }) - .catch((error) => { - console.log("App loading error: " + error.toString()); - }) + if (setAppFramework !== undefined) { + setAppFramework(responseJson) + } + + setApps(responseJson) + reloadAppButtons(responseJson) + }) + .catch((error) => { + console.log("App loading error: " + error.toString()); + }) } const onNodeSelect = (label) => { @@ -277,8 +285,6 @@ const AppSelection = props => { }); } - console.log("NODESELECT: ", label) - setDiscoveryData(label) setSelectionOpen(true) setDefaultSearch(label.charAt(0).toUpperCase() + (label.substring(1)).toLowerCase()) @@ -304,209 +310,225 @@ const AppSelection = props => { }; return ( - - - { - navigate('/welcome'); - window.location.reload(); - }} - > - - Back - - -
- {selectionOpen ? ( -
-
-
- {discoveryData} -
-
- - { - setSelectionOpen(false) - }} - > - - - - - { - e.preventDefault(); - setSelectionOpen(false) - setDefaultSearch("") - const submitDeletedApp = { - "description": "", - "id": "remove", - "name": "", - "type": discoveryData - } - setFrameworkItem(submitDeletedApp) - setNewSelectedApp({}) - setTimeout(() => { - setDiscoveryData({}) - setFrameworkItem(submitDeletedApp) - setNewSelectedApp({}) - }, 1000) - //setAppName(discoveryData.cases.name) - }} - > - - - -
-
-
- -
- ) : null} - - Find your apps - - - Select the apps you work with and we will connect them for you. - - - {appButtons.map((appData, index) => { - // This is here due to a memory issue with setting apps properly - if (appData.id === "remove") { - console.log("Removed as appdata is overridden: ", appData) + +
+ {/* + + { + navigate('/welcome'); + window.location.reload(); + }} + > + + Back + + + */} +
+ {selectionOpen ? ( +
+
+
+ {discoveryData} +
+
+ + { + setSelectionOpen(false) + }} + > + + + + + { + e.preventDefault(); + setSelectionOpen(false) + setDefaultSearch("") + const submitDeletedApp = { + "description": "", + "id": "remove", + "name": "", + "type": discoveryData + } - const appName = appData.name - const AppImage = appData.large_image - const appType = appData.type + setFrameworkItem(submitDeletedApp) + setNewSelectedApp({}) + setTimeout(() => { + setDiscoveryData({}) + setFrameworkItem(submitDeletedApp) + setNewSelectedApp({}) + }, 200) + //setAppName(discoveryData.cases.name) + }} + > + + + +
+
+
+ +
+ ) : null} + + Find your apps + + + Select the apps you work with and we will connect them for you. + + + {appButtons.map((appData, index) => { + // This is here due to a memory issue with setting apps properly + if (appData.id === "remove") { + appData = { + "count": 0, + "description": "", + "id": "", + "large_image": "", + "name": "", + "type": appData.type, + } + } - return ( - - ) - })} - -
- {!moreButton ? ( -
- { - setMoreButton(true) - setTimeout(() => { - navigate("/welcome?tab=2") - }, 250) - }} - >See More Apps -
) : ""} -
- -
- + if (appData === undefined || appData === null || appData.name === undefined || appData.name === "") { + appData = { + "count": 0, + "description": "", + "id": "", + "large_image": "", + "name": "", + "type": appData.type, + } + } + + //console.log("APP: ", appData) + + const appName = appData.name + const AppImage = appData.large_image + const appType = appData.type + + return ( + + ) + })} + +
+ {!moreButton ? ( +
+ { + setMoreButton(true) + + setTimeout(() => { + navigate("/welcome?tab=2") + }, 250) + }} + >See More Apps +
) : ""} + +
+ +
+
+
) } diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index de38ef86..39734084 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -122,7 +122,7 @@ const Branding = (props) => { return (

- Branding + Partner Status & Branding

You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more. diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 6c8ae0ee..f7bbb7ed 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -792,8 +792,6 @@ const ConfigureWorkflow = (props) => { } parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " "); - - console.log("AUTH Action: ", action) return ( { - const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, } = props + const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, } = props const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [submitLoading, setSubmitLoading] = React.useState(false); - const [showMoreClicked, setShowMoreClicked] = React.useState(false); + const [showMoreClicked, setShowMoreClicked] = React.useState(expanded === true ? true : false); const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) const [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : []) @@ -80,6 +80,8 @@ const EditWorkflow = (props) => { const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day')) const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : []) + const [inputMarkdown, setInputMarkdown] = React.useState(workflow.input_markdown !== undefined && workflow.input_markdown !== null ? workflow.input_markdown : "") + const [outputMarkdown, setOutputMarkdown] = React.useState(workflow.output_markdown !== undefined && workflow.output_markdown !== null ? workflow.output_markdown : "") const classes = useStyles(); @@ -283,6 +285,7 @@ const EditWorkflow = (props) => { } innerWorkflow.input_questions = validfields + innerWorkflow.input_markdown = inputMarkdown innerWorkflow.name = name innerWorkflow.description = description @@ -298,7 +301,13 @@ const EditWorkflow = (props) => { innerWorkflow.due_date = new Date(`${dueDate["$y"]}-${dueDate["$M"]+1}-${dueDate["$D"]}`).getTime()/1000 } - if (setNewWorkflow !== undefined) { + if (saveWorkflow !== undefined) { + saveWorkflow(innerWorkflow) + + if (setWorkflow !== undefined) { + setWorkflow(innerWorkflow) + } + } else if (setNewWorkflow !== undefined) { setNewWorkflow( innerWorkflow.name, innerWorkflow.description, @@ -804,6 +813,40 @@ const EditWorkflow = (props) => { + + {inputQuestions.length === 0 ? null : +
+ + Input Markdown + + { + setInputMarkdown(e.target.value) + workflow.input_markdown = e.target.value + setWorkflow(workflow) + setUpdate(Math.random()) + }} + /> + + {/* + + Output Markdown + + + */} +
+ } + diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index e196c2cf..2afba2b0 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -171,7 +171,7 @@ const Header = (props) => { setTooltipOpen(true); }; - const topbar_var = "topbar_closed2" + const topbar_var = "topbar_closed4" useEffect(() => { const topbar = localStorage.getItem(topbar_var) @@ -575,15 +575,15 @@ const Header = (props) => { //globalUrl = responseJson.region_url } if (responseJson["reason"] === "SSO_REDIRECT") { - setTimeout(() => { - toast.info("Redirecting to SSO login page as SSO is required for this organization.") - window.location.href = responseJson["url"] - return - }, 2000) + setTimeout(() => { + toast.info("Redirecting to SSO login page as SSO is required for this organization.") + window.location.href = responseJson["url"] + return + }, 2000) } else { - setTimeout(() => { - window.location.reload() - }, 2000); + setTimeout(() => { + window.location.reload() + }, 2000); } toast("Successfully changed active organization - refreshing!"); } else { @@ -711,7 +711,7 @@ const Header = (props) => { */} - + { handleClose(); @@ -858,7 +858,7 @@ const Header = (props) => { - + @@ -1639,12 +1638,12 @@ const Header = (props) => { const topbarHeight = showTopbar ? 40 : 0 const topbar = !isCloud || !showTopbar ? null : - curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" || curpath === "/professional-services" ? + curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/usecases2" || curpath === "/training" || curpath === "/professional-services" ?
- + {/* Shuffle 1.4.0 is out! Read more about  */} - New Public  + New  { ReactGA.event({ @@ -1655,11 +1654,11 @@ const Header = (props) => { navigate("/training") - }} style={{ cursor: "pointer", textDecoration: "none", fontWeight: 600, color: "rgba(255,255,255,0.8)" }}> - Training Dates Released + }} style={{ cursor: "pointer", textDecoration: "none", fontWeight: 600, color: "rgba(255,255,255,0.9)" }}> + Public Training -  Ahead! +  Dates Released! { setShowTopbar(false) diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index e9e794b2..45324bd6 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -299,12 +299,11 @@ const AuthenticationOauth2 = (props) => { const handleOauth2Request = (client_id, client_secret, oauth_url, scopes, admin_consent, prompt, skipScopeReplace) => { - console.log("SKIP SCOPE: ", skipScopeReplace) if (skipScopeReplace === false || skipScopeReplace === undefined) { console.log("Selected scopes: ", selectedScopes) if (selectedScopes !== undefined && selectedScopes !== null && selectedScopes.length > 0) { - toast("Using your scopes instead of the default ones") + //toast("Using your scopes instead of the default ones") scopes = selectedScopes } } @@ -484,7 +483,27 @@ const AuthenticationOauth2 = (props) => { url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; } + if (url !== undefined && url !== null && url.length > 0) { + if (url.toLowerCase().includes("{tenant")) { + // Check location of {tenant, then find the next } and replace with 'common'. Make sure next } is AFTER {tenant + try { + const tenantIndex = url.toLowerCase().indexOf("{tenant") + const substring = url.substring(tenantIndex) + const nextBracket = substring.indexOf("}") + const newUrl = url.substring(0, tenantIndex) + "common" + url.substring(tenantIndex + nextBracket + 1) + url = newUrl + } catch (e) { + console.log("Failed to replace {tenant} with common: ", e) + } + + } + } + + /* console.log("OAUTH2 URL: ", url) + return + */ + // Force new consent //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index d4cf0541..fb462566 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -600,8 +600,9 @@ const ParsedAction = (props) => { let errorVars = []; if(paramvalue.includes("$")){ let actions = workflow.actions?.map((action) => { - return "$"+action.label.toLowerCase(); + return "$"+action.label?.toLowerCase(); }) + if(newActionList?.length > 0){ let appParentActions = parentActionList?.map(action => "$" + action.name.toLowerCase()); let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action)) @@ -629,9 +630,9 @@ const ParsedAction = (props) => { let regex = /(^|[^\\])\$/; if (regex.test(paramvalue)) { if(message.length > 0){ - message += "\nUse \"\\$\" instead of \"$\"."; + message += "\nUse \"\\$\" instead of \"$\"."; }else{ - message = "Use \"\\$\" instead of \"$\"."; + message = "Use \"\\$\" instead of \"$\" to escape variables."; } } } @@ -640,7 +641,7 @@ const ParsedAction = (props) => { setSelectedActionParameters(newParameters); setActionlist(newActionList); }, [workflow.execution_variables,paramUpdate, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents,setNewSelectedAction]); - console.log("Selected Action:", selectedAction) + useEffect(() => { selectedNameChange(appActionName) @@ -1280,12 +1281,14 @@ const ParsedAction = (props) => { } } - var authWritten = false; + var authWritten = false; var noAppSelected = false - var paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") - if (paramIndex === -1 || selectedAction.parameters[paramIndex].value === "" || selectedAction.parameters[paramIndex].value === "noapp") { - // Check the actual value and if it's the same - noAppSelected = true + if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { + var paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") + if (paramIndex === -1 || selectedAction.parameters[paramIndex].value === "" || selectedAction.parameters[paramIndex].value === "noapp") { + // Check the actual value and if it's the same + noAppSelected = true + } } } @@ -1320,8 +1323,14 @@ const ParsedAction = (props) => { } }); } - setHiddenDescription(false) - document.activeElement.blur(); + + document.activeElement.blur(); + + const disabledUiBox = localStorage.getItem("disabled_ui_box") + if (disabledUiBox === "true") { + } else { + setHiddenDescription(false) + } }} >
@@ -1874,11 +1883,9 @@ const ParsedAction = (props) => { } } - console.log("Old found: ", workflow.actions[key].parameters[subkey].value) const extralength = newname.length-parsedBaseLabel.length param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) - console.log("New: ", workflow.actions[key].parameters[subkey].value) } else { break } @@ -1990,18 +1997,25 @@ const ParsedAction = (props) => { selectedAction.authentication_id = ""; for (let [key,keyval] in Object.entries(selectedAction.parameters)) { - if (selectedAction.parameters[key].configuration) { + if (selectedAction.parameters[key].configuration === false) { + console.log("FIELDSKIP: ", selectedAction.parameters[key].name) + continue + } - if (selectedAction.parameters[key].example !== undefined && selectedAction.parameters[key].example !== null && selectedAction.parameters[key].example !== "") { - if (selectedAction.parameters[key].example.toLowerCase().includes("api") || selectedAction.parameters[key].example.toLowerCase().includes("key") || selectedAction.parameters[key].example.toLowerCase().includes("pass")) { - selectedAction.parameters[key].value = "" - } else { - selectedAction.parameters[key].value = selectedAction.parameters[key].example - } - } else { - selectedAction.parameters[key].value = "" - } - } + if (selectedAction.parameters[key].name === "url" && authenticationType?.type === "oauth2-app" && selectedAction.parameters[key].value.includes("http")) { + continue + } + + if (selectedAction.parameters[key].example !== undefined && selectedAction.parameters[key].example !== null && selectedAction.parameters[key].example !== "") { + if (selectedAction.parameters[key].example.toLowerCase().includes("apik") || selectedAction.parameters[key].example.toLowerCase().includes("key") || selectedAction.parameters[key].example.toLowerCase().includes("pass") || selectedAction.parameters[key].example.toLowerCase().includes("****")) { + selectedAction.parameters[key].value = "" + } else { + selectedAction.parameters[key].value = selectedAction.parameters[key].example + } + + } else { + selectedAction.parameters[key].value = "" + } } setSelectedAction(selectedAction); setUpdate(Math.random()); @@ -2020,7 +2034,11 @@ const ParsedAction = (props) => { for (let [key,keyval] in Object.entries(selectedAction.parameters)) { //console.log(selectedAction.parameters[key]) if (selectedAction.parameters[key].configuration) { - selectedAction.parameters[key].value = "authgroup controlled" + + if (selectedAction.parameters[key].name === "url" && authenticationType?.type === "oauth2-app") { + } else { + selectedAction.parameters[key].value = "authgroup controlled" + } } } @@ -2130,6 +2148,14 @@ const ParsedAction = (props) => {
) : null} + {selectedAction.authentication_id === "authgroups" && (authGroups === undefined || authGroups === null || authGroups.length === 0) ? + + + Create your first Authentication group + + + : null} + {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? (
@@ -2611,7 +2637,7 @@ const ParsedAction = (props) => { marginBottom: hideExtraTypes ? 50 : 200, }} > { - Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0 ? + selectedActionParameters !== undefined && selectedActionParameters !== null && Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0 ?
{isIntegration ? apps !== undefined && apps !== null && apps.length > 0 ? @@ -2907,20 +2933,59 @@ const ParsedAction = (props) => { } if (data.value === "authgroup controlled") { - return null + if (data?.name === "url" && authenticationType?.type === "oauth2-app") { + } else { + return null + } } - // selectedAction.selectedAuthentication = e.target.value - // selectedAction.authentication_id = e.target.value.id + /* if ( (selectedAction.auth_not_required !== undefined && !selectedAction.auth_not_required) && + selectedActionParameters[count] !== undefined && + selectedActionParameters[count] !== null && selectedActionParameters[count].value !== undefined && + selectedAction.parameters[count] !== undefined && + selectedAction.parameters[count] !== null && selectedAction.parameters[count].value !== undefined && selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined ) { + */ + + /* + if (selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) { + + // This sets the placeholder in the frontend. (Replaced in backend) + selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name]; + selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name]; + setSelectedAction(selectedAction); + //setUpdate(Math.random()) + + if (authWritten) { + return null + } + + authWritten = true + return ( + + Authentication fields are hidden + + ) + } + */ + + + //!selectedAction.auth_not_required && + if (selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) { // This sets the placeholder in the frontend. (Replaced in backend) selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name]; @@ -2947,6 +3012,7 @@ const ParsedAction = (props) => { ); } + // Added autofill to make this ALOT simpler if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { if (selectedActionParameters[count].length === 0) { @@ -3063,6 +3129,7 @@ const ParsedAction = (props) => { var rows = "3"; var openApiHelperText = "This is an OpenAPI specific field"; + if (selectedApp.generated && data.name === "headers") { //console.log("HEADER: ", data) //if (data.value.length === 0) { @@ -3278,7 +3345,7 @@ const ParsedAction = (props) => { const description = data.description === undefined ? "" : data?.description; const tooltipDescription = ( - { {tmpitem.charAt(0).toUpperCase() + tmpitem.slice(1)} { - event.preventDefault(); - event.stopPropagation(); - }} - - onClick={() => { - setUiBox("closed") - const inputElement = document.getElementById(uiBox); - if (inputElement) { - inputElement.focus(); - } - }}> + onClick={() => { + setUiBox("closed") + + /* + const inputElement = document.getElementById(uiBox); + if (inputElement) { + inputElement.focus(); + } + */ + }} + > @@ -3316,7 +3381,7 @@ const ParsedAction = (props) => { Description: {description} - Ex. : {data?.example.length > 0 ? data.example : "No example available"} + Ex. : {data?.example?.length > 0 ? data.example : "No example available"} { data?.configuration === true ? @@ -3326,15 +3391,31 @@ const ParsedAction = (props) => { ) : null } + +
{ + e.preventDefault() + e.stopPropagation() + + localStorage.setItem("disabled_ui_box", "true") + setUiBox("closed") + }}> + + Don't show again + +
); + var datafield = ( { }} > { setScrollConfig(scrollConfig) } }} - id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" // defaultValue={data.value} @@ -3458,15 +3539,28 @@ const ParsedAction = (props) => { handleParamChange(event, count, data) }} onFocus={(event) => { - setUiBox(event.target.id) - + // Get local storage key "disabled_ui_box" and check if it's true + const disabledUiBox = localStorage.getItem("disabled_ui_box") + if (disabledUiBox === "true") { + } else { + //setUiBox(event.target.id) + } }} onBlur={(event) => { baseHelperText = calculateHelpertext(event.target.value) if (setLastSaved !== undefined) { setLastSaved(false) } - setUiBox("closed") + + // Check if we clicked the tooltip or not + const tooltipid = "rightside_field_tooltip" + count + const foundElement = document.getElementById(tooltipid) + if (foundElement !== null && foundElement !== undefined) { + console.log("FOUND: ", foundElement) + } else { + //console.log("TOOLTIP -> NOT FOUND") + //setUiBox("closed") + } }} /> @@ -3664,7 +3758,6 @@ const ParsedAction = (props) => {
} - //console.log("FIELD VALUE: ", data.value) //const regexp = new RegExp("\W+\.", "g") //let match //while ((match = regexp.exec(data.value)) !== null) { @@ -3680,15 +3773,13 @@ const ParsedAction = (props) => { // } //} - // Basic helpertext - if (files !== undefined && files !== null && data.name.toLowerCase() === "file_category") { - //selectedActionParameters[count].options.length > 0 - console.log("FileS: ", files) - if (files.namespaces !== undefined && files.namespaces !== null && files.namespaces.length > 0) { - data.options = files.namespaces - } - } + //selectedActionParameters[count].options.length > 0 + console.log("FileS: ", files) + if (files.namespaces !== undefined && files.namespaces !== null && files.namespaces.length > 0) { + data.options = files.namespaces + } + } //const keywords = ["len", "lower", "upper", "trim", "split", "length", "number", "parse", "join"] if ( @@ -3736,26 +3827,11 @@ const ParsedAction = (props) => { }} onBlur={(event) => {}} /> - ); - //const fileId = "6daabec1-892b-469c-b603-c902e47223a9" - //datafield = `SHOW FILES FROM OTHER NODES? Filename: ${selectedActionParameters[count].value}` - /* - if (selectedActionParameters[count].value != fileId) { - changeActionParameter(fileId, count, data) - setUpdate(Math.random()) - - } - */ + ) } else if ( - (data.options !== undefined && - data.options !== null && - data.options.length > 0) - || - (selectedActionParameters[count].options !== undefined && - selectedActionParameters[count].options !== null && - selectedActionParameters[count].options.length > 0) - ) { - const parsedoptions = data.options !== undefined && data.options !== null && data.options.length > 0 ? data.options : selectedActionParameters[count].options + (data.options !== undefined && data.options !== null && data.options.length > 0) || + (selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0)) { + const parsedoptions = data.options !== undefined && data.options !== null && data.options.length > 0 ? data.options : selectedActionParameters[count].options if (selectedActionParameters[count].value === "") { // && selectedActionParameters[count].required) { @@ -3833,7 +3909,7 @@ const ParsedAction = (props) => { } if (data.field_active === false) { - return null; + return null; } @@ -4212,12 +4288,18 @@ const ParsedAction = (props) => { const buttonTitle = `Authenticate ${selectedApp.name.replaceAll("_", " ")}` const hasAutocomplete = data?.autocompleted === true - - if (data.variant === undefined || data.variant === null) { data.variant = "STATIC_VALUE" } + /* + if (data?.configuration === true) { + if (data?.name === "url" && authenticationType?.type === "oauth2-app") { + data.configuration = false + } + } + */ + return (
{/* {hideBodyButton} */} diff --git a/frontend/src/components/WelcomeForm2.jsx b/frontend/src/components/WelcomeForm2.jsx index 3c9200e0..47c040db 100644 --- a/frontend/src/components/WelcomeForm2.jsx +++ b/frontend/src/components/WelcomeForm2.jsx @@ -64,6 +64,7 @@ const WelcomeForm = (props) => { discoveryWrapper, setDiscoveryWrapper, appFramework, + setAppFramework, getFramework, activeStep, setActiveStep, @@ -435,20 +436,6 @@ const WelcomeForm = (props) => { setSkipped(newSkipped); }; - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - - if (activeStep === 2) { - setDiscoveryWrapper({}); - - if (getFramework !== undefined) { - getFramework(); - } - navigate("/welcome?tab=2"); - } else if (activeStep === 1) { - navigate("/welcome?tab=1"); - } - }; const handleReset = () => { setActiveStep(0); @@ -645,6 +632,7 @@ const WelcomeForm = (props) => { globalUrl={globalUrl} userdata={userdata} appFramework={appFramework} + setAppFramework={setAppFramework} setActiveStep={setActiveStep} defaultSearch={defaultSearch} setDefaultSearch={setDefaultSearch} diff --git a/frontend/src/components/WorkflowValidationTimeline.jsx b/frontend/src/components/WorkflowValidationTimeline.jsx index 2b4b2c13..04eb2314 100644 --- a/frontend/src/components/WorkflowValidationTimeline.jsx +++ b/frontend/src/components/WorkflowValidationTimeline.jsx @@ -4,21 +4,28 @@ import { toast } from "react-toastify" import { Tooltip, + Chip, Typography, + IconButton, Avatar, AvatarGroup, } from "@mui/material" +import { + ErrorOutline as ErrorOutlineIcon, +} from "@mui/icons-material" + import { green, yellow, red, + grey, } from "../views/AngularWorkflow.jsx" import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; import theme from "../theme.jsx"; -const itemHeight = 40 +const itemHeight = 24 export const getParentNodes = (workflow, action) => { if (action === undefined || action === null) { @@ -123,6 +130,8 @@ export const getParentNodes = (workflow, action) => { const WorkflowValidationTimeline = (props) => { const { workflow, originalWorkflow, apps, getParents, execution} = props + const showMiddle = false + if (workflow === undefined || workflow === null) { return null @@ -196,6 +205,15 @@ const WorkflowValidationTimeline = (props) => { parents = getParentNodes(workflow, action) } + if (action.app_name === "Integration Framework" || action.app_name === "Integration") { + for (var paramkey in action.parameters) { + const param = action.parameters[paramkey] + if (param.name === "app_name") { + action.app_name = param.value.charAt(0).toUpperCase() + param.value.slice(1) + } + } + } + //const parents = getParentNodes(workflow, action) //console.log("PARENTS", key, parents) if (parents !== undefined && parents !== null && parents.length > 0) { @@ -219,6 +237,20 @@ const WorkflowValidationTimeline = (props) => { continue } + for (var branchkey in workflow.branches) { + const branch = workflow.branches[branchkey] + + // Checking for OUTBOUND branches from it. + // This will mean it's NOT the last node and is easy to visualize + if (branch.source_id !== trigger.id) { + continue + } + + trigger.order = 2 + } + + + // Just in case (: if (workflow.actions.find((element) => element.id === trigger.id) === undefined) { newactions.push(trigger) //workflow.actions.push(trigger) @@ -242,14 +274,19 @@ const WorkflowValidationTimeline = (props) => { }) // FIXME: Add other relevant items as well from subflows (?) - var nodecolor = "grey" - var branchcolor = "grey" + var nodecolor = grey + var branchcolor = grey var skipped = false var previousTools = false + // Use this variable to control visualization + //const showMiddle = false + // border: workflow.validation.valid ? `2px solid ${green}` : "1px solid rgba(255,255,255,0.4)", + var middleError = "" + var startBranchColor = "" return ( -
+
{relevantactions.map((action, index) => { action.result = {} @@ -274,13 +311,13 @@ const WorkflowValidationTimeline = (props) => { if (validate.result.success === true) { branchcolor = green } else { - branchcolor = "grey" + branchcolor = grey } } } else if (action.status === "SKIPPED") { - branchcolor = "grey" + branchcolor = grey } else { if (action.status === undefined) { branchcolor = green @@ -292,13 +329,13 @@ const WorkflowValidationTimeline = (props) => { previousTools = true if (startnodeId !== action.id) { - return null + //return null } } else { if (action.status === "SUCCESS") { nodecolor = green } else if (action.status === "SKIPPED") { - nodecolor = "grey" + nodecolor = grey } else { if (action.status === undefined) { nodecolor = green @@ -308,7 +345,7 @@ const WorkflowValidationTimeline = (props) => { } } } else { - nodecolor = "grey" + nodecolor = grey } if (action.status === "SKIPPED") { @@ -340,18 +377,44 @@ const WorkflowValidationTimeline = (props) => { } var founderror = "" + //console.log("WORKFLOW VALIDATION: ", workflow.validation) if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null) { const foundError = workflow.validation.errors.find((element) => element.action_id === action.id) if (foundError !== undefined) { founderror = foundError.error - nodecolor = yellow - branchcolor = yellow + nodecolor = red + branchcolor = red + } else { + //console.log("NO ERROR: ", action.id) + } + } + + if (!showMiddle && relevantactions.length > 2 && index > 0 && index === relevantactions.length - 2) { + if (founderror.length > 0) { + middleError += founderror+"\n" + } + + if (index === relevantactions.length-2 && relevantactions.length > 2) { + + const selectedIcon = middleError.length > 0 ? + + + + + + : null + + return ( + selectedIcon + ) + } else { + return null } } if (skipped && !lastitem) { - nodecolor = "grey" - branchcolor = "grey" + nodecolor = grey + branchcolor = grey } if (previousTools) { @@ -379,14 +442,60 @@ const WorkflowValidationTimeline = (props) => { } + var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3 + + if (nodecolor === green) { + branchcolor = green + } else if (nodecolor === yellow) { + branchcolor = yellow + } else if (nodecolor === red) { + branchcolor = red + } + + if (index === 0) { + startBranchColor = branchcolor + } + + if (lastitem && middleError.length === 0) { + branchcolor = startBranchColor + } + const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : "" + const appname = action.app_name.replaceAll('_', ' ').slice(0, 16) + + const chipBackground = nodecolor === green ? "rgba(2,203,112, 0.2)" : nodecolor === grey ? "#494949": "rgba(245,52,52,0.8)" + const chipColor = nodecolor === green ? "#02cb70" : nodecolor === grey ? "#CDCDCD" : "white" + + //const ballcolor = lastitem ? nodecolor : branchcolor + const ballcolor = branchcolor + const ballsize = 8 + const topMargin = 20 + + const chipStyle = { + height: 40, + minWidth: 125, + maxWidth: 125, + borderRadius: 50, + color: chipColor, + backgroundColor: chipBackground, + + //border: `2px solid ${chipBackground}`, + //backgroundColor: "rgba(0,0,0,0.0)", + } + + if (image === "") { + console.log("MISSING IMAGE: ", appname, image, action) + } return (
{lastitem ? -
+
+
+
+
: null} @@ -419,30 +528,36 @@ const WorkflowValidationTimeline = (props) => { : - {founderror.length > 0 ? founderror : `${action.app_name.replaceAll('_', ' ')}`} + {founderror.length > 0 ? founderror : `App: ${appname}`} } placement="top"> - - {image !== "" ? - - : null} - + + {image !== "" ? + + : null} + + } /> + } {lastitem ? null : -
+
+
+
+
}
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 050e139c..d166a9cf 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -2361,7 +2361,7 @@ If you're interested, please let me know a time that works for you, or set up a 1: "cloud_sync", 2: "priorities", 3: "billing", - 4: "branding", + 4: "partner", }; const setConfig = (event, inputValue) => { @@ -3753,7 +3753,7 @@ If you're interested, please let me know a time that works for you, or set up a > Edit Details /> Limits & Cloud Sync /> - Priorities /> + Notifications /> Billing & Stats /> Partner /> @@ -4383,9 +4383,11 @@ If you're interested, please let me know a time that works for you, or set up a + {logsLoading && ipSelected.length !== 0 ? (
- {logs.map((data, index) => ( + + + + + + {logs.map((data, index) => { + //console.log("LOG: ", data) + + return ( // redirect user to logs // using request id or trace id - ))} + )})} @@ -4567,6 +4601,11 @@ If you're interested, please let me know a time that works for you, or set up a onClick={() => { setLogsViewModal(true); setUserLogViewing(data); + + if (userLogViewing.login_info !== undefined && userLogViewing.login_info !== null && userLogViewing.login_info.length > 0) { + getLogs(userLogViewing.login_info[0].ip, userLogViewing.id) + setIpSelected(userLogViewing.login_info[0].ip); + } }} > {data.username} @@ -6077,7 +6116,7 @@ If you're interested, please let me know a time that works for you, or set up a
) : null; - const getLogs = async (ip, userId) => { + const getLogs = (ip, userId) => { setLogsLoading(true); console.log("logs loading: ", logsLoading); fetch(`${globalUrl}/api/v1/users/${userId}/audit?user_ip=${ip}`, { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index c6a1cb20..d95c0d9b 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -319,9 +319,12 @@ export function SetJsonDotnotation(jsonInput, inputKey) { return jsonInput; } -export const green = "#86c142"; +//export const green = "#86c142"; +export const green = "#02CB70" export const yellow = "#FECC00"; -export const red = "#ff3632"; +//export const red = "#ff3632"; +export const red = "#F53434"; +export const grey = "#b0b0b0"; export function removeParam(key, sourceURL) { if (sourceURL === undefined) { @@ -673,10 +676,16 @@ const releaseToConnectLabel = "Release to Connect" const loadAppConfig = (appId, select) => { if (appId === undefined || appId === null || appId.length === 0) { + console.log("No appId to load") + return + } + + if (appId === "integration") { return } if (loadedApps.includes(appId)) { + console.log("App already loaded: ", appId) return } @@ -699,8 +708,6 @@ const releaseToConnectLabel = "Release to Connect" if (responseJson.success === true && responseJson.app !== undefined && responseJson.app !== null && responseJson.app.length > 0) { // Base64 decode into json const foundapp = JSON.parse(atob(responseJson.app)) - console.log("Checked app: ", foundapp) - const selectedAppActions = selectedApp.actions === undefined || selectedApp.actions === null ? [] : selectedApp.actions if (foundapp.actions !== undefined && foundapp.actions !== null && foundapp.actions.length > selectedAppActions.length) { @@ -749,6 +756,7 @@ const releaseToConnectLabel = "Release to Connect" if (selectedApp.actions === undefined || selectedApp.actions === null || selectedApp.actions.length > 1) { return } else { + if (selectedApp.id !== undefined && selectedApp.id !== null && selectedApp.id.length > 0) { loadAppConfig(selectedApp.id, true) } @@ -1241,8 +1249,14 @@ const releaseToConnectLabel = "Release to Connect" }) .then((response) => { if (response.status !== 200) { - console.log("Status not 200 for setting app auth :O!"); - } + console.log("Status not 200 for setting app auth :O!"); + + if (response.status === 400) { + toast.error("Failed setting new auth. Please try again", { + "autoClose": true, + }) + } + } return response.json(); }) @@ -1253,8 +1267,6 @@ const releaseToConnectLabel = "Release to Connect" "autoClose": false, }) - - } else { if (refresh === true) { getAppAuthentication(true, true, true) @@ -2153,6 +2165,7 @@ const releaseToConnectLabel = "Release to Connect" }) .catch((error) => { setSavingState(0); + setExecutionRequestStarted(false) console.log("Save workflow error: ", error.toString()); toast.warn("Failed to save the workflow. Is the network down?") }); @@ -2346,6 +2359,7 @@ const releaseToConnectLabel = "Release to Connect" //toast(error.toString()); setExecutionRequestStarted(false) console.log("Execute workflow err: ", error.toString()); + toast.warn("Failed to run the workflow. Is the network down?") }); }) }; @@ -5092,6 +5106,10 @@ const releaseToConnectLabel = "Release to Connect" } */ + if (curapp !== undefined && curapp !== null && curapp.id !== undefined && curapp.id !== null && curapp.id.length > 0) { + loadAppConfig(curapp.id, true) + } + if (!curapp || curapp === undefined) { const tmpapp = { name: curaction.app_name, @@ -5753,8 +5771,10 @@ const releaseToConnectLabel = "Release to Connect" setLastSaved(false); const edge = event.target.data(); - //console.log("edge added: ", edge) + console.log("edge added: ", edge) if (edge.source === undefined && edge.target === undefined) { + console.log("Edge added without source or target") + return } @@ -5768,8 +5788,9 @@ const releaseToConnectLabel = "Release to Connect" const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { + console.log("Source or destination node is undefined or null: ", sourcenode, destinationnode) } else { - //console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) + console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) if (sourcenode.data("type") === "TRIGGER") { if (sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") { setTimeout(() => { @@ -5778,7 +5799,6 @@ const releaseToConnectLabel = "Release to Connect" (data) => data.data.source === edge.source && data.data.id !== edge.id ) - console.log("Node: ", targetedge) if (targetedge !== -1) { event.target.remove() @@ -5810,14 +5830,15 @@ const releaseToConnectLabel = "Release to Connect" ); if (targetnode !== -1) { if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow" || workflow.triggers[targetnode].app_name === "Shuffle Subflow") { + console.log("User Input or Shuffle Workflow") } else { - toast("Can't have triggers as target of branch"); - event.target.remove(); + toast("Can't have triggers as target of branch") + event.target.remove() } } const eventTarget = event.target.target() - //console.log("BUTTON ADDED! Find parent from: ", eventTarget) + console.log("BUTTON ADDED! Find parent from: ", eventTarget) if (eventTarget.data("isButton") === true) { const parentNode = cy.getElementById(eventTarget.data("attachedTo")) event.target.remove() @@ -5843,13 +5864,10 @@ const releaseToConnectLabel = "Release to Connect" } } - if ( - eventTarget.data("isDescriptor") === true || - eventTarget.data("type") === "COMMENT" - ) { + if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { console.log("Removing because of descriptor or comment") - event.target.remove(); - return; + event.target.remove() + return } targetnode = -1; @@ -5858,26 +5876,33 @@ const releaseToConnectLabel = "Release to Connect" // dest == source && source == dest // dest == dest && source == source // backend: check all children? to stop recursion + // var found = false; for (let branchkey in workflow.branches) { - if ( - workflow.branches[branchkey].destination_id === edge.source && - workflow.branches[branchkey].source_id === edge.target - ) { - toast("A branch in the opposite direction already exists"); - event.target.remove(); - found = true; - break; - } else if ( - workflow.branches[branchkey].destination_id === edge.target && - workflow.branches[branchkey].source_id === edge.source - ) { - //toast("That branch already exists"); - event.target.remove(); + if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { + toast("A branch in the opposite direction already exists") + event.target.remove() + found = true + break + } - found = true; - break; - } else if (edge.target === workflow.start) { + if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) { + + console.log("That branch already exists: ", workflow.branches[branchkey]) + const foundbranch = cy.getElementById(workflow.branches[branchkey].id) + if (foundbranch !== undefined && foundbranch !== null && foundbranch.data() !== undefined && foundbranch.data() !== null) { + console.log("Removing branch: ", foundbranch.data()) + + event.target.remove() + + found = true + break + } else { + console.log("Old branch didn't exist afterall. Remove.") + } + } + + if (edge.target === workflow.start) { targetnode = workflow.triggers.findIndex( (data) => data.id === edge.source ); @@ -5890,7 +5915,9 @@ const releaseToConnectLabel = "Release to Connect" found = true; } - } else if (edge.source === workflow.branches[branchkey].source_id) { + } + + if (edge.source === workflow.branches[branchkey].source_id) { // FIXME: Verify multi-target for triggers // 1. Check if destination exists // 2. Check if source is a trigger @@ -7522,7 +7549,7 @@ const releaseToConnectLabel = "Release to Connect" // Reset cytoscape nodes and branches if (cy !== undefined && cy !== null) { if (inputworkflow.actions !== undefined && inputworkflow.actions !== null && inputworkflow.actions.length > 0) { - cy.remove('*') + //cy.remove('*') } } @@ -9181,6 +9208,10 @@ const releaseToConnectLabel = "Release to Connect" const actionIndex = startIndex < 0 ? 0 : startIndex if (app.actions[actionIndex] === undefined || app.actions[actionIndex] === null) { + if (app.id !== undefined && app.id !== null) { + loadAppConfig(app.id, false) + } + console.log("No actions found for app: ", app) return } @@ -9409,7 +9440,7 @@ const releaseToConnectLabel = "Release to Connect" setHover(true) - if (app.actions !== undefined && app.actions !== null && app.actions.length === 1) { + if (app.actions !== undefined && (app.actions === null || app.actions.length === 1)) { console.log("HOVERING: ", app.id) loadAppConfig(app.id, false) } @@ -10295,6 +10326,10 @@ const releaseToConnectLabel = "Release to Connect" var handled = []; var results = []; + if (cy === undefined || cy === null) { + return [] + } + // maxiter = max amount of parent nodes to loop // also handles breaks if there are issues var iterations = 0; @@ -10934,6 +10969,10 @@ const releaseToConnectLabel = "Release to Connect" checked = workflow.auth_groups.includes(data.id) } + if (data === undefined || data === null || data.app_auths === undefined || data.app_auths === null || data.app_auths.length === 0) { + return null + } + return (
0 ) { console.log("WORKFLOW: ", e.target.value); - const startnode = e.target.value.actions.find((action) => action.id === e.target.value.start); + const startnode = e?.target?.value?.actions?.find((action) => action.id === e.target.value.start); if (startnode !== undefined && startnode !== null) { @@ -16643,9 +16682,17 @@ const releaseToConnectLabel = "Release to Connect" } const foundnode = cy.nodes().filter((node) => { - return node.data().label === word + const nodelabel = node.data("label") + if (nodelabel === undefined || nodelabel === null) { + return false + } + + console.log("Node: ", nodelabel.toLowerCase(), "Word: ", word.toLowerCase()) + return nodelabel.toLowerCase() === word.toLowerCase() }) + console.log("FOUND: ", foundnode) + if (foundnode === undefined || foundnode === null || foundnode.length === 0) { return } @@ -18986,7 +19033,7 @@ const releaseToConnectLabel = "Release to Connect" ) : null} - {executionData.workflow !== undefined && executionData.workflow !== null && executionData.status !== "EXECUTING" ? + {userdata.support === true && executionData.workflow !== undefined && executionData.workflow !== null && executionData.status !== "EXECUTING" ?
0) { // FIXME: Find image from cytoscape action @@ -20837,6 +20884,25 @@ const releaseToConnectLabel = "Release to Connect" />
{selectedApp.authentication.parameters.map((data, index) => { + // FIXME: Look for relevant fields in the action that may already be filled in with the same name + if (data.value === "" || data.value === null || data.value === undefined || data.name === "url") { + if (selectedAction !== undefined && selectedAction !== null && selectedAction.parameters !== undefined && selectedAction.parameters !== null) { + for (var fieldkey in selectedAction.parameters) { + const field = selectedAction.parameters[fieldkey] + if (field.name !== data.name) { + continue + } + + if (field.value !== undefined && field.value !== null && field.value.length > 0) { + data.value = field.value + data.autocomplete = true + break + } + } + } + } + + return (
@@ -21617,8 +21683,8 @@ const releaseToConnectLabel = "Release to Connect" // Should render if it's not the same as workflow.edited console.log("Clicked revision: ", newrevision) - setLastSaved(false) - setSelectedVersion(newrevision); + setLastSaved(false) + setSelectedVersion(newrevision); setWorkflow(newrevision) setSelectedAction({}); setSelectedApp({}) @@ -21638,18 +21704,19 @@ const releaseToConnectLabel = "Release to Connect" cy.removeListener("free"); cy.removeListener("cxttap"); - //cy.remove('*') setElements([]) - } - // Remove all edges + cy.remove('*') cy.edges().remove() cy.nodes().remove() + } + // Remove all cy nodes setTimeout(() => { + //toast("Running setupgraph with new revision. Actions: " + newrevision.actions.length) setupGraph(newrevision) - }, 100) + }, 250) // Re-adding cytoscape triggers diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index e9db7736..c9e0b218 100755 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -228,6 +228,8 @@ const parseCurl = (s) => { }; // Basically CRUD for each category + special +// These are already tracked in the shuffle/shuffle-shared/blobs.go file +// as backend should be used for managing this long-term export const appCategories = [ { "name": "Communication", diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 770e37c8..b79ad590 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -1542,12 +1542,15 @@ const Apps = (props) => { flex: 1, padding: 15, margin: 10, + paddingTop: 25, backgroundColor: hover ? theme.palette.surfaceColor : "transparent", border: hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", cursor: hover ? "pointer" : "default", textAlign: "center", minHeight: 150, maxHeight: 150, + + borderRadius: theme.palette.borderRadius, }} > {icon} @@ -1569,7 +1572,7 @@ const Apps = (props) => {
{ setOpenApiModal(true) }} diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx index 4d8dc1bf..8dbc019a 100644 --- a/frontend/src/views/RunWorkflow.jsx +++ b/frontend/src/views/RunWorkflow.jsx @@ -2,14 +2,17 @@ import React, {useState, useEffect} from 'react'; import ReactDOM from "react-dom" -import { ToastContainer, toast } from "react-toastify" -import { useInterval } from "react-powerhooks"; -import { makeStyles } from '@mui/material/styles'; +import { green, yellow, red, grey} from "./AngularWorkflow.jsx"; +import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx"; import { useNavigate, Link, useParams } from "react-router-dom"; -import {isMobile} from "react-device-detect"; -import theme from '../theme.jsx'; import { validateJson, GetIconInfo } from "./Workflows.jsx"; -import { green, yellow } from "./AngularWorkflow.jsx"; +import EditWorkflow from "../components/EditWorkflow.jsx" +import { ToastContainer, toast } from "react-toastify" +import { makeStyles } from '@mui/material/styles'; +import { useInterval } from "react-powerhooks"; +import { isMobile } from "react-device-detect"; +import Markdown from "react-markdown"; +import theme from '../theme.jsx'; import { Tooltip, @@ -21,6 +24,10 @@ import { Paper, Typography, Divider, + + Dialog, + DialogTitle, + DialogContent, } from '@mui/material'; import { @@ -35,14 +42,15 @@ const hrefStyle = { const bodyDivStyle = { margin: "auto", - marginTop: 100, width: isMobile? "100%":"500px", position: "relative", + + marginTop: 25, } const RunWorkflow = (defaultprops) => { - const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; + const { globalUrl, userdata, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; let navigate = useNavigate(); const [_, setUpdate] = useState(""); // Used to force rendring, don't remove @@ -58,15 +66,16 @@ const RunWorkflow = (defaultprops) => { const [apps, setApps] = React.useState([]); const [buttonClicked, setButtonClicked] = React.useState(""); const [foundSourcenode, setFoundSourcenode] = React.useState(undefined); + const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false) + const [sharingOpen, setSharingOpen] = React.useState(false) const boxStyle = { color: "white", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", + padding: 50, backgroundColor: theme.palette.surfaceColor, marginBottom: 150, + borderRadius: 25, + minHeight: 500, } const params = useParams(); @@ -74,7 +83,7 @@ const RunWorkflow = (defaultprops) => { props.match = {} props.match.params = params - const defaultTitle = "Run Workflow" + const defaultTitle = workflow.name !== undefined ? workflow.name : "Run Workflow" if (document != undefined && document.title != defaultTitle) { document.title = defaultTitle } @@ -96,16 +105,42 @@ const RunWorkflow = (defaultprops) => { return true } + const saveWorkflow = (workflow) => { + const url = `${globalUrl}/api/v1/workflows/${workflow.id}` + fetch(url, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(workflow), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + toast.success("Saved workflow") + }) + .catch((error) => { + toast.error("Save workflow error: " + error) + }); + + } const getApps = () => { - fetch(globalUrl + "/api/v1/apps", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) + fetch(globalUrl + "/api/v1/apps", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); @@ -829,52 +864,75 @@ const RunWorkflow = (defaultprops) => { const basedata =
-
{onSubmit(e)}} style={{margin: "15px 15px 15px 15px"}}> - - {workflow.name} + {workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? +
+ + {workflow.input_markdown} + +
+ : null} - - {organization} - - + {onSubmit(e)}} style={{margin: "50px 0px 15px 0px",}}> + {workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? null : +
+ {workflow.name} - {disabledButtons && message.length > 0 ? null : - - {message} + + {organization} - } + - {answer !== undefined && answer !== null ? null : - {workflow.name} - } - - {workflowQuestion.length > 0 ? -
- - {workflowQuestion} + {disabledButtons && message.length > 0 ? null : + + {message} + } + + {answer !== undefined && answer !== null ? null : + {workflow.name} + } + + {workflowQuestion.length > 0 ? +
+ + {workflowQuestion} + +
+ : null} +
- : null} - + } + {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
{workflow.input_questions.map((question, index) => { @@ -988,7 +1046,7 @@ const RunWorkflow = (defaultprops) => {
} @@ -1018,12 +1076,79 @@ const RunWorkflow = (defaultprops) => {
+ + // const isCorrectOrg = userdata.active_org.id === undefined || userdata.active_org.id === null || workflow.org_id === null || workflow.org_id === undefined || workflow.org_id.length === 0 || userdata.active_org.id === workflow.org_id const loadedCheck = isLoaded ?
+ {editWorkflowModalOpen === true ? + + : null} + + { + setSharingOpen(false); + }} + PaperProps={{ + style: { + color: "white", + minWidth: isMobile ? "90%" : 400, + maxWidth: isMobile ? "90%" : 400, + minHeight: 350, + paddingTop: 25, + paddingLeft: 50, + //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, + //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, + }, + }} + > + + Share Landingpage + + + + + + {isLoggedIn && userdata?.active_org?.id === workflow?.org_id ? +
+ + +
+ : null} {basedata}
:
+
return ( diff --git a/frontend/src/views/Usecases.jsx b/frontend/src/views/Usecases.jsx index 16c20375..afb978b5 100644 --- a/frontend/src/views/Usecases.jsx +++ b/frontend/src/views/Usecases.jsx @@ -21,6 +21,7 @@ import { Paper, Chip, Checkbox, + Fade, } from "@mui/material"; import { @@ -1741,9 +1742,11 @@ const Dashboard = (props) => {
const dataWrapper = -
- {data} -
+ +
+ {data} +
+
return dataWrapper diff --git a/frontend/src/views/Welcome.jsx b/frontend/src/views/Welcome.jsx index 991cd898..12811456 100644 --- a/frontend/src/views/Welcome.jsx +++ b/frontend/src/views/Welcome.jsx @@ -32,7 +32,7 @@ const Welcome = (props) => { const [inputUsecase, setInputUsecase] = useState({}); const [frameworkData, setFrameworkData] = useState(undefined); const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined); - const [activeStep, setActiveStep] = React.useState(1); + const [activeStep, setActiveStep] = React.useState(0); const [apps, setApps] = React.useState([]); const [defaultSearch, setDefaultSearch] = React.useState("") const [selectionOpen, setSelectionOpen] = React.useState(false) @@ -285,6 +285,9 @@ const Welcome = (props) => { } setActiveStep(foundTab-1) + + //setRenderDone( + } } }, []) @@ -398,6 +401,7 @@ const Welcome = (props) => { discoveryWrapper={discoveryWrapper} setDiscoveryWrapper={setDiscoveryWrapper} appFramework={frameworkData} + setAppFramework={setFrameworkData} getFramework={getFramework} steps={steps} skipped={skipped} @@ -462,7 +466,7 @@ const Welcome = (props) => { OR */}
- { + { if (isCloud) { ReactGA.event({ category: "welcome",