From 858d18c7dceaa8594ea88a13aeaf81f76ec2b5a4 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 15 Feb 2024 18:53:39 +0100 Subject: [PATCH 1/4] A lot of things --- backend/app_sdk/app_base.py | 2 - backend/go-app/walkoff.go | 4 +- frontend/src/components/AppFramework.jsx | 263 ++++++++------- frontend/src/components/AppSearchButtons.jsx | 12 +- frontend/src/components/ConfigureWorkflow.jsx | 5 +- frontend/src/components/EditWorkflow.jsx | 4 +- frontend/src/components/ParsedAction.jsx | 56 ++-- frontend/src/components/RuntimeDebugger.jsx | 18 +- .../src/components/WorkflowTemplatePopup.jsx | 62 +++- frontend/src/views/Admin.jsx | 8 +- frontend/src/views/AngularWorkflow.jsx | 314 ++++++++++++++---- frontend/src/views/Dashboard.jsx | 21 +- 12 files changed, 512 insertions(+), 257 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 51690770..1ae7049c 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1914,7 +1914,6 @@ class AppBase: c_parentheses = parse_nested_param(parse_string, 0)[0] match_string = re.escape(c_parentheses) custom_casting = re.findall(fr"({wrapper_group})\({match_string}", parse_string) - print("[DEBUG] In ELSE: %s" % custom_casting) # check if a wrapper was found if len(custom_casting) != 0: inner_result = parse_type(c_parentheses, custom_casting[0]) @@ -2093,7 +2092,6 @@ class AppBase: except IndexError: newvalue, is_loop = (tmpitem, parsersplit[outercnt+1:]) else: - #print("[INFO] In ELSE - handling %s and %s" % (firstitem, seconditem)) if isinstance(firstitem, str): if firstitem.lower() == "max" or firstitem.lower() == "last" or firstitem.lower() == "end": firstitem = len(basejson)-1 diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index c1e71f2f..a9d72774 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -552,7 +552,9 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { ctx := context.Background() workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId) if err != nil { - log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err) + if len(actionResult.ExecutionId) > 0 { + log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err) + } resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`))) return diff --git a/frontend/src/components/AppFramework.jsx b/frontend/src/components/AppFramework.jsx index 383553fd..fbdf7fa0 100644 --- a/frontend/src/components/AppFramework.jsx +++ b/frontend/src/components/AppFramework.jsx @@ -40,12 +40,12 @@ cytoscape.use(edgehandles); export const findSpecificApp = (framework, inputcategory) => { // Get the frameworkinfo for the org and fill in if (framework === undefined || framework === null) { - console.log("findSpecificApp: framework is null") + //console.log("findSpecificApp: framework is null") return null } if (inputcategory === undefined || inputcategory === null) { - console.log("findSpecificApp: category is null") + //console.log("findSpecificApp: category is null") return null } @@ -118,8 +118,8 @@ export const findSpecificApp = (framework, inputcategory) => { } return { - name: "EDR :default", - large_image: parsedDatatypeImages["EDR & AV"], + name: "IAM :default", + large_image: parsedDatatypeImages["IAM"], count: 0, description: "", id: "", @@ -654,7 +654,7 @@ const AppFramework = (props) => { const handleLoadNextSuggestion = (frameworkData) => { - console.log("Should check for next apps to load from App suggestion model") + //console.log("Should check for next apps to load from App suggestion model") //fetch(globalUrl + "/api/v1/workflows/usecases", { //credentials: "include", //cors: "no-cors", @@ -720,7 +720,7 @@ const AppFramework = (props) => { } const showRecommendations = (changed, frameworkData) => { - console.log("Inside recommendation loader") + //console.log("Inside recommendation loader") setChangedApp(changed) handleLoadNextSuggestion(frameworkData) @@ -761,7 +761,7 @@ const AppFramework = (props) => { //console.log("APptype, changed, framework: ", apptype.toLowerCase(), alternativeChanged.toLowerCase(), changed.toLowerCase(), frameworkData) if (changed.toLowerCase() === apptype.toLowerCase() || changed.toLowerCase().includes(apptype.toLowerCase()) || alternativeChanged.toLowerCase() === apptype.toLowerCase() || alternativeChanged.toLowerCase().includes(apptype.toLowerCase())) { potential = true - console.log("Potential: !", apptype) + //console.log("Potential: !", apptype) if (frameworkData[apptype] !== undefined && frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) { usecase.items[itemtype].app = frameworkData[apptype] @@ -819,10 +819,10 @@ const AppFramework = (props) => { continue } } else { - console.log("No usecase to try to match it to (usecase.usecase_references in UsecaseSearch)") + //console.log("No usecase to try to match it to (usecase.usecase_references in UsecaseSearch)") } - console.log("Usecase: ", usecase) + //console.log("Usecase: ", usecase) if (matches.length === usecase.items.length) { usecase.color = "#c51152" usecase.type = usecaseTypes[key].name @@ -833,8 +833,8 @@ const AppFramework = (props) => { } // FIXME: Check if a usecase has already been handled - console.log("") - console.log("GOT USECASES: ", showusecases) + //console.log("") + //console.log("GOT USECASES: ", showusecases) // FIXME: Just showing one usecase at a time for now if (showusecases.length > 0) { @@ -894,38 +894,37 @@ const AppFramework = (props) => { parsedUsecase.process.push(edge.data) } - fetch(globalUrl + "/api/v1/workflows/usecases", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(parsedUsecase), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for framework!"); - } + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(parsedUsecase), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } - return response.json(); - }) - .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.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Failed updating: " + responseJson.reason) } else { - toast("Updated usecase.") + toast("Failed to update framework for your org.") } - }) - .catch((error) => { - toast(error.toString()); - //setFrameworkLoaded(true) - }) - } + } else { + toast("Updated usecase.") + } + }) + .catch((error) => { + toast(error.toString()); + }) + } const activateApp = (appid) => { fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", { @@ -957,45 +956,54 @@ const AppFramework = (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: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(data), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for framework!"); - } + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } - return response.json(); - }) - .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.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Failed getting appframework: " + responseJson.reason) + } else { + toast("Failed to update framework for your org.") - } - } + } + } else { + if (data.id === "remove") { + frameworkData[data.type] = {} + frameworkData[data.type.toLowerCase()] = {} + } else { + frameworkData[data.type] = data + frameworkData[data.type.toLowerCase()] = data + } - //setFrameworkLoaded(true) - //setFrameworkData(responseJson) - }) - .catch((error) => { - toast(error.toString()); - //setFrameworkLoaded(true) - }) - } + setDiscoveryData({}) + if (setFrameworkData !== undefined) { + setFrameworkData(frameworkData) + } + } + }) + .catch((error) => { + toast(error.toString()); + //setFrameworkLoaded(true) + }) + } useEffect(() => { if (!window.location.pathname.includes("usecases")) { @@ -1058,7 +1066,6 @@ const AppFramework = (props) => { frameworkData[keys[key]] = submitValue } - console.log("Frameworkdata: ", frameworkData) setFrameworkData(frameworkData) if (discoveryData.large_image !== undefined && discoveryData.large_image !== null && discoveryData.large_image.includes("storage.googleapis.com")) { @@ -1076,8 +1083,6 @@ const AppFramework = (props) => { const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - - const imgSize = 50; var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData @@ -1263,7 +1268,7 @@ const AppFramework = (props) => { var parsedStyle2 = { "ghost": "yes", - "border-width": "7px", + "border-width": "7px", } /* @@ -1399,7 +1404,6 @@ const AppFramework = (props) => { const onNodeSelect = (event) => { var data = event.target.data(); - console.log("Node: ", data) if (data.id === "SHUFFLE") { event.target.unselect() return @@ -1465,6 +1469,15 @@ const AppFramework = (props) => { const foundMiddleImage = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : '/images/Shuffle_logo.png' + const siemcheck = parsedFrameworkData.SIEM.large_image === undefined || (parsedFrameworkData.SIEM.name !== undefined && parsedFrameworkData.SIEM.name !== null && parsedFrameworkData.SIEM.name.includes(":default")) + const iamcheck = parsedFrameworkData.IAM.large_image === undefined || (parsedFrameworkData.IAM.name !== undefined && parsedFrameworkData.IAM.name !== null && parsedFrameworkData.IAM.name.includes(":default")) + const casescheck = parsedFrameworkData.Cases.large_image === undefined || (parsedFrameworkData.Cases.name !== undefined && parsedFrameworkData.Cases.name !== null && parsedFrameworkData.Cases.name.includes(":default")) + const assetscheck = parsedFrameworkData.Assets.large_image === undefined || (parsedFrameworkData.Assets.name !== undefined && parsedFrameworkData.Assets.name !== null && parsedFrameworkData.Assets.name.includes(":default")) + const intelcheck = parsedFrameworkData.Intel.large_image === undefined || (parsedFrameworkData.Intel.name !== undefined && parsedFrameworkData.Intel.name !== null && parsedFrameworkData.Intel.name.includes(":default")) + const commscheck = parsedFrameworkData.Comms.large_image === undefined || (parsedFrameworkData.Comms.name !== undefined && parsedFrameworkData.Comms.name !== null && parsedFrameworkData.Comms.name.includes(":default")) + const edrcheck = parsedFrameworkData["EDR & AV"].large_image === undefined || (parsedFrameworkData["EDR & AV"].name !== undefined && parsedFrameworkData["EDR & AV"].name !== null && parsedFrameworkData["EDR & AV"].name.includes(":default")) + const networkcheck = parsedFrameworkData.Network.large_image === undefined || (parsedFrameworkData.Network.name !== undefined && parsedFrameworkData.Network.name !== null && parsedFrameworkData.Network.name.includes(":default")) + const fontSize = `${12*scale}px` const defaultSize = `${85*scale}px` @@ -1484,11 +1497,11 @@ const AppFramework = (props) => { name: parsedFrameworkData.Cases.name === undefined ? "" : parsedFrameworkData.Cases.name, description: parsedFrameworkData.Cases.description === undefined ? "" : parsedFrameworkData.Cases.description, app_id: parsedFrameworkData.Cases.id === undefined ? "" : parsedFrameworkData.Cases.id, - text_margin_y: parsedFrameworkData.Cases.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.Cases.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.Cases.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize, + text_margin_y: casescheck ? textMarginDefault : textMarginImage, + margin_x: casescheck ? `${32*scale}px` : "0px", + margin_y: casescheck ? `${19*scale}px` : `0px`, + width: casescheck ? iconSize : defaultSize, + height: casescheck ? iconSize : defaultSize, large_image: parsedFrameworkData.Cases.large_image === undefined ? parsedDatatypeImages["CASES"] : parsedFrameworkData.Cases.large_image, label: securityFramework[0].text.toUpperCase(), @@ -1501,6 +1514,7 @@ const AppFramework = (props) => { } }, { + group: "nodes", data: { font_size: fontSize, @@ -1512,11 +1526,11 @@ const AppFramework = (props) => { name: parsedFrameworkData.IAM.name === undefined ? "" : parsedFrameworkData.IAM.name, description: parsedFrameworkData.IAM.description === undefined ? "" : parsedFrameworkData.IAM.description, app_id: parsedFrameworkData.IAM.id === undefined ? "" : parsedFrameworkData.IAM.id, - text_margin_y: parsedFrameworkData.IAM.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.IAM.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.IAM.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize, + text_margin_y: iamcheck ? textMarginDefault : textMarginImage, + margin_x: iamcheck ? `${32*scale}px` : "0px", + margin_y: iamcheck ? `${19*scale}px` : `0px`, + width: iamcheck ? iconSize : defaultSize, + height: iamcheck ? iconSize : defaultSize, large_image: parsedFrameworkData.IAM.large_image === undefined ? parsedDatatypeImages["IAM"] : parsedFrameworkData.IAM.large_image, label: securityFramework[3].text.toUpperCase(), @@ -1540,11 +1554,11 @@ const AppFramework = (props) => { name: parsedFrameworkData.Assets.name === undefined ? "" : parsedFrameworkData.Assets.name, description: parsedFrameworkData.Assets.description === undefined ? "" : parsedFrameworkData.Assets.description, app_id: parsedFrameworkData.Assets.id === undefined ? "" : parsedFrameworkData.Assets.id, - text_margin_y: parsedFrameworkData.Assets.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.Assets.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.Assets.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize, + text_margin_y: assetscheck ? textMarginDefault : textMarginImage, + margin_x: assetscheck ? `${32*scale}px` : "0px", + margin_y: assetscheck ? `${19*scale}px` : `0px`, + width: assetscheck ? iconSize : defaultSize, + height: assetscheck ? iconSize : defaultSize, large_image: parsedFrameworkData.Assets.large_image === undefined ? parsedDatatypeImages["ASSETS"] : parsedFrameworkData.Assets.large_image, label: securityFramework[2].text.toUpperCase(), @@ -1568,11 +1582,11 @@ const AppFramework = (props) => { name: parsedFrameworkData.Intel.name === undefined ? "" : parsedFrameworkData.Intel.name, description: parsedFrameworkData.Intel.description === undefined ? "" : parsedFrameworkData.Intel.description, app_id: parsedFrameworkData.Intel.id === undefined ? "" : parsedFrameworkData.Intel.id, - text_margin_y: parsedFrameworkData.Intel.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.Intel.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.Intel.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize, + text_margin_y: intelcheck ? textMarginDefault : textMarginImage, + margin_x: intelcheck ? `${32*scale}px` : "0px", + margin_y: intelcheck ? `${19*scale}px` : `0px`, + width: intelcheck ? iconSize : defaultSize, + height: intelcheck ? iconSize : defaultSize, large_image: parsedFrameworkData.Intel.large_image === undefined ? parsedDatatypeImages["INTEL"] : parsedFrameworkData.Intel.large_image, label: securityFramework[4].text.toUpperCase(), @@ -1596,11 +1610,11 @@ const AppFramework = (props) => { name: parsedFrameworkData.Comms.name === undefined ? "" : parsedFrameworkData.Comms.name, description: parsedFrameworkData.Comms.description === undefined ? "" : parsedFrameworkData.Comms.description, app_id: parsedFrameworkData.Comms.id === undefined ? "" : parsedFrameworkData.Comms.id, - text_margin_y: parsedFrameworkData.Comms.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.Comms.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.Comms.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize, + text_margin_y: commscheck ? textMarginDefault : textMarginImage, + margin_x: commscheck ? `${32*scale}px` : "0px", + margin_y: commscheck ? `${19*scale}px` : `0px`, + width: commscheck ? iconSize : defaultSize, + height: commscheck ? iconSize : defaultSize, large_image: parsedFrameworkData.Comms.large_image === undefined ? parsedDatatypeImages["COMMS"] : parsedFrameworkData.Comms.large_image, label: securityFramework[5].text.toUpperCase(), @@ -1624,11 +1638,11 @@ const AppFramework = (props) => { name: parsedFrameworkData["EDR & AV"].name === undefined ? "" : parsedFrameworkData["EDR & AV"].name, description: parsedFrameworkData["EDR & AV"].description === undefined ? "" : parsedFrameworkData["EDR & AV"].description, app_id: parsedFrameworkData["EDR & AV"].id === undefined ? "" : parsedFrameworkData["EDR & AV"].id, - text_margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize, + text_margin_y: edrcheck ? textMarginDefault : textMarginImage, + margin_x: edrcheck ? `${32*scale}px` : "0px", + margin_y: edrcheck ? `${19*scale}px` : `0px`, + width: edrcheck ? iconSize : defaultSize, + height: edrcheck ? iconSize : defaultSize, large_image: parsedFrameworkData["EDR & AV"].large_image === undefined ? parsedDatatypeImages["EDR & AV"] : parsedFrameworkData["EDR & AV"].large_image, label: securityFramework[7].text.toUpperCase(), @@ -1652,11 +1666,11 @@ const AppFramework = (props) => { name: parsedFrameworkData.Network.name === undefined ? "" : parsedFrameworkData.Network.name, description: parsedFrameworkData.Network.description === undefined ? "" : parsedFrameworkData.Network.description, app_id: parsedFrameworkData.Network.id === undefined ? "" : parsedFrameworkData.Network.id, - text_margin_y: parsedFrameworkData.Network.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.Network.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.Network.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize, + text_margin_y: networkcheck ? textMarginDefault : textMarginImage, + margin_x: networkcheck ? `${32*scale}px` : "0px", + margin_y: networkcheck ? `${19*scale}px` : `0px`, + width: networkcheck ? iconSize : defaultSize, + height: networkcheck ? iconSize : defaultSize, large_image: parsedFrameworkData.Network.large_image === undefined ? parsedDatatypeImages["NETWORK"] : parsedFrameworkData.Network.large_image, label: securityFramework[6].text.toUpperCase(), id: securityFramework[6].text.toUpperCase(), @@ -1679,11 +1693,11 @@ const AppFramework = (props) => { name: parsedFrameworkData.SIEM.name === undefined ? "" : parsedFrameworkData.SIEM.name, description: parsedFrameworkData.SIEM.description === undefined ? "" : parsedFrameworkData.SIEM.description, app_id: parsedFrameworkData.SIEM.id === undefined ? "" : parsedFrameworkData.SIEM.id, - text_margin_y: parsedFrameworkData.SIEM.large_image === undefined ? textMarginDefault : textMarginImage, - margin_x: parsedFrameworkData.SIEM.large_image === undefined ? `${32*scale}px` : "0px", - margin_y: parsedFrameworkData.SIEM.large_image === undefined ? `${19*scale}px` : `0px`, - width: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize, - height: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize, + text_margin_y: siemcheck ? textMarginDefault : textMarginImage, + margin_x: siemcheck ? `${32*scale}px` : "0px", + margin_y: siemcheck ? `${19*scale}px` : `0px`, + width: siemcheck ? iconSize : defaultSize, + height: siemcheck ? iconSize : defaultSize, large_image: parsedFrameworkData.SIEM.large_image === undefined ? parsedDatatypeImages["SIEM"] : parsedFrameworkData.SIEM.large_image, label: securityFramework[1].text.toUpperCase(), id: securityFramework[1].text.toUpperCase(), @@ -1988,6 +2002,7 @@ const AppFramework = (props) => { const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color return (
+ {/*
{ inputSearch={changedApp} apps={apps} /> -
- {injectedApps.map((apps, appindex) => { +
+ */} + + {/*injectedApps.map((apps, appindex) => { var categoryTop = 100 var categoryLeft = 100 @@ -2028,8 +2045,11 @@ const AppFramework = (props) => { return null } + const scale = 0.9 + const offsetTop = -70 + const offsetLeft = 0 return ( -
+
{apps.map((app, appIndex) => { return ( { } if (setFrameworkData !== undefined) { - console.log("Setting frameworkdata") // Find discoveryData.id var keys = [] for (const [key, value] of Object.entries(frameworkData)) { @@ -2088,7 +2107,7 @@ const AppFramework = (props) => { })}
) - })} + })*/} {showOptions === false ? null :
diff --git a/frontend/src/components/AppSearchButtons.jsx b/frontend/src/components/AppSearchButtons.jsx index 553b7e41..262843ad 100644 --- a/frontend/src/components/AppSearchButtons.jsx +++ b/frontend/src/components/AppSearchButtons.jsx @@ -37,7 +37,7 @@ import { } from '@mui/material'; const AppSearchButtons = (props) => { - const { userdata, globalUrl, appFramework, moreButton, 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, getAppFramework, } = props const ref = useRef() let navigate = useNavigate(); @@ -48,8 +48,6 @@ const AppSearchButtons = (props) => { useEffect(() => { if (newSelectedApp !== undefined && setMissing != undefined) { - console.log("AppSearchButtons: setMissing is defined!") - const submitAppFramework = { "description": newSelectedApp.description, "id": newSelectedApp.objectID, @@ -60,11 +58,17 @@ const AppSearchButtons = (props) => { setFrameworkItem(submitAppFramework) setMissing(newSelectedApp) + + if (getAppFramework !== undefined) { + setTimeout(() => { + getAppFramework() + }, 1000) + } } }, [newSelectedApp]) if (appType === undefined) { - console.log("Apptype is required in AppSearchButtons") + //console.log("Apptype is required in AppSearchButtons") return null; } diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 81e9fd3d..ef727fde 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -77,10 +77,8 @@ const ConfigureWorkflow = (props) => { const [checkStarted, setCheckStarted] = React.useState(false); useEffect(() => { - console.log("Required actions: ", requiredActions) + //console.log("Configure Workflow: Required actions: ", requiredActions) if (requiredActions.length === 0) { - console.log("No more actions? Set parent to done?") - if (setConfigurationFinished !== undefined) { setConfigurationFinished(true) } @@ -731,6 +729,7 @@ const ConfigureWorkflow = (props) => { parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " "); + console.log("AUTH Action: ", action) return ( { const newWorkflow = isEditing === true ? false : true const priority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) - console.log("PRIO: ", priority) - var upload = ""; var total_count = 0 @@ -485,7 +483,7 @@ const EditWorkflow = (props) => { - +
} - {(missingSource !== undefined || missingDestination !== undefined) ? + {(appSetupDone === false && missingSource !== undefined || missingDestination !== undefined) ? - {"Find relevevant Apps for this Usecase"} + {"Find relevant Apps for this Usecase"} : null} @@ -491,6 +525,8 @@ const WorkflowTemplatePopup = (props) => { AppImage={missingSource.image} setMissing={setMissingSource} + + getAppFramework={getAppFramework} />
: null} @@ -505,6 +541,8 @@ const WorkflowTemplatePopup = (props) => { AppImage={missingDestination.image} setMissing={setMissingDestination} + + getAppFramework={getAppFramework} /> : null} diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index f653bd2c..39e7fa25 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -4406,7 +4406,7 @@ If you're interested, please let me know a time that works for you, or set up a /> setDefaultEnvironment(environment)} color="primary" > diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 2f461690..fc7aae3c 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -13362,6 +13362,67 @@ const AngularWorkflow = (defaultprops) => {

{workflow.name}

+ + {workflow.public === true || 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 ? null : + + Warning: Change { + toast("Changing to correct organization. Please wait a few seconds.") + + localStorage.setItem("globalUrl", ""); + localStorage.setItem("getting_started_sidebar", "open"); + fetch(`${globalUrl}/api/v1/orgs/${workflow.org_id}/change`, { + mode: "cors", + credentials: "include", + crossDomain: true, + method: "POST", + body: JSON.stringify({"org_id": workflow.org_id}), + 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) { + console.log("In here?") + 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 { + if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) { + toast(responseJson.reason); + } else { + toast("Failed changing org. Try again or contact support@shuffler.io if this persists."); + } + } + }) + .catch((error) => { + console.log("error changing: ", error); + //removeCookie("session_token", {path: "/"}) + }) + + + + }} + >Active Organization to edit this Workflow. + + }
{parentWorkflows.slice(0,5).map((wf, index) => { @@ -13740,9 +13801,64 @@ const AngularWorkflow = (defaultprops) => {
: null + + const RightsideBar = () => { const [hovered, setHovered] = useState(false) + useEffect(() => { + const handleKeyDown = (event) => { + if ((event.metaKey || event.ctrlKey) && event.key === '/') { + event.preventDefault(); // Prevent default browser behavior (like opening search bar) + if (!workflow.public && !executionRequestStarted) { + executeWorkflow(executionText, workflow.start, lastSaved); + } + } + if ((event.ctrlKey || event.metaKey) && event.key === "'") { + // Check if Ctrl (Windows/Linux) or Command (Mac) key is pressed along with '/' + if (!workflow.public && !executionModalOpen) { + setExecutionModalOpen(true); + getWorkflowExecution(props.match.params.key, ""); + } else if (!workflow.public && executionModalOpen) { + setExecutionModalOpen(false); + } + } + + if ((event.ctrlKey || event.metaKey) && event.key === "]") { + console.log("Show workflow revisions key pressed") + if (!workflow.public) { + setShowWorkflowRevisions(true) + setSelectedRevision(workflow) + //setOriginalWorkflow(workflow) + } + } + + if (( event.ctrlKey || event.metaKey ) && event.key === ";") { + if (!workflow.public && executionModalOpen) { + getWorkflowExecution(props.match.params.key, ""); + } + } + + if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { + console.log("Shift key pressed") + if (!workflow.public && executionModalOpen) { + setExecutionRunning(false); + stop() + const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; + const newitem = removeParam("execution_id", cursearch); + navigate(curpath + newitem) + setExecutionModalView(0); + } + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [executeWorkflow, executionText, workflow, lastSaved, executionRequestStarted]); + return (
{ ) : ( - +
+ + { ) : (
- - { - setExecutionRunning(false); - stop(); - getWorkflowExecution(props.match.params.key, ""); - setExecutionModalView(0); - setLastExecution(executionData.execution_id); - }} + + - { - setExecutionRunning(false); - stop() - }} - > - - -

{ - const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; - const newitem = removeParam("execution_id", cursearch); - navigate(curpath + newitem) - setExecutionRunning(false); - stop() - }} - > - See more runs -

-
-
+ { + setExecutionRunning(false); + stop(); + getWorkflowExecution(props.match.params.key, ""); + setExecutionModalView(0); + setLastExecution(executionData.execution_id); + }} + > + { + setExecutionRunning(false); + stop() + }} + > + + + +

{ + const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; + const newitem = removeParam("execution_id", cursearch); + navigate(curpath + newitem) + setExecutionRunning(false); + stop() + }} + > + See more runs +

+
+
+ { color="primary" title="Explore logs for the workflow" placement="top" - style={{ zIndex: 50000 }} + style={{ zIndex: 50000, }} >
@@ -1713,12 +1719,15 @@ const Dashboard = (props) => { {treeKeys.length > 0 ? From d2b9552dad286ff270edeb43317acfd18f67bd52 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 15 Feb 2024 18:53:54 +0100 Subject: [PATCH 2/4] More settings/admin fixes --- frontend/src/views/Admin.jsx | 29 +++++++------ frontend/src/views/SettingsPage.jsx | 65 +++++++++++++++++++++++++++-- frontend/src/views/Workflows.jsx | 2 +- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 39e7fa25..7bdc2fb5 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -3415,24 +3415,26 @@ If you're interested, please let me know a time that works for you, or set up a // using request id or trace id - diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index 2b0c1ebd..be99104c 100755 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -13,6 +13,11 @@ import { //import { useAlert import { ToastContainer, toast } from "react-toastify" +import { FileCopy, Visibility, VisibilityOff } from "@mui/icons-material"; +import IconButton from "@mui/material/IconButton"; +import { Tooltip } from "@mui/material"; + + const Settings = (props) => { const { globalUrl, isLoaded, userdata, setUserData } = props; //const alert = useAlert(); @@ -44,6 +49,17 @@ const Settings = (props) => { const [userSettings, setUserSettings] = useState({}); + const [showApiKey, setShowApiKey] = useState(false); + const [apiKeyCopied, setApiKeyCopied] = useState(false); + + const handleCopyApiKey = () => { + navigator.clipboard.writeText(userSettings.apikey); + setApiKeyCopied(true); + setTimeout(() => { + setApiKeyCopied(false); + }, 2000); + } + /* const [userdata.eth_info, setEthInfo] = useState(userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0 ? userdata.eth_info : { "account": "", @@ -467,7 +483,7 @@ const Settings = (props) => { > What is the API key used for? - { id="standard-required" margin="normal" variant="outlined" - /> + /> */} + + + setShowApiKey(!showApiKey)} + > + {showApiKey ? : } + + + + + + + + + ), + }} + color="primary" + value={showApiKey ? userSettings.apikey : '*'.repeat(36)} // Show API key if showApiKey is true, else show asterisks + required + disabled + fullWidth + placeholder="APIKEY" + id="standard-required" + margin="normal" + variant="outlined" + />

{passwordFormMessage}

- -

Creator Incentive Program

+ {isCloud && ( + <> + +

Creator Incentive Program

+ + )} +
{isCloud ? diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 0cff5f0f..fdf23604 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -924,7 +924,7 @@ const Workflows = (props) => { >
- Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId).name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}?
+ Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId)?.name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}?
Other workflows relying on {selectedWorkflowIndexes.length > 0 ? "them" : "it"} one will stop working
From 51935b96a8ad60153aa88a8a376e907dfed5c7eb Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 16 Feb 2024 16:32:55 +0100 Subject: [PATCH 3/4] Made caching work well for hooks --- backend/go-app/go.mod | 4 +- frontend/src/components/Priorities.jsx | 84 +++++++++++++++++++++++--- frontend/src/components/Priority.jsx | 1 - frontend/src/views/Admin.jsx | 14 ++++- 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 98f8f418..278ea169 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -1,6 +1,6 @@ module shuffle-shared -replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared go 1.19 @@ -18,7 +18,7 @@ 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.5.65 + github.com/shuffle/shuffle-shared v0.5.77 golang.org/x/crypto v0.16.0 google.golang.org/api v0.125.0 google.golang.org/grpc v1.55.0 diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx index 63c06515..bd6e5fa8 100644 --- a/frontend/src/components/Priorities.jsx +++ b/frontend/src/components/Priorities.jsx @@ -67,9 +67,15 @@ const Priorities = (props) => { }) } - const dismissNotification = (alert_id) => { - // Don't really care about the logout - fetch(`${globalUrl}/api/v1/notifications/${alert_id}/markasread`, { + const dismissNotification = (alert_id, disabled) => { + var notificationurl = `${globalUrl}/api/v1/notifications/${alert_id}/markasread` + if (disabled === true) { + notificationurl += "?disabled=true" + } else if (disabled === false) { + notificationurl += "?disabled=false" + } + + fetch(notificationurl , { credentials: "include", method: "GET", headers: { @@ -85,12 +91,50 @@ const Priorities = (props) => { }) .then(function (responseJson) { if (responseJson.success === true) { - const newNotifications = notifications.filter( - (data) => data.id !== alert_id - ); - console.log("NEW NOTIFICATIONS: ", newNotifications); + // Mark current one as read + var newNotifications = notifications.map((notification) => { + if (notification.id === alert_id) { + notification.read = true + } - if (setNotifications !== undefined) { + return notification + }) + + + if (disabled === true) { + toast("Notification disabled, and will not be shown again.") + + newNotifications = newNotifications.map((notification) => { + if (notification.id === alert_id) { + notification.ignored = true + } + + return notification + }) + + console.log("NEW NOTIFICATIONS: ", newNotifications); + } else if (disabled === false) { + toast("Notification re-enabled successfully") + + newNotifications = newNotifications.map((notification) => { + if (notification.id === alert_id) { + notification.ignored = false + } + + return notification + }) + + } else { + toast("Notification dismissed successfully") + } + + //const newNotifications = notifications.filter( + // (data) => data.id !== alert_id + //) + + //console.log("NEW NOTIFICATIONS: ", newNotifications); + + if (setNotifications !== undefined && newNotifications !== undefined) { setNotifications(newNotifications) } } else { @@ -174,6 +218,14 @@ const Priorities = (props) => { style={{marginRight: 15, height: 25, }} /> : null} + {data.ignored === true ? + + : null} {data.read === false ? { Dismiss ) : null} + + + diff --git a/frontend/src/components/Priority.jsx b/frontend/src/components/Priority.jsx index 13c278d3..60e4bce2 100644 --- a/frontend/src/components/Priority.jsx +++ b/frontend/src/components/Priority.jsx @@ -32,7 +32,6 @@ const Priority = (props) => { let newdescription = priority.description const descsplit = priority.description.split("&") if (appFramework !== undefined && descsplit.length === 5 && priority.description.includes(":default")) { - console.log("descsplit: ", descsplit) if (descsplit[1] === "") { const item = findSpecificApp(appFramework, descsplit[0]) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 7bdc2fb5..ce774122 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -1867,16 +1867,24 @@ If you're interested, please let me know a time that works for you, or set up a {selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app" ?
- Only the name of the auth can be modified for Oauth2. Please remake the authentication to change the fields like Client ID, Secret, Scopes etc. + Only the name and url can be modified for Oauth2/OpenID connect. Please remake the authentication if you want to change the other fields like Client ID, Secret, Scopes etc.
- : - selectedAuthentication.fields.map((data, index) => { + : null } + + {selectedAuthentication.fields.map((data, index) => { var fieldname = data.key.replaceAll("_", " ") if (fieldname.endsWith(" basic")) { fieldname = fieldname.substring(0, fieldname.length - 6) } + if (selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app") { + if (selectedAuthentication.fields[index].key !== "url") { + return null + } + } + + //console.log("DATA: ", data, selectedAuthentication) return (
From f8640d854fc271dd3ff90a5618ade3de8f4ce6fe Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 21 Feb 2024 09:32:33 +0100 Subject: [PATCH 4/4] Fixed file category loading and small workflow changes --- backend/app_sdk/app_base.py | 2 +- backend/go-app/go.mod | 4 +- backend/go-app/go.sum | 6 +- backend/go-app/main.go | 1 + frontend/src/components/ConfigureWorkflow.jsx | 26 +- frontend/src/components/Files.jsx | 312 ++++++++++++++++-- frontend/src/components/ParsedAction.jsx | 6 - frontend/src/components/RenderCytoscape.jsx | 34 +- frontend/src/components/ShuffleCodeEditor.jsx | 32 -- .../src/components/WorkflowTemplatePopup.jsx | 39 ++- frontend/src/views/Admin.jsx | 2 - frontend/src/views/AngularWorkflow.jsx | 2 +- frontend/src/views/Dashboard.jsx | 21 +- frontend/src/views/Workflows.jsx | 10 +- 14 files changed, 375 insertions(+), 122 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 1ae7049c..a7e1e7bf 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1212,7 +1212,7 @@ class AppBase: return results # Downloads all files from a namespace - # Currently only working on local version of Shuffle + # Currently only working on local version of Shuffle (2023) def get_file_category_ids(self, category): org_id = self.full_execution["workflow"]["execution_org"]["id"] diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 278ea169..ccfc643b 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -18,7 +18,7 @@ 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.5.77 + github.com/shuffle/shuffle-shared v0.5.78 golang.org/x/crypto v0.16.0 google.golang.org/api v0.125.0 google.golang.org/grpc v1.55.0 @@ -52,7 +52,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/frikky/schemaless v0.0.5 // indirect + github.com/frikky/schemaless v0.0.6 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index 3bdc7d06..bdd5b7b7 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -176,8 +176,8 @@ github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM github.com/frikky/kin-openapi v0.41.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0= github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= -github.com/frikky/schemaless v0.0.5 h1:ptQ0FpQpm/+e7HCYt7Wmc+vcMSelTYCRpKqtLVN2SOY= -github.com/frikky/schemaless v0.0.5/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= +github.com/frikky/schemaless v0.0.6 h1:mPWbqCxiOz0HUmdN+IiVOHqquCzA0aachzOdMTCaKtg= +github.com/frikky/schemaless v0.0.6/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsouza/go-dockerclient v1.9.7 h1:FlIrT71E62zwKgRvCvWGdxRD+a/pIy+miY/n3MXgfuw= @@ -455,6 +455,8 @@ github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shuffle/shuffle-shared v0.5.78 h1:emHTEu+WboTZQUUcPDrxMx70RtVuZ1LtkYjG2KzncBE= +github.com/shuffle/shuffle-shared v0.5.78/go.mod h1:Lg6/+qjQlWzNKwj4/4ATpvScyP2JQGLkTPlNlRM6RJk= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= diff --git a/backend/go-app/main.go b/backend/go-app/main.go index b2e901d5..0eca60ba 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -4953,6 +4953,7 @@ func initHandlers() { // PS: For cloud, this has to use cloud storage. // https://developer.box.com/reference/get-files-id-content/ // 1. Creating the "get file" option. Make it possible to run this in the frontend. + r.HandleFunc("/api/v1/files/download_remote", shuffle.HandleDownloadRemoteFiles).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/create", shuffle.HandleCreateFile).Methods("POST", "OPTIONS") diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index ef727fde..e3938c65 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -77,7 +77,6 @@ const ConfigureWorkflow = (props) => { const [checkStarted, setCheckStarted] = React.useState(false); useEffect(() => { - //console.log("Configure Workflow: Required actions: ", requiredActions) if (requiredActions.length === 0) { if (setConfigurationFinished !== undefined) { setConfigurationFinished(true) @@ -703,9 +702,9 @@ const ConfigureWorkflow = (props) => { }) .then((responseJson) => { if (!responseJson.success) { - toast("Failed to set app auth: " + responseJson.reason); + toast("Failed to set app authentication: " + responseJson.reason); } else { - toast("App auth set for app " + app.name.replace("_", " ")); + toast("App authentication set for app " + app.name.replace("_", " ")); setFinalized(true) setOpened(false) } @@ -743,6 +742,25 @@ const ConfigureWorkflow = (props) => {
{ setOpened(!opened); + + // Scroll to it + const element = document.getElementById("app-config"); + if (element) { + // Scroll down 100px + setTimeout(() => { + element.scrollIntoView({ + behavior: "smooth", + top: 100, + }) + + //element.scrollIntoView({ + // behavior: "smooth", + // block: "center", + // inline: "center" + //}); + }, 250) + } + }} >
@@ -1317,7 +1335,7 @@ const ConfigureWorkflow = (props) => { : null} - + {requiredActions.map((data, index) => { // AppWrapper = Default in a workflow, only shows with steps diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index 670cba97..73dbcfb7 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -16,6 +16,11 @@ import { Divider, Select, MenuItem, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Typography, } from "@mui/material"; import { @@ -30,9 +35,8 @@ import { Add as AddIcon, } from "@mui/icons-material"; -//import { useAlert import Dropzone from "../components/Dropzone.jsx"; -import CodeEditor from "../components/ShuffleCodeEditor.jsx"; +import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; import theme from "../theme.jsx"; const Files = (props) => { @@ -45,6 +49,13 @@ const Files = (props) => { const [fileContent, setFileContent] = React.useState(""); const [openEditor, setOpenEditor] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false); + const [loadFileModalOpen, setLoadFileModalOpen] = React.useState(false); + + const [field1, setField1] = React.useState(""); + const [field2, setField2] = React.useState(""); + const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards") + const [downloadBranch, setDownloadBranch] = React.useState("main"); + const [downloadFolder, setDownloadFolder] = React.useState("translation_standards"); //const alert = useAlert(); const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log"] @@ -67,7 +78,7 @@ const Files = (props) => { } - const runUpdateText = (text) =>{ + const runUpdateText = (text) =>{ fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, { method: "PUT", headers: { @@ -76,17 +87,34 @@ const Files = (props) => { }, body:text, credentials: "include", - }).then((response) => { + }) + .then((response) => { if (response.status !== 200) { console.log("Can't update file"); } return response.json(); - }) - //console.log(text); + }) + .then((responseJson) => { + if (responseJson.success === true) { + toast("Successfully updated file"); + } + }) + .catch((error) => { + toast("Error updating file: " + error.toString()); + }) } - const getFiles = () => { - fetch(globalUrl + "/api/v1/files", { + const getFiles = (namespace) => { + var parsedurl = `${globalUrl}/api/v1/files` + if (namespace === undefined || namespace === "default") { + + } else if (namespace !== undefined && namespace !== null && namespace !== "") { + parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true` + } else if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace !== "default" && selectedNamespace !== "") { + parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedNamespace}?ids=true` + } + + fetch(parsedurl, { method: "GET", headers: { "Content-Type": "application/json", @@ -104,12 +132,23 @@ const Files = (props) => { }) .then((responseJson) => { if (responseJson.files !== undefined && responseJson.files !== null) { - setFiles(responseJson.files); - } else { + setFiles(responseJson.files); + } else if (responseJson.list !== undefined && responseJson.list !== null) { + // Set the "namespace" field in all items + if (namespace !== undefined && namespace !== null) { + responseJson.list.forEach((item) => { + item.namespace = namespace + item.filename = item.name + item.workflow_id = "global" + }) + } + + setFiles(responseJson.list); + } else { setFiles([]); } - if (responseJson.namespaces !== undefined && responseJson.namespaces !== null) { + if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileNamespaces.length === 0 || responseJson.namespaces.length > fileNamespaces.length)) { setFileNamespaces(responseJson.namespaces); } }) @@ -119,9 +158,214 @@ const Files = (props) => { }; useEffect(() => { - getFiles(); + getFiles(selectedNamespace) }, []); + const importStandardsFromUrl = (url, folder) => { + if (url === undefined || url === null || url.length < 5) { + toast("Please enter a valid URL"); + return; + } + + if (folder === undefined || folder === null || folder.length < 2) { + toast("Please enter a valid folder name") + return + } + + const parsedData = { + url: url, + path: folder, + field_3: downloadBranch || "master", + }; + + if (field1.length > 0) { + parsedData["field_1"] = field1; + } + + if (field2.length > 0) { + parsedData["field_2"] = field2; + } + + toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`); + fetch(globalUrl + "/api/v1/files/download_remote", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(parsedData), + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + toast("Successfully loaded files from " + downloadUrl); + setLoadFileModalOpen(false); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + if (responseJson.reason !== undefined) { + toast("Failed loading: " + responseJson.reason); + } else { + toast("Failed loading"); + } + } + }) + .catch((error) => { + toast(error.toString()); + }); + } + + const handleGithubValidation = () => { + importStandardsFromUrl(downloadUrl, downloadFolder); + } + + const fileDownloadModal = loadFileModalOpen ? + {}} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + +
+ Load Files from Github +
+ + Files will be loaded from the repository and branch you specify, with the focus on files in one folder at a time. This is NOT recursive. + +
+ + Repository URL (supported: github, gitlab, bitbucket) + setDownloadUrl(e.target.value)} + placeholder="https://github.com/shuffle/standards" + fullWidth + /> +
+ + + Branch (default value is "main"): + + setDownloadBranch(e.target.value)} + placeholder="master" + fullWidth + /> + + + + Folder (can use / for subfolders): + + setDownloadFolder(e.target.value)} + placeholder="translation_standards" + fullWidth + /> + +
+ + Authentication (optional - private repos etc): + +
+ setField1(e.target.value)} + type="username" + placeholder="Username / APIkey (optional)" + fullWidth + /> + setField2(e.target.value)} + type="password" + placeholder="Password (optional)" + fullWidth + /> +
+
+ + + + +
+ : null + const deleteFile = (file) => { fetch(globalUrl + "/api/v1/files/" + file.id, { method: "DELETE", @@ -140,7 +384,7 @@ const Files = (props) => { }) .then((responseJson) => { if (responseJson.success) { - toast("Successfully deleted file " + file.name); + toast("Successfully deleted file") } else if ( responseJson.reason !== undefined && responseJson.reason !== null @@ -254,7 +498,7 @@ const Files = (props) => { }); }; - const handleCreateFile = (filename, file) => { + const handleCreateFile = (filename, file) => { var data = { filename: filename, org_id: selectedOrganization.id, @@ -355,7 +599,7 @@ const Files = (props) => { } setTimeout(() => { - getFiles(); + getFiles() }, 2500); }; @@ -378,7 +622,21 @@ const Files = (props) => { }} onDrop={uploadFile} > -
+
+ + + setLoadFileModalOpen(true)} + > + + + + + {fileDownloadModal} +

Files

@@ -393,6 +651,9 @@ const Files = (props) => {
+ + + + + {fileNamespaces !== undefined && fileNamespaces !== null && fileNamespaces.length > 1 ? ( @@ -442,8 +705,13 @@ const Files = (props) => { }} value={selectedNamespace} onChange={(event) => { - console.log("CHANGE NAMESPACE: ", event.target); setSelectedNamespace(event.target.value); + + if (event.target.value === "all" || event.target.value === "default") { + getFiles() + } else { + getFiles(event.target.value) + } }} > {fileNamespaces.map((data, index) => { @@ -453,7 +721,7 @@ const Files = (props) => { value={data} style={{ color: "white" }} > - {data} + {data.replaceAll("_", " ")} ); })} @@ -487,6 +755,8 @@ const Files = (props) => { } + + {renderTextBox && { handleKeyDown(event); @@ -504,7 +774,7 @@ const Files = (props) => { autoFocus />}
- { /> @@ -615,7 +885,7 @@ const Files = (props) => { }} /> - ) : ( + : ( { - const { globalUrl, inworkflow } = props; + const { globalUrl, inworkflow, height, width } = props; const [elements, setElements] = useState([]); const [workflow, setWorkflow] = useState(inworkflow); const [cy, setCy] = React.useState(); - const bodyWidth = 200; - const bodyHeight = 150; + const bodyWidth = height === undefined ? 1000 : width + const bodyHeight = width === undefined ? 1000 : height const setupGraph = () => { const actions = workflow.actions.map((action) => { @@ -76,29 +76,6 @@ const CytoscapeWrapper = (props) => { }; // This is an attempt at prettier edges. The numbers are weird to work with. - /* - //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html - const sourcenode = actions.find(node => node.data._id === branch.source_id) - const destinationnode = actions.find(node => node.data._id === branch.destination_id) - if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) { - //node.data._id = action["id"] - console.log("SOURCE: ", sourcenode.position) - console.log("DESTINATIONNODE: ", destinationnode.position) - - var opposite = true - if (sourcenode.position.x > destinationnode.position.x) { - opposite = false - } else { - opposite = true - } - - edge.style = { - 'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"], - 'control-point-weight': ['0.3', '0.7'], - } - } - */ - return edge; }); @@ -135,9 +112,10 @@ const CytoscapeWrapper = (props) => { elements={elements} minZoom={0.35} maxZoom={2.0} + zoom={1.0} style={{ - width: bodyWidth - 15, - height: bodyHeight - 5, + width: bodyWidth, + height: bodyHeight, backgroundColor: surfaceColor, }} stylesheet={cystyle} diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx index 15aec446..3b6e02a2 100644 --- a/frontend/src/components/ShuffleCodeEditor.jsx +++ b/frontend/src/components/ShuffleCodeEditor.jsx @@ -57,7 +57,6 @@ import { padding, textAlign } from '@mui/system'; import data from '../frameworkStyle.jsx'; import { useNavigate, Link, useParams } from "react-router-dom"; import { tags as t } from '@lezer/highlight'; -import { createTheme } from '@uiw/codemirror-themes'; @@ -84,37 +83,6 @@ const pythonFilters = [ {"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``}, ] -/* -const shuffleTheme = createTheme({ - theme: 'dark', - settings: { - background: "rgba(40,40,40, 1)", - foreground: '#75baff', - caret: '#5d00ff', - selection: '#036dd626', - selectionMatch: '#036dd626', - lineHighlight: '#8a91991a', - gutterForeground: '#8a919966', - }, - styles: [ - { tag: t.comment, color: '#787b8099' }, - { tag: t.variableName, color: '#0080ff' }, - { tag: [t.string, t.special(t.brace)], color: '#5c6166' }, - { tag: t.number, color: '#5c6166' }, - { tag: t.bool, color: '#5c6166' }, - { tag: t.null, color: '#5c6166' }, - { tag: t.keyword, color: '#5c6166' }, - { tag: t.operator, color: '#5c6166' }, - { tag: t.className, color: '#5c6166' }, - { tag: t.definition(t.typeName), color: '#5c6166' }, - { tag: t.typeName, color: '#5c6166' }, - { tag: t.angleBracket, color: '#5c6166' }, - { tag: t.tagName, color: '#5c6166' }, - { tag: t.attributeName, color: '#5c6166' }, - ], -}); -*/ - const CodeEditor = (props) => { const { globalUrl, diff --git a/frontend/src/components/WorkflowTemplatePopup.jsx b/frontend/src/components/WorkflowTemplatePopup.jsx index 08817eda..6df11b9b 100644 --- a/frontend/src/components/WorkflowTemplatePopup.jsx +++ b/frontend/src/components/WorkflowTemplatePopup.jsx @@ -5,6 +5,7 @@ import theme from '../theme.jsx'; import { useNavigate, Link, useParams } from "react-router-dom"; import AppSearchButtons from "../components/AppSearchButtons.jsx"; import { isMobile } from "react-device-detect"; +import RenderCytoscape from "../components/RenderCytoscape.jsx"; import { Button, Typography, @@ -43,6 +44,8 @@ const WorkflowTemplatePopup = (props) => { const [missingDestination, setMissingDestination] = React.useState(undefined); const [configurationFinished, setConfigurationFinished] = React.useState(false); const [appSetupDone, setAppSetupDone] = React.useState(false) + + const [requestSent, setRequestSent] = React.useState(false) const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; let navigate = useNavigate(); @@ -294,6 +297,9 @@ const WorkflowTemplatePopup = (props) => { // middle:[] // name: "Email analysis" // source:{app_id: "accdaaf2eeba6a6ed43b2efc0112032d", app_name + if (requestSent === true) { + return + } if (srcapp.includes(":default") || dstapp.includes(":default")) { @@ -331,6 +337,7 @@ const WorkflowTemplatePopup = (props) => { }, } + setRequestSent(true) const url = isCloud ? `${globalUrl}/api/v1/workflows/merge` : `https://shuffler.io/api/v1/workflows/merge` fetch(url, { method: "POST", @@ -344,6 +351,7 @@ const WorkflowTemplatePopup = (props) => { .then((response) => { if (response.status !== 200) { //console.log("Status not 200 for framework!"); + setRequestSent(false) } setWorkflowLoading(false) @@ -361,6 +369,7 @@ const WorkflowTemplatePopup = (props) => { if (responseJson.success === false) { //console.log("Error in workflow template: ", responseJson.error); + setRequestSent(false) const defaultMessage = "Failed to generate workflow the workflow - the Shuffle team has been notified. Contact support@shuffler.io for further assistance." if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason !== "") { @@ -386,6 +395,7 @@ const WorkflowTemplatePopup = (props) => { }) .catch((error) => { console.log("err in framework: ", error.toString()); + setRequestSent(false) setWorkflowLoading(false) }) } @@ -422,6 +432,9 @@ const WorkflowTemplatePopup = (props) => { return null } + const divHeight = 500 + const divWidth = 500 + return ( { setConfigurationFinished={setConfigurationFinished} /> + + {/*workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null && workflow.id !== "" ? +
+ +
+ : null*/} + {errorMessage === "" && configurationFinished === true && workflow.id !== undefined && workflowLoading === false ? - - + + { + // Open in new tab + window.open("/workflows/" + workflow.id, "_blank") + }} + > {/* */} - - Workflow generated! + + Workflow Successfully Generated! @@ -686,6 +716,7 @@ const WorkflowTemplatePopup = (props) => { : ""}
+
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index ce774122..984ebb30 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -5,8 +5,6 @@ import { makeStyles } from "@mui/styles"; import { useNavigate, Link } from "react-router-dom"; import countries from "../components/Countries.jsx"; -import CodeEditor from "../components/ShuffleCodeEditor.jsx"; -import getLocalCodeData from "../components/ShuffleCodeEditor.jsx"; import CacheView from "../components/CacheView.jsx"; import { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index fc7aae3c..30161530 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -121,7 +121,7 @@ import * as edgehandles from "cytoscape-edgehandles"; import CytoscapeComponent from "react-cytoscapejs"; import Draggable from "react-draggable"; import cytoscapestyle from "../defaultCytoscapeStyle.jsx"; -import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx"; +import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; import { GetParsedPaths, internalIds, } from "../views/Apps.jsx"; diff --git a/frontend/src/views/Dashboard.jsx b/frontend/src/views/Dashboard.jsx index 04b53a40..e9a5c201 100755 --- a/frontend/src/views/Dashboard.jsx +++ b/frontend/src/views/Dashboard.jsx @@ -1,16 +1,11 @@ -import React, { useState, useEffect } from "react"; -import { useInterval } from "react-powerhooks"; -import AppFramework from "../components/AppFramework.jsx"; -import { makeStyles, } from "@mui/styles"; -// nodejs library that concatenates classes -import classNames from "classnames"; -import theme from '../theme.jsx'; -import { useNavigate, Link, useParams } from "react-router-dom"; -import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"; - -// react plugin used to create charts -//import { Line, Bar } from "react-chartjs-2"; -//import { useAlert +import React, { useState, useEffect } from "react" +import { useInterval } from "react-powerhooks" +import AppFramework from "../components/AppFramework.jsx" +import { makeStyles, } from "@mui/styles" +import classNames from "classnames" +import theme from '../theme.jsx' +import { useNavigate, Link, useParams } from "react-router-dom" +import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx" import { ToastContainer, toast } from "react-toastify" import { parsedDatatypeImages } from "../components/AppFramework.jsx" import { findSpecificApp } from "../components/AppFramework.jsx" diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index fdf23604..f453c98a 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -551,9 +551,7 @@ const Workflows = (props) => { const [field1, setField1] = React.useState(""); const [field2, setField2] = React.useState(""); - const [downloadUrl, setDownloadUrl] = React.useState( - "https://github.com/frikky/shuffle-workflows" - ); + const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/workflows") const [downloadBranch, setDownloadBranch] = React.useState("master"); const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false); @@ -3630,7 +3628,7 @@ const Workflows = (props) => { const handleGithubValidation = () => { importWorkflowsFromUrl(downloadUrl); setLoadWorkflowsModalOpen(false); - }; + } const workflowDownloadModalOpen = loadWorkflowsModalOpen ? ( { }, }} onChange={(e) => setDownloadUrl(e.target.value)} - placeholder="https://github.com/frikky/shuffle-apps" + placeholder="https://github.com/shuffle/workflows" fullWidth /> - Branch (default value is "master"): + Branch (default value is "main"):