diff --git a/frontend/src/components/ApiExplorer.jsx b/frontend/src/components/ApiExplorer.jsx index c2dfaeb3..7e3092ea 100644 --- a/frontend/src/components/ApiExplorer.jsx +++ b/frontend/src/components/ApiExplorer.jsx @@ -100,6 +100,8 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se const [ExampleBody, setExampleBody] = useState({}); const [filteredActions, setFilteredActions] = useState([]); + const [firstSendDone, setFirstSendDone] = useState(false) + const getJsonObject = (properties) => { let jsonObject = {}; @@ -1425,18 +1427,20 @@ const ActionsList = memo(({ }; const handleSearch = (e) => { - const query = e.target.value; - setSearchQuery(query); + const query = e?.target?.value?.toLowerCase().replaceAll("_", " "); + + setSearchQuery(query) if (query.length === 0) { - setVisibleActions(actions); + setVisibleActions(actions) } else { setVisibleActions( - actions.filter((action) => - action.name.toLowerCase().includes(searchQuery.toLowerCase()) + actions?.filter((action) => + action?.name?.toLowerCase()?.replaceAll("_", " ")?.includes(searchQuery) ) - ); + ) } - }; + } + return (
@@ -1465,7 +1469,7 @@ const ActionsList = memo(({
- {action.name} + {actionname}
{ + /* + if (!firstSendDone) { + setFirstSendDone(true) + setCurTab(2) + } + */ + if (actionUrl.length === 0) { toast.error("URL cannot be empty"); return; diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index 1bafe19b..577d249c 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -215,8 +215,8 @@ const Billing = memo((props) => { var priceItem = "price_1MROFrDzMUgUjxHShcSxgHO1" - const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` - const failUrl = `${window.location.origin}/admin?admin_tab=billing&payment=failure` + const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success` + const failUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=failure` var checkoutObject = { lineItems: [ { @@ -2361,7 +2361,8 @@ const Billing = memo((props) => {
)} -
+ {isCloud ? ( +
@@ -2545,6 +2546,7 @@ const Billing = memo((props) => {
+ ): null}
{ - const { globalUrl, userdata, serverside, orgId, isSelectedDataStore } = props; + const { globalUrl, userdata, serverside, orgId, isSelectedDataStore, selectedOrganization } = props; const [orgCache, setOrgCache] = React.useState(""); const [listCache, setListCache] = React.useState([]); const [addCache, setAddCache] = React.useState(""); @@ -82,12 +88,16 @@ const CacheView = memo((props) => { const [editCache, setEditCache] = React.useState(false); const [cachedLoaded, setCachedLoaded] = React.useState(false); const [show, setShow] = useState({}); + const [showDistributionPopup, setShowDistributionPopup] = useState(false); + const [selectedSubOrg, setSelectedSubOrg] = useState([]); + const [selectedCacheKey, setSelectedCacheKey] = useState(""); useEffect(() => { if(orgId?.length >0){ listOrgCache(orgId); } }, [orgId]); + const listOrgCache = (orgId) => { fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, { method: "GET", @@ -214,7 +224,7 @@ const CacheView = memo((props) => { }) .then((responseJson) => { setAddCache(responseJson); - toast("New Cache Added Successfully!"); + toast("New key Added Successfully!"); listOrgCache(orgId); setModalOpen(false); }) @@ -296,7 +306,7 @@ const CacheView = memo((props) => { > - { editCache ? "Edit Cache" : "Add Cache" } + { editCache ? "Edit Key" : "Add Key" }
@@ -368,6 +378,7 @@ const CacheView = memo((props) => { style={{ borderRadius: "2px", fontSize: 16, color: "#ff8544", textTransform:"none" }} onClick={() => { setModalOpen(false) + setKey("") setValue("") setDataValue({}) }} @@ -380,7 +391,7 @@ const CacheView = memo((props) => { style={{ borderRadius: "2px", backgroundColor: "#ff8544",color: "#1a1a1a", textTransform:"none" }} onClick={() => { {editCache ? editOrgCache(orgId) : addOrgCache(orgId)} - + setKey("") setValue("") setDataValue({}) }} @@ -392,9 +403,175 @@ const CacheView = memo((props) => { ); + const handleSelectSubOrg = (id, action) => { + if (action === "all") { + const childOrgs = userdata.orgs.filter( + (data) => data.creator_org === userdata.active_org.id + ); + setSelectedSubOrg((prev) => { + if (prev.length === childOrgs.length) { + // If all child orgs are already selected, clear the selection + return []; + } else { + // Otherwise, select all child org IDs + return childOrgs.map((data) => data.id); + } + }); + } else if (action === "none") { + setSelectedSubOrg([]); + } else { + setSelectedSubOrg((prev) => { + if (prev.includes(id)) { + return prev.filter((data) => data !== id); + } else { + return [...prev, id]; + } + }); + } + }; + + const changeDistribution = (id, selectedSubOrg) => { + + editFileConfig(id, [...new Set(selectedSubOrg)]) + } + + const editFileConfig = (id, selectedSubOrg, cacheKey) => { + const data = { + Key: id, + action: "suborg_distribute", + selected_suborgs: selectedSubOrg, + } + console.log("data: ", data); + + const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/config`; + + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed overwriting datastore"); + } else { + toast("Successfully updated datastore!"); + setTimeout(() => { + listOrgCache(orgId); + setShowDistributionPopup(false); + }, 1000); + } + }) + ) + .catch((error) => { + toast("Err: " + error.toString()); + }); + }; + + + const cacheDistributionModal = showDistributionPopup ? ( + setShowDistributionPopup(false)} + PaperProps={{ + sx: { + borderRadius: theme?.palette?.DialogStyle?.borderRadius, + border: theme?.palette?.DialogStyle?.border, + fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + zIndex: 1000, + minWidth: "600px", + minHeight: "320px", + overflow: "auto", + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogActions-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + }, + }} + > + +
+ Select sub-org to distribute files +
+
+ + {handleSelectSubOrg(null, "none")}}>None + {handleSelectSubOrg(null, "all")}}>All + {userdata.orgs.map((data, index) => { + if (data.creator_org !== userdata.active_org.id) { + return null; + } + + const imagesize = 22; + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginRight: 10, + marginLeft: data.id === userdata.active_org.id ? 0 : 20, + }; + + const image = data.image === "" ? ( + {data.name} + ) : ( + {data.name} + ); + + return ( + handleSelectSubOrg(data.id)} + style={{ display: "flex", alignItems: "center" }} + > + + {image} + {data.name} + + ); + })} + +
+ + +
+
+
+ ) : null; + return (
{modalView} + {cacheDistributionModal}
@@ -422,7 +599,7 @@ const CacheView = memo((props) => { setValue("") }} > - Add Cache + Add Key - } + {/**/} {detectionInfo?.category === "SIGMA" || detectionInfo?.category === "SIEM" ? - + 0 ? green : red}} /> diff --git a/frontend/src/components/DetectionRuleCard.jsx b/frontend/src/components/DetectionRuleCard.jsx index 986b227f..1a2e9760 100644 --- a/frontend/src/components/DetectionRuleCard.jsx +++ b/frontend/src/components/DetectionRuleCard.jsx @@ -137,6 +137,8 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i setResponseValue(e.target.value) + toast.error("The automatic response system is NOT available for you yet. Please contact support@shuffler.io if you want to try this feature.") + // FIXME: Handle: // 1. Get the current cache for the detection // 2. Create a new mapping for Detection -> Response @@ -187,7 +189,7 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index a7694562..296612b8 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -647,7 +647,7 @@ const EditWorkflow = (props) => { userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ? userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ? - Your organization does not have any suborgs yet OR your user may not have access to available suborgs. Please make one or get access to suborgs by another admin, then try again. + Your organization does not have any suborgs yet OR your user may not have access to available suborgs. Please make one or get access to suborgs by another admin, then try again. : @@ -738,7 +738,7 @@ const EditWorkflow = (props) => { })} : - + Create a sub-org to distribute workflows to suborgs. @@ -753,7 +753,7 @@ const EditWorkflow = (props) => { Git Backup Repository - Decide where this workflow is backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the root folder in the format 'orgid/workflow status/workflow id.json' without images. Overrides your default backup repository. Credentials are encrypted. Creates notifications if it fails. + Decide where this workflow is backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the root folder in the format 'orgid/workflow status/workflow id.json' without images. Overrides your default backup repository. Credentials are encrypted. Creates notifications if it fails. diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index 378bc532..6db9124b 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -1567,7 +1567,7 @@ const Files = memo((props) => { placement="top" > { : shuffleVariant === 0 ? "price_1PZPSSEJjT17t98NLJoTMYja" : "price_1PZPQuEJjT17t98N3yORUtd9" - const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` - const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure` + const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success` + const failUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=failure` console.log("Priceitem: ", priceItem, shuffleVariant) var checkoutObject = { @@ -888,7 +888,7 @@ const LicencePopup = (props) => { } if (stripe === undefined || stripe === null || stripe.redirectToCheckout === undefined) { - window.open("https://shuffler.io/admin?admin_tab=billing&payment=stripe_error", "_self") + window.open("https://shuffler.io/admin?admin_tab=billingstats&payment=stripe_error", "_self") } stripe.redirectToCheckout(checkoutObject) diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index e193460c..ed8ef8a1 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -465,7 +465,7 @@ const Header = (props) => { - + { handleClose(); @@ -1107,7 +1107,7 @@ const Header = (props) => { ); })} - + { expansionModalOpen, setExpansionModalOpen, + fixExample, listCache, setActiveDialog, @@ -258,7 +260,7 @@ const ParsedAction = (props) => { if (param.required === false && param.name.startsWith("${") && param.name.endsWith("}")) { // Check if it's a required param - param.autocompleted = false + param.autocompleted = true if (selectedAction.required_body_fields !== undefined && selectedAction.required_body_fields !== null && selectedAction.required_body_fields.length > 0) { if (selectedAction.required_body_fields.includes(param.name)) { param.required = true @@ -281,6 +283,7 @@ const ParsedAction = (props) => { } if (param.field_active === true) { + param.autocompleted = true generated_optional.push(param) continue } @@ -298,8 +301,8 @@ const ParsedAction = (props) => { const newparams = auth .concat(bodyfield) .concat(required) - .concat(special_optional) .concat(generated_optional) + .concat(special_optional) .concat(optional) var newkeyorder = [] @@ -1492,6 +1495,7 @@ const ParsedAction = (props) => { newAppname = newAppname.replaceAll("_", " ") } + var optionalFound = false return (
@@ -3122,91 +3126,6 @@ const ParsedAction = (props) => { }} /> - {/* - - - - - - - - - */}
) @@ -3551,7 +3470,7 @@ const ParsedAction = (props) => { }} /> - ); + ) // Finds headers from a string to be used for autocompletion const findHeaders = (inputdata) => { @@ -4087,9 +4006,24 @@ const ParsedAction = (props) => { }; var parsedPaths = []; - if (typeof innerdata.example === "object") { - parsedPaths = GetParsedPaths(innerdata.example, ""); - } + if (innerdata.type === "workflow_variable") { + // Try to parse the value if it's a string that could be JSON + if (typeof innerdata.value === "string") { + try { + const parsedValue = JSON.parse(innerdata.value) + if (typeof parsedValue === "object") { + parsedPaths = GetParsedPaths(parsedValue, ""); + } + } catch (e) { + // Not valid JSON, use the value directly + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.value === "object") { + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } const coverColor = "#82ccc3" //menuPosition.left -= 50 @@ -4270,8 +4204,14 @@ const ParsedAction = (props) => { data.variant = "STATIC_VALUE" } + const isFirstOptional = optionalFound === false && data.configuration === false && data.required === false ? true : false + if (optionalFound === false && data.configuration === false && data.required === false) { + optionalFound = true + } + return ( -
+
+ {isFirstOptional ? : null} {showButtonField === true ? hideBodyButtonValue : null}
{ ) : null} {hasAutocomplete === true ? + data.field_active === true ? + + + + + : { title={"Explore your keys in Datastore"} placement="top" > - + { parsedvalue = "" } + //console.log("Required fields: ", selectedActionParameters[count]) + setEditorData({ "name": data.name, - "value": parsedvalue, + "value": fixExample(parsedvalue), "field_number": count, "actionlist": actionlist, "field_id": clickedFieldId, - "example": selectedActionParameters[count].example, + "example": fixExample(selectedActionParameters[count].example), }) }} /> diff --git a/frontend/src/components/SearchData.jsx b/frontend/src/components/SearchData.jsx index dacab40f..1e6403fd 100644 --- a/frontend/src/components/SearchData.jsx +++ b/frontend/src/components/SearchData.jsx @@ -387,7 +387,7 @@ const SearchData = props => { if (responseJson.success === false) { toast(`Failed to ${type} the app for your organization. Please try again or contact support@shuffler.io for more info`) } else { - toast(`App successfully ${type}d. Please refresh the page to use it.`) + toast(`App successfully ${type}d. It may now be used in your workflows.`) } }) .catch(error => { diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index f28668fa..b2c684a5 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -114,7 +114,9 @@ const CodeEditor = (props) => { editorData, setAiQueryModalOpen, - fullScreenMode + fullScreenMode, + environment, + fixExample, } = props const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); @@ -189,8 +191,27 @@ const CodeEditor = (props) => { tmpVariables.push('$' + actionlist[i].autocomplete.toLowerCase()) var parsedPaths = [] - if (typeof actionlist[i].example === "object") { - parsedPaths = GetParsedPaths(actionlist[i].example, ""); + if (actionlist[i].type === "workflow_variable") { + // Try to parse the value if it's a string that could be JSON + if (typeof actionlist[i].value === "string") { + try { + const parsedValue = JSON.parse(actionlist[i].value) + if (typeof parsedValue === "object") { + parsedPaths = GetParsedPaths(parsedValue, ""); + } + } catch (e) { + // Not valid JSON, skip parsing + continue + } + } else if (typeof actionlist[i].value === "object") { + // Direct object/array value + parsedPaths = GetParsedPaths(actionlist[i].value, ""); + } + } else { + // Handle regular action results + if (typeof actionlist[i].example === "object") { + parsedPaths = GetParsedPaths(actionlist[i].example, ""); + } } for (var key in parsedPaths) { @@ -596,7 +617,7 @@ const CodeEditor = (props) => { var code_lines = value.split('\n') for (var i = 0; i < code_lines.length; i++) { var current_code_line = code_lines[i] - var variable_occurence = current_code_line.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_@-]+\.?){1}([a-zA-Z0-9#_@-]+\.?){0,}/g); + var variable_occurence = current_code_line.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_@-]+\.?){1}([a-zA-Z0-9#_@-]+\.?){0,}/g) if (!variable_occurence) { continue; @@ -664,6 +685,75 @@ const CodeEditor = (props) => { } } + var code_lines = value.split('\n') + for (var i = 0; i < code_lines.length; i++) { + var current_code_line = code_lines[i] + + // Look for REPLACE_ME + var variable_occurence = current_code_line.match(/REPLACE_ME/g) + + if (!variable_occurence) { + continue; + } + + var new_occurences = variable_occurence.filter((occurrence) => occurrence[0]) + variable_occurence = new_occurences + + // Find the start position of REPLACE_ME and highlight it + var dollar_occurence = [] + for (let ch = 0; ch < current_code_line.length; ch++) { + // Not allowing it then lol + if (ch + 9 >= current_code_line.length) { + continue + } + + // Rofl - at least it is specific + if (current_code_line[ch] === 'R' && current_code_line[ch + 1] === 'E' && current_code_line[ch + 2] === 'P' && current_code_line[ch + 3] === 'L' && current_code_line[ch + 4] === 'A' && current_code_line[ch + 5] === 'C' && current_code_line[ch + 6] === 'E' && current_code_line[ch + 7] === '_' && current_code_line[ch + 8] === 'M' && current_code_line[ch + 9] === 'E') { + dollar_occurence.push(ch) + } + } + + try { + if (variable_occurence.length === 0) { + //value.markText({line:i, ch:0}, {line:i, ch:code_lines[i].length-1}, {"css": "background-color: #282828; border-radius: 0px; color: #b8bb26"}) + //value.markText({line:i, ch:0}, {line:i, ch:code_lines[i].length-1}, {"css": "background-color: #; border-radius: 0px; color: inherit"}) + } + + for (let occ = 0; occ < variable_occurence.length; occ++) { + const fixedVariable = variable_occurence[occ] + + var startCh = dollar_occurence[occ] + var endCh = dollar_occurence[occ] + 10 + try { + newMarkers.push({ + startRow: i, + startCol: startCh, + endRow: i, + endCol: endCh, + className: "bad-marker", + type: "text", + }) + } catch (e) { + console.log("Error in color highlighting: ", e); + newMarkers.push({ + startRow: i, + startCol: startCh, + endRow: i, + endCol: endCh, + className: "bad-marker", + type: "text", + }) + } + + setMarkers(newMarkers) + } + + + } catch (e) { + console.log("Error in color highlighting: ", e); + } + } + setMarkers(newMarkers) } @@ -720,6 +810,34 @@ const CodeEditor = (props) => { const fixedVariable = fixVariable(found[i]) var valuefound = false + + // First check if it's a workflow variable + if (actionlist !== undefined && actionlist.length > 0) { + const workflowVar = actionlist?.find(item => + item.type === "workflow_variable" && + `$${item.autocomplete.toLowerCase()}` === fixedVariable.toLowerCase() + ) + + if (workflowVar && workflowVar.example) { + valuefound = true + try { + // Try to parse the example value if it's stored as a JSON string + if (typeof workflowVar.example === "string" && + (workflowVar.example.startsWith("[") || workflowVar.example.startsWith("{"))) { + const parsedExample = JSON.parse(workflowVar.example) + input = input.replace(found[i], JSON.stringify(parsedExample), -1) + } else { + input = input.replace(found[i], workflowVar.example, -1) + } + continue + } catch (e) { + console.log("Error parsing workflow variable:", e) + input = input.replace(found[i], workflowVar.example, -1) + continue + } + } + } + for (var j = 0; j < actionlist.length; j++) { if (fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()) { continue @@ -750,9 +868,26 @@ const CodeEditor = (props) => { var shouldbreak = false for (var k = 0; k < actionlist.length; k++) { var parsedPaths = [] - if (typeof actionlist[k].example === "object") { - parsedPaths = GetParsedPaths(actionlist[k].example, ""); - } + + // Handle both workflow variables and regular actions + if (actionlist[k].type === "workflow_variable") { + // Try to parse the value if it's a string that could be JSON + if (typeof actionlist[k].value === "string") { + try { + const parsedValue = JSON.parse(actionlist[k].value) + if (typeof parsedValue === "object") { + parsedPaths = GetParsedPaths(parsedValue, ""); + } + } catch (e) { + // Not valid JSON, use the value directly + parsedPaths = GetParsedPaths(actionlist[k].value, ""); + } + } else if (typeof actionlist[k].value === "object") { + parsedPaths = GetParsedPaths(actionlist[k].value, ""); + } + } else if (typeof actionlist[k].example === "object") { + parsedPaths = GetParsedPaths(actionlist[k].example, ""); + } for (var key in parsedPaths) { const fullpath = "$" + actionlist[k].autocomplete.toLowerCase() + parsedPaths[key].autocomplete.toLowerCase() @@ -766,7 +901,22 @@ const CodeEditor = (props) => { var new_input = "" try { - new_input = FindJsonPath(fullpath, actionlist[k].example) + const sourceData = actionlist[k].type === "workflow_variable" ? + (() => { + // Try to parse the value if it's a JSON string + if (typeof actionlist[k].value === "string") { + try { + return JSON.parse(actionlist[k].value); + } catch (e) { + // If parsing fails, return the original string value + return actionlist[k].value; + } + } + return actionlist[k].value; + })() : + actionlist[k].example; + + new_input = FindJsonPath(fullpath, sourceData) } catch (e) { console.log("ERR IN INPUT: ", e) } @@ -789,8 +939,9 @@ const CodeEditor = (props) => { } } - input = input.replace(fixedVariable, new_input, -1) - input = input.replace(found[i], new_input, -1) + // Replace both the fixed and original variable to handle both #0 and # cases + input = input.replace(found[i], new_input) + input = input.replace(fixedVariable, new_input) shouldbreak = true break @@ -876,8 +1027,12 @@ const CodeEditor = (props) => { } if (edited === false) { - if (!item.value.includes("{%") && !item.value.includes("{{")) { - setlocalcodedata(localcodedata + " | " + item.value + " }}") + if (item.value.includes("{%") || item.value.includes("{{")) { + if (!item.value.includes("}}") && !item.value.includes("%}")) { + setlocalcodedata(localcodedata + " | " + item.value + " }}") + } else { + setlocalcodedata(localcodedata + item.value) + } } else { setlocalcodedata(localcodedata + item.value) } @@ -899,7 +1054,7 @@ const CodeEditor = (props) => { const actionname = selectedAction.name === "execute_python" && !inputdata.replaceAll(" ", "").includes("{%python%}") ? "execute_python" : selectedAction.name === "execute_bash" ? "execute_bash" : "repeat_back_to_me" const params = actionname === "execute_python" ? [{ "name": "code", "value": inputdata }] : actionname === "execute_bash" ? [{ "name": "code", "value": inputdata }, { "name": "shuffle_input", "value": "", }] : [{ "name": "call", "value": inputdata }] - const actiondata = { "description": "Repeats the call parameter", "id": "", "name": actionname, "label": "", "node_type": "", "environment": "", "sharing": false, "private_id": "", "public_id": "", "app_id": appid, "tags": null, "authentication": [], "tested": false, "parameters": params, "execution_variable": { "description": "", "id": "", "name": "", "value": "" }, "returns": { "description": "", "example": "", "id": "", "schema": { "type": "string" } }, "authentication_id": "", "example": "", "auth_not_required": false, "source_workflow": "", "run_magic_output": false, "run_magic_input": false, "execution_delay": 0, "app_name": "Shuffle Tools", "app_version": "1.2.0", "selectedAuthentication": {} } + const actiondata = { "description": "Repeats the call parameter", "id": "", "name": actionname, "label": "", "node_type": "", "environment": environment?.Name, "sharing": false, "private_id": "", "public_id": "", "app_id": appid, "tags": null, "authentication": [], "tested": false, "parameters": params, "execution_variable": { "description": "", "id": "", "name": "", "value": "" }, "returns": { "description": "", "example": "", "id": "", "schema": { "type": "string" } }, "authentication_id": "", "example": "", "auth_not_required": false, "source_workflow": "", "run_magic_output": false, "run_magic_input": false, "execution_delay": 0, "app_name": "Shuffle Tools", "app_version": "1.2.0", "selectedAuthentication": {} } setExecutionResult({ "valid": false, @@ -1396,7 +1551,22 @@ const CodeEditor = (props) => { }; var parsedPaths = []; - if (typeof innerdata.example === "object") { + if (innerdata.type === "workflow_variable") { + // Try to parse the value if it's a string that could be JSON + if (typeof innerdata.value === "string") { + try { + const parsedValue = JSON.parse(innerdata.value) + if (typeof parsedValue === "object") { + parsedPaths = GetParsedPaths(parsedValue, ""); + } + } catch (e) { + // Not valid JSON, use the value directly + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.value === "object") { + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.example === "object") { parsedPaths = GetParsedPaths(innerdata.example, ""); } @@ -1585,7 +1755,13 @@ const CodeEditor = (props) => { }} disabled={editorData === undefined || editorData.example === undefined || editorData.example === null || editorData.example.length === 0} onClick={() => { - setlocalcodedata(editorData.example) + if (fixExample !== undefined) { + const newExample = fixExample(editorData.example) + setlocalcodedata(newExample) + } else { + console.log("No fix example available!") + setlocalcodedata(editorData.example) + } }} color="secondary" > diff --git a/frontend/src/views/Admin2.jsx b/frontend/src/views/Admin2.jsx index 2e68cef6..2d630fbb 100644 --- a/frontend/src/views/Admin2.jsx +++ b/frontend/src/views/Admin2.jsx @@ -11,29 +11,8 @@ const Admin2 = (props) => { const [organizationFeatures, setOrganizationFeatures] = useState({}); const [orgRequest, setOrgRequest] = React.useState(true); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const handleGetOrg = (orgId) => { - // if ( - // serverside !== true && - // window.location.search !== undefined && - // window.location.search !== null - // ) { - // const urlSearchParams = new URLSearchParams(window.location.search); - // const params = Object.fromEntries(urlSearchParams.entries()); - // const foundorgid = params["org_id"]; - // if (foundorgid !== undefined && foundorgid !== null) { - // orgId = foundorgid; - // } - // } - console.log("getting organization details for: ", orgId); - - // if (orgId === undefined) { - // toast( - // "Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.", - // ); - // return; - // } - - // Just use this one? fetch(`${globalUrl}/api/v1/orgs/${orgId}`, { method: "GET", diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index bfeff035..516a1a60 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -568,6 +568,9 @@ const AngularWorkflow = (defaultprops) => { } }, [editWorkflowModalOpen]) + const dragRef = React.useRef(false); + + // New for generated stuff const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ @@ -696,11 +699,32 @@ const releaseToConnectLabel = "Release to Connect" } if (loadedApps.includes(appId)) { - return + console.log("App already loaded: ", appId) + + // 1. Find the app and check if it has actions + // 2. If it doesn't have actions, reload once again + var should_reload = false + for (var i = 0; i < apps.length; i++) { + const curapp = apps[i] + if (curapp.id !== appId) { + continue + } + + if (curapp.actions === undefined || curapp.actions === null || curapp.actions.length === 0 || curapp.actions.length === 1) { + should_reload = true + break + } + } + + if (!should_reload) { + return + } } - loadedApps.push(appId) - setLoadedApps(loadedApps) + if (!loadedApps.includes(appId)) { + loadedApps.push(appId) + setLoadedApps(loadedApps) + } const appUrl = `${globalUrl}/api/v1/apps/${appId}/config?openapi=false` fetch(appUrl, { @@ -2331,6 +2355,35 @@ const releaseToConnectLabel = "Release to Connect" return success }; + const fixExample = (input, required) => { + if (input === undefined || input === null || input.length === 0) { + return "" + } + + + // 1. Find anything matching ${variable} + // 2. If it is required, replace it with REQUIRED + // 3. If it is not required, replace it with empty + + // Check if it is a string or not + var newExample = input + if (typeof newExample !== "string") { + return newExample + } + + const found = newExample.match(/\${(.*?)}/g) + if (found === null || found === undefined || found.length === 0) { + return newExample + } + + for (var i = 0; i < found.length; i++) { + //newExample = newExample.replace(found[i], "REQUIRED") + newExample = newExample.replace(found[i], "REPLACE_ME") + } + + return newExample + } + const monitorUpdates = () => { var firstnode = cy.getElementById(workflow.start); if (firstnode.length === 0) { @@ -3656,7 +3709,7 @@ const releaseToConnectLabel = "Release to Connect" setWorkflows([responseJson]) } else { getAppAuthentication(); - getEnvironments(); + getEnvironments(responseJson.org_id) getSettings(); getFiles() @@ -6864,7 +6917,9 @@ const releaseToConnectLabel = "Release to Connect" } } - if (showEnvCnt > 1) { + // Always showing for now + //if (showEnvCnt > 1) { + if (showEnvCnt > 0) { setShowEnvironment(true) } @@ -10018,15 +10073,37 @@ const releaseToConnectLabel = "Release to Connect" const ParsedAppPaper = (props) => { const app = props.app - const small = props.small - const actionString = props.action + const small = props.small + const actionString = props.action + const [hover, setHover] = React.useState(false); - React.useEffect(() => { - if(app.name === "Shuffle Tools"){ - if (app.actions !== undefined && (app.actions === null || app.actions.length === 1)) { - loadAppConfig(app.id, false) + + // Prevent hover effects during drag + const handleMouseMove = React.useCallback((e) => { + if (dragRef.current) { + setHover(false); + } + }, []); + + React.useEffect(() => { + // Add mousemove listener to track dragging + document.addEventListener('mousemove', handleMouseMove); + return () => { + document.removeEventListener('mousemove', handleMouseMove); + }; + }, [handleMouseMove]); + + + React.useEffect(() => { + if (props.skip_load === true) { + return + } + + if (app.name === "Shuffle Tools"){ + if (app.actions !== undefined && (app.actions === null || app.actions.length === 1)) { + loadAppConfig(app.id, false) + } } - } }, []) if (app === undefined || app === null) { @@ -10115,13 +10192,20 @@ const releaseToConnectLabel = "Release to Connect" } newAppStyle.backgroundColor = theme.palette.backgroundColor - return ( { + dragRef.current = true; + newAppStyle.zIndex = 9999 + + }} onDrag={(e) => { + newAppStyle.zIndex = 9999 handleAppDrag(e, app) }} onStop={(e) => { + dragRef.current = false; + newAppStyle.zIndex = "none" handleDragStop(e, app) }} key={app.id} @@ -10132,18 +10216,18 @@ const releaseToConnectLabel = "Release to Connect" { - e.preventDefault() - e.stopPropagation() - - setHover(true) - - if (app.actions !== undefined && (app.actions === null || app.actions.length === 1)) { - loadAppConfig(app.id, false) - } - + onMouseEnter={(e) => { + if (!dragRef.current) { + e.preventDefault(); + e.stopPropagation(); + setHover(true); + + if (app.actions !== undefined && (app.actions === null || app.actions.length === 1)) { + loadAppConfig(app.id, false); + } + } }} - onMouseOut={() => { + onMouseLeave={() => { setHover(false); }} onClick={() => { @@ -10586,6 +10670,27 @@ const releaseToConnectLabel = "Release to Connect" } var viewedApps = [] + + const QuickAccessSection = ({title, items, renderItem}) => ( +
+ + {title} + +
+ {items.map((item, index) => renderItem(item, index))} +
+
+ ); + + // Popular Shuffle Tools actions + const popularActions = [ + ["repeat_back_to_me", "filter_list", "execute_python", "parse_ioc"], + ["set_cache_value", "get_file_meta", "merge_lists", "send_sms_shuffle"] + ]; return (
@@ -10628,59 +10733,37 @@ const releaseToConnectLabel = "Release to Connect" }} /> - {shuffleToolsApp !== undefined && shuffleToolsApp !== null && document?.getElementById("appsearch")?.value?.length === 0 ? -
- - Popular Actions - -
- -
- -
- -
- -
-
- -
- -
- -
- -
-
- : null} - {document?.getElementById("appsearch")?.value?.length === 0 ? -
- - Triggers - -
- {triggers.map((trigger, index) => { - if (trigger.trigger_type === "PIPELINE") { - return null - } - - - return( -
- -
- ) - })} -
-
- : null} + {shuffleToolsApp && !document?.getElementById("appsearch")?.value?.length && ( + ( + + )} + /> + )} + + {!document?.getElementById("appsearch")?.value?.length && ( + t.trigger_type !== "PIPELINE")} + renderItem={(trigger, index) => ( + + )} + /> + )} Your Apps @@ -13942,8 +14025,23 @@ const releaseToConnectLabel = "Release to Connect" }; var parsedPaths = []; - console.log("Found example data: ", innerdata.example) - if (typeof innerdata.example === "object") { + + if (innerdata.type === "workflow_variable") { + // Try to parse the value if it's a string that could be JSON + if (typeof innerdata.value === "string") { + try { + const parsedValue = JSON.parse(innerdata.value) + if (typeof parsedValue === "object") { + parsedPaths = GetParsedPaths(parsedValue, ""); + } + } catch (e) { + // Not valid JSON, use the value directly + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.value === "object") { + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.example === "object") { parsedPaths = GetParsedPaths(innerdata.example, ""); } @@ -16531,7 +16629,7 @@ const releaseToConnectLabel = "Release to Connect" }
- {showEnvironment === true && environments.length > 1 && selectedActionEnvironment !== undefined && selectedActionEnvironment !== null && selectedActionEnvironment.Name !== undefined && selectedActionEnvironment.Name !== null ? + {showEnvironment === true && environments.length > 0 && selectedActionEnvironment !== undefined && selectedActionEnvironment !== null && selectedActionEnvironment.Name !== undefined && selectedActionEnvironment.Name !== null ? { + console.log("CLICK!") + getEnvironments(workflow.org_id) + }} onChange={(e) => { setLastSaved(false) const env = environments.find((a) => a.Name === e.target.value); @@ -16600,8 +16702,9 @@ const releaseToConnectLabel = "Release to Connect" > {data.Name === "cloud" || data.Name === "Cloud" ? null : !isRunning ? +
- + { e.preventDefault() e.stopPropagation() + window.open(`/admin?tab=locations&env=${data.Name}`, "_blank", "noopener,noreferrer") }} @@ -16617,7 +16721,21 @@ const releaseToConnectLabel = "Release to Connect" /> - : null} + : + { + //handleChipClick + }} + variant="outlined" + color="primary" + /> + } {data.default === true ? { e.preventDefault() e.stopPropagation() - window.open(`/admin?admin_tab=priorities&workflow=${data.workflow.id}&execution_id=${data.execution_id}`, "_blank") + window.open(`/admin?admin_tab=notifications&workflow=${data.workflow.id}&execution_id=${data.execution_id}`, "_blank") }} /> @@ -19075,7 +19193,7 @@ const releaseToConnectLabel = "Release to Connect" )}
) : ( -
+
-
+

Details

{ e.preventDefault() e.stopPropagation() - window.open(`/admin?admin_tab=priorities&workflow=${executionData.workflow.id}&execution_id=${executionData.execution_id}`, "_blank") + window.open(`/admin?admin_tab=notifications&workflow=${executionData.workflow.id}&execution_id=${executionData.execution_id}`, "_blank") }} /> @@ -20190,7 +20308,7 @@ const releaseToConnectLabel = "Release to Connect" } if (stringjson.includes("kms/")) { - return "KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=priorities. If you need help with KMS, please contact support@shuffler.io" + return "KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=notifications. If you need help with KMS, please contact support@shuffler.io" } if (stringjson.includes("invalidurl")) { @@ -20208,7 +20326,7 @@ const releaseToConnectLabel = "Release to Connect" } if (isCloud && stringjson.toLowerCase().includes("timeout error")) { - return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=locationsto create an environment to connect to" + return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=locations to create an environment to connect to" } if (stringjson.toLowerCase().includes("invalid header")) { @@ -20218,7 +20336,7 @@ const releaseToConnectLabel = "Release to Connect" if (stringjson.includes("connectionerror")) { if (stringjson.includes("kms")) { - return "KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=priorities&kms=true. If you need help with KMS, please contact support@shuffler.io" + return "KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=notifications&kms=true. If you need help with KMS, please contact support@shuffler.io" } return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs." @@ -20696,6 +20814,7 @@ const releaseToConnectLabel = "Release to Connect" setExpansionModalOpen={setCodeEditorModalOpen} setEditorData={setEditorData} setAiQueryModalOpen={setAiQueryModalOpen} + fixExample={fixExample} />
@@ -22583,6 +22702,8 @@ const releaseToConnectLabel = "Release to Connect" changeActionParameterCodeMirror={changeActionParameterCodeMirror} activeDialog={activeDialog} setActiveDialog={setActiveDialog} + environment={selectedActionEnvironment} + setAiQueryModalOpen={setAiQueryModalOpen} /> diff --git a/frontend/src/views/ApiExplorerWrapper.jsx b/frontend/src/views/ApiExplorerWrapper.jsx index 8de63fb9..197e0d08 100644 --- a/frontend/src/views/ApiExplorerWrapper.jsx +++ b/frontend/src/views/ApiExplorerWrapper.jsx @@ -101,8 +101,8 @@ const ApiExplorerWrapper = (props) => { }, [selectedAppData, openapi]) useEffect(() => { + getAppData(appid) if (appid !== undefined && appid !== null && appid.length !== 0) { - getAppData(appid) HandleGetLocations() } @@ -157,13 +157,17 @@ const ApiExplorerWrapper = (props) => { } if (!found) { - toast.error("Failed to get app data or App doesn't exist (1). Redirecting.."); + toast.error(`Failed to get API data for '${appname}' (1). Contact support@shuffler.io if this persists.`, { + "autoClose": 10000, + }) setTimeout(()=>{ navigate("/search?tab=apps"); },3000) } } else { - toast.error("Failed to get app data or App doesn't exist (2). Redirecting.."); + toast.error(`Failed to get API data for '${appname}' (2). Contact support@shuffler.io if this persists.`, { + "autoClose": 10000, + }) setTimeout(()=>{ navigate("/search?tab=apps"); },3000) @@ -177,6 +181,11 @@ const ApiExplorerWrapper = (props) => { // Fetch data when appid is available const getAppData = useCallback((appid) => { if (appid === undefined || appid === null || appid.length === 0) { + toast.error("No API data to load (4). Please contact support@shuffler.io if this persists.") + + setTimeout(() => { + navigate("/search?tab=apps") + }, 3000) return } @@ -197,9 +206,9 @@ const ApiExplorerWrapper = (props) => { .then((response) => { if (response.status !== 200) { toast.error("Failed to get app data or App doesn't exist (3). Redirecting.."); - setTimeout(()=>{ - navigate("/search?tab=apps"); - },3000) + setTimeout(() => { + navigate("/search?tab=apps") + }, 3000) return; } return response.json(); @@ -1790,7 +1799,7 @@ const Wrapper = ({children, isLoaded,isLoggedIn})=>{ return( -
+
{children}
) diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 84be501b..0af12280 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -383,6 +383,7 @@ const Docs = (defaultprops) => { if (hash.includes('?')) { hash = hash.split('?')[0] } + if (hash) { const element = document.getElementById(hash.toLowerCase()) if (element) { @@ -526,7 +527,7 @@ const Docs = (defaultprops) => { backgroundColor: theme.palette.inputColor, padding: 15, borderRadius: theme.palette?.borderRadius, - marginBottom: 30, + marginBottom: 25, display: "flex", }} > @@ -618,7 +619,8 @@ const Docs = (defaultprops) => { @@ -664,6 +666,8 @@ const Docs = (defaultprops) => { minHeight: "93vh", maxHeight: "93vh", marginTop: 70, + maxWidth: 250, + overflow: "hidden", } const fetchDocList = () => { @@ -705,8 +709,7 @@ const Docs = (defaultprops) => { // Find tags and translate them into ![]() format const imgRegex = / { fetchDocs(props.match.params.key); } - // const parseElementScroll = () => { - // const offset = 45; - // var parent = document.getElementById("markdown_wrapper_outer"); - // if (parent !== null) { - // //console.log("IN PARENT") - // var elements = parent.getElementsByTagName("h2"); - // - // const name = window.location.hash - // .slice(1, window.location.hash.length) - // .toLowerCase() - // .split("%20") - // .join(" ") - // .split("_") - // .join(" ") - // .split("-") - // .join(" ") - // .split("?")[0] - // - // //console.log(name) - // var found = false; - // for (var key in elements) { - // const element = elements[key]; - // if (element.innerHTML === undefined) { - // continue; - // } - // - // // Fix location.. - // if (element.innerHTML.toLowerCase() === name) { - // //console.log(element.offsetTop) - // element.scrollIntoView({ behavior: "smooth" }); - // //element.scrollTo({ - // // top: element.offsetTop+offset, - // // behavior: "smooth" - // //}) - // found = true; - // //element.scrollTo({ - // // top: element.offsetTop-100, - // // behavior: "smooth" - // //}) - // } - // } - // - // // H# - // if (!found) { - // elements = parent.getElementsByTagName("h3"); - // //console.log("NAMe: ", name) - // found = false; - // for (key in elements) { - // const element = elements[key]; - // if (element.innerHTML === undefined) { - // continue; - // } - // - // // Fix location.. - // if (element.innerHTML.toLowerCase() === name) { - // element.scrollIntoView({ behavior: "smooth" }); - // //element.scrollTo({ - // // top: element.offsetTop-offset, - // // behavior: "smooth" - // //}) - // found = true; - // //element.scrollTo({ - // // top: element.offsetTop-100, - // // behavior: "smooth" - // //}) - // } - // } - // } - // } - // //console.log(element) - // - // //console.log("NAME: ", name) - // //console.log(document.body.innerHTML) - // // parent = document.getElementById(parent); - // - // //var descendants = parent.getElementsByTagName(tagname); - // - // // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' }); - // - // //$(".parent").find("h2:contains('Statistics')").parent(); - // }; - const markdownStyle = { color: "rgba(255, 255, 255, 0.90)", overflow: "hidden", @@ -872,7 +793,7 @@ const Docs = (defaultprops) => { maxWidth: "100%", minWidth: "100%", overflow: "hidden", - fontSize: isMobile ? "1.3rem" : "1.1rem", + fontSize: isMobile ? "1.3rem" : "0.8rem", }; const alertNote = { @@ -1084,25 +1005,23 @@ const Docs = (defaultprops) => {
{tocLines.length > 0 ? ( -

Table Of Content

+

Table of Content

) : null}