diff --git a/.env b/.env index 2b7da284..63ac9da3 100755 --- a/.env +++ b/.env @@ -51,8 +51,8 @@ HTTP_PROXY= HTTPS_PROXY= SHUFFLE_PASS_WORKER_PROXY=TRUE SHUFFLE_PASS_APP_PROXY=TRUE -SHUFFLE_INTERNAL_HTTP_PROXY= -SHUFFLE_INTERNAL_HTTPS_PROXY= +SHUFFLE_INTERNAL_HTTP_PROXY=NOPROXY +SHUFFLE_INTERNAL_HTTPS_PROXY=NOPROXY # Timezone-handler in Orborus, Worker and Apps TZ=Europe/Amsterdam # Used to FIND the containername. cgroup v2: issue 501 diff --git a/frontend/src/codeeditor-index.css b/frontend/src/codeeditor-index.css index 3691b90d..6a8b757f 100644 --- a/frontend/src/codeeditor-index.css +++ b/frontend/src/codeeditor-index.css @@ -58,3 +58,35 @@ code { color: white !important; opacity: 0.6; } + +/* Style for checkbox */ +.ais-RefinementList-checkbox { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 16px; + height: 16px; + border: 1px solid #595b5e; + border-radius: 4px; + background-color: #27292d; + cursor: pointer; + flex-shrink: 0; +} + +.ais-RefinementList-checkbox:checked { + background-color: rgb(248, 103, 67) !important; + border-color: rgb(248, 103, 67) !important; + width: 16px; + height: 16px; +} + +.ais-RefinementList-checkbox:checked::before { + content: "\2714"; + display: block; + width: 100%; + height: 100%; + text-align: center; + line-height: 18px; + color: #fff; + font-size: 14px; +} diff --git a/frontend/src/components/AppFramework.jsx b/frontend/src/components/AppFramework.jsx index fbdf7fa0..a73be239 100644 --- a/frontend/src/components/AppFramework.jsx +++ b/frontend/src/components/AppFramework.jsx @@ -936,7 +936,7 @@ const AppFramework = (props) => { credentials: "include", }) .then((response) => { - if (response.status !== 200) { + if (response.status !== 200 || response.status !== 202) { console.log("Failed to activate") } @@ -944,7 +944,11 @@ const AppFramework = (props) => { }) .then((responseJson) => { if (responseJson.success === false) { - toast("Failed to activate the app") + var msgString = "Failed to activate the app" + if (responseJson.reason !== undefined) { + msgString += ": " + responseJson.reason + } + toast(msgString) } else { //toast("App activated for your organization! Refresh the page to use the app.") } diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index e3938c65..5e9605f2 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -603,10 +603,10 @@ const ConfigureWorkflow = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - toast("Failed to activate the app: "+responseJson.reason); - } else { - toast("Failed to activate the app"); - } + toast("Failed to activate the app: "+responseJson.reason); + } else { + toast("Failed to activate the app"); + } } else { toast("App activated for your organization!"); } diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index 402519e7..106cd18d 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -59,7 +59,7 @@ const Files = (props) => { const [downloadFolder, setDownloadFolder] = React.useState("translation_standards"); //const alert = useAlert(); - const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1"] + const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1", "response"] var upload = ""; const handleKeyDown = (event) => { @@ -395,8 +395,6 @@ const Files = (props) => { setTimeout(() => { getFiles(); }, 1500); - - console.log(responseJson); }) .catch((error) => { toast(error.toString()); @@ -759,21 +757,21 @@ const Files = (props) => { {renderTextBox && { - handleKeyDown(event); - }} - InputProps={{ - style: { - color: "white", - }, - }} - color="primary" - placeholder="File category name" - required - margin="dense" - defaultValue={""} - autoFocus - />} + onKeyPress={(event)=>{ + handleKeyDown(event); + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + placeholder="File category name" + required + margin="dense" + defaultValue={""} + autoFocus + />} { handleClose(); }} > - Admin + Organisation - + + { + handleClose(); + }} + > + Account + + + { @@ -584,7 +593,6 @@ const Header = (props) => { Use Cases - { @@ -594,16 +602,7 @@ const Header = (props) => { Creator page - - { - handleClose(); - }} - > - Settings - - - + { @@ -617,7 +616,7 @@ const Header = (props) => { - Version: 1.3.3 + Version: 1.3.4 @@ -1052,6 +1051,12 @@ const Header = (props) => { const namesplit = regionsplit[0].split("/"); regiontag = namesplit[namesplit.length - 1]; + + if (regiontag === "california") { + regiontag = "us" + } else if (regiontag === "frankfurt") { + regiontag = "fr" + } } } diff --git a/frontend/src/components/OrgHeader.jsx b/frontend/src/components/OrgHeader.jsx index 95020bb1..f9eceb64 100644 --- a/frontend/src/components/OrgHeader.jsx +++ b/frontend/src/components/OrgHeader.jsx @@ -172,6 +172,7 @@ const OrgHeader = (props) => { : theme.palette.inputColor, maxWidth: 174, maxHeight: 174, + borderRadius: theme.shape.borderRadius, }} onClick={() => { upload.click(); diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 26c566a5..4fd1edbf 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -164,6 +164,7 @@ const ParsedAction = (props) => { expansionModalOpen, setExpansionModalOpen, + apps, setEditorData, setcodedata, setAiQueryModalOpen, @@ -178,6 +179,8 @@ const ParsedAction = (props) => { const [autoCompleting, setAutocompleting] = React.useState(false); + const isIntegration = selectedAction.app_id === "integration" + useEffect(() => { if (setLastSaved !== undefined) { setLastSaved(false) @@ -1087,29 +1090,113 @@ const ParsedAction = (props) => { var authWritten = false; return (
- 0 ? +
+ {apps.map((app, appIndex) => { + if (app.categories === undefined || app.categories === null || app.categories.length === 0) { + return null + } + + var found = false + var actionname = selectedAction.name.toLowerCase() + if (actionname === "email") { + actionname = "communication" + } + + + for (var key in app.categories) { + if (app.categories[key].toLowerCase() !== actionname) { + continue + } + + found = true + break + } + + if (!found) { + return null + } + + var isAppSelected = false + const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") + if (paramIndex > -1) { + // Check the actual value and if it's the same + if (selectedAction.parameters[paramIndex].value === app.name) { + isAppSelected = true + } + } + + return ( +
{ + selectedAction.large_image = app.large_image + + /* + if (cy !== undefined) { + const foundnode = cy.getElementById(selectedAction.id) + if (foundnode !== undefined && foundnode !== null) { + foundnode.data("large_image", app.large_image) + } + } + */ + + if (paramIndex === -1) { + console.log("Couldn't find app_name parameter") + selectedAction.parameters.push({ + name: "app_name", + value: app.name, + autocompleted: false, + }) + } else { + selectedAction.parameters[paramIndex].value = app.name + } + + setSelectedAction(selectedAction) + setUpdate(Math.random()) + + }}> + + + +
+ ) + })} +
+ : null + : + + - - {selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ? -
- - Select an app you want to use - + Parameters + + + } + {selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ? +
+ + Select an app you want to use + {
) : null} + {selectedActionParameters.map((data, count) => { if (data.variant === "") { data.variant = "STATIC_VALUE"; } + if (isIntegration && data.name === "app_name") { + return null + } + // selectedAction.selectedAuthentication = e.target.value // selectedAction.authentication_id = e.target.value.id if ( @@ -1288,7 +1380,7 @@ const ParsedAction = (props) => { var staticcolor = "inherit"; var actioncolor = "inherit"; var varcolor = "inherit"; - var multiline; + var multiline if ( data.multiline !== undefined && data.multiline !== null && @@ -1297,6 +1389,11 @@ const ParsedAction = (props) => { multiline = true; } + // make data.value from array to comma separated string if it is an array + if (data.value !== undefined && data.value !== null && Array.isArray(data.value)) { + data.value = data.value.join(",") + } + if ( data.value !== undefined && data.value !== null && @@ -2003,6 +2100,13 @@ const ParsedAction = (props) => { changeActionParameter(e, count, data); } + var multi = false + if (selectedActionParameters[count].multiselect !== undefined && selectedActionParameters[count].multiselect !== null && selectedActionParameters[count].multiselect === true) { + multi = true + + selectedActionParameters[count].value = selectedActionParameters[count].value.split(",") + } + datafield = ( { - console.log("VAL: ", event.target.value) - console.log("App: ", selectedApp) + console.log("VAL: ", event.target.value) + console.log("App: ", selectedApp) const newversion = selectedApp.versions.find( (tmpApp) => tmpApp.version == event.target.value ); @@ -3051,15 +3162,6 @@ const ParsedAction = (props) => { ) : null}
-
Name @@ -3308,10 +3410,10 @@ const ParsedAction = (props) => { )} {selectedApp.name !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication !== undefined && - selectedAction.authentication.length === 0 && - requiresAuthentication ? ( + selectedAction.authentication !== null && + selectedAction.authentication !== undefined && + selectedAction.authentication.length === 0 && + requiresAuthentication ? (
{
) : null} - {showEnvironment !== undefined && showEnvironment && environments.length > 1 ? ( + {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? (
Environment { + setSelectedRegion(e.target.value); + setRegionChangeModalOpen(false); + }} + > + {regions.map((region) => { + // Set the default region if selectedOrganization.region is not set + if (selectedOrganization.region.length === 0) { + selectedOrganization.region = "europe-west2"; + } + + // Check if the current region matches the selected region + if (region === selectedOrganization.region) { + // If the region matches, set the MenuItem as selected + return ( + + {region} + + ); + } else { + // Otherwise, render a regular MenuItem + return {region}; + } + })} + + + + + + + + ); + }; const get2faCode = (userId) => { fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, { @@ -324,190 +437,249 @@ const Admin = (props) => { ] */ - //const alert = useAlert(); - const handleStatusChange = (event) => { - const { value } = event.target; - setSelectedStatus(value); + //const alert = useAlert(); + const handleStatusChange = (event) => { + const { value } = event.target; + setSelectedStatus(value); - - handleEditOrg( - "", - "", - selectedOrganization.id, - "", - {}, - {}, - value.length === 0 ? ["none"] : value, - ) - } + handleEditOrg( + "", + "", + selectedOrganization.id, + "", + {}, + {}, + value.length === 0 ? ["none"] : value, + ); + }; - // Basically just a simple way to get a generated email - // This also may help understand how to communicate with users - // both inside and outside Shuffle - // This could also be generated on the backend - const mailsendingButton = (org) => { - if (org === undefined || org === null) { - return "" - } + // Basically just a simple way to get a generated email + // This also may help understand how to communicate with users + // both inside and outside Shuffle + // This could also be generated on the backend + const mailsendingButton = (org) => { + if (org === undefined || org === null) { + return ""; + } - if (users.length === 0) { - return "" - } + if (users.length === 0) { + return ""; + } - // 1 mail based on users that have only apps - // Another based on those doing workflows - // Another based on those trying usecases(?) or templates - // - // Start based on edr, siem & ticketing - // Talk about enrichment? - // Check suggested usecases - // Check suggested workflows - var your_apps = "- Connecting " + // 1 mail based on users that have only apps + // Another based on those doing workflows + // Another based on those trying usecases(?) or templates + // + // Start based on edr, siem & ticketing + // Talk about enrichment? + // Check suggested usecases + // Check suggested workflows + var your_apps = "- Connecting "; - var subject_add = 0 - var subject = "POC to automate " + var subject_add = 0; + var subject = "POC to automate "; - if (org.security_framework !== undefined && org.security_framework !== null) { - if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") { - your_apps += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1) + ", " + if ( + org.security_framework !== undefined && + org.security_framework !== null + ) { + if ( + org.security_framework.cases.name !== undefined && + org.security_framework.cases.name !== null && + org.security_framework.cases.name !== "" + ) { + your_apps += + org.security_framework.cases.name + .replace("_", " ", -1) + .replace(" API", "", -1) + ", "; - if (subject_add < 2) { - if (subject_add === 1) { - subject += " and " - } + if (subject_add < 2) { + if (subject_add === 1) { + subject += " and "; + } - subject_add += 1 - subject += org.security_framework.cases.name.replace("_", " ", -1).replace(" API", "", -1) - } - } + subject_add += 1; + subject += org.security_framework.cases.name + .replace("_", " ", -1) + .replace(" API", "", -1); + } + } - if (org.security_framework.siem.name !== undefined && org.security_framework.siem.name !== null && org.security_framework.siem.name !== "") { - your_apps += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1) + ", " - if (subject_add < 2) { - if (subject_add === 1) { - subject += " and " - } + if ( + org.security_framework.siem.name !== undefined && + org.security_framework.siem.name !== null && + org.security_framework.siem.name !== "" + ) { + your_apps += + org.security_framework.siem.name + .replace("_", " ", -1) + .replace(" API", "", -1) + ", "; + if (subject_add < 2) { + if (subject_add === 1) { + subject += " and "; + } - subject_add += 1 - subject += org.security_framework.siem.name.replace("_", " ", -1).replace(" API", "", -1) - } - } + subject_add += 1; + subject += org.security_framework.siem.name + .replace("_", " ", -1) + .replace(" API", "", -1); + } + } - if (org.security_framework.communication.name !== undefined && org.security_framework.communication.name !== null && org.security_framework.communication.name !== "") { - your_apps += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1) + ", " + if ( + org.security_framework.communication.name !== undefined && + org.security_framework.communication.name !== null && + org.security_framework.communication.name !== "" + ) { + your_apps += + org.security_framework.communication.name + .replace("_", " ", -1) + .replace(" API", "", -1) + ", "; - if (subject_add < 2) { - if (subject_add === 1) { - subject += " and " - } + if (subject_add < 2) { + if (subject_add === 1) { + subject += " and "; + } - subject_add += 1 - subject += org.security_framework.communication.name.replace("_", " ", -1).replace(" API", "", -1) - } - } + subject_add += 1; + subject += org.security_framework.communication.name + .replace("_", " ", -1) + .replace(" API", "", -1); + } + } - if (org.security_framework.edr.name !== undefined && org.security_framework.edr.name !== null && org.security_framework.edr.name !== "") { - your_apps += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1) + ", " + if ( + org.security_framework.edr.name !== undefined && + org.security_framework.edr.name !== null && + org.security_framework.edr.name !== "" + ) { + your_apps += + org.security_framework.edr.name + .replace("_", " ", -1) + .replace(" API", "", -1) + ", "; - if (subject_add < 2) { - if (subject_add === 1) { - subject += " and " - } + if (subject_add < 2) { + if (subject_add === 1) { + subject += " and "; + } - subject_add += 1 - subject += org.security_framework.edr.name.replace("_", " ", -1).replace(" API", "", -1) - } - } + subject_add += 1; + subject += org.security_framework.edr.name + .replace("_", " ", -1) + .replace(" API", "", -1); + } + } - if (org.security_framework.intel.name !== undefined && org.security_framework.intel.name !== null && org.security_framework.intel.name !== "") { - your_apps += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1) + ", " + if ( + org.security_framework.intel.name !== undefined && + org.security_framework.intel.name !== null && + org.security_framework.intel.name !== "" + ) { + your_apps += + org.security_framework.intel.name + .replace("_", " ", -1) + .replace(" API", "", -1) + ", "; - if (subject_add < 2) { - if (subject_add === 1) { - subject += " and " - } + if (subject_add < 2) { + if (subject_add === 1) { + subject += " and "; + } - subject_add += 1 - subject += org.security_framework.intel.name.replace("_", " ", -1).replace(" API", "", -1) - } - } + subject_add += 1; + subject += org.security_framework.intel.name + .replace("_", " ", -1) + .replace(" API", "", -1); + } + } + // Remove comma + //subject += "?" + your_apps = your_apps.substring(0, your_apps.length - 2); + } - // Remove comma - //subject += "?" - your_apps = your_apps.substring(0, your_apps.length - 2) - } + // Add usecases they may not have tried (from recommendations): org.priorities where item type is usecase + var usecases = "- Building usecases like "; + const active_usecase = org.priorities.filter( + (item) => item.type === "usecase" && item.active === true, + ); + if (active_usecase.length > 0) { + for (var i = 0; i < active_usecase.length; i++) { + if (active_usecase[i].name.includes("Suggested Usecase: ")) { + usecases += + active_usecase[i].name.replace("Suggested Usecase: ", "", -1) + + ", "; + } else { + usecases += active_usecase[i].name + ", "; + } + } + usecases = usecases.substring(0, usecases.length - 2); + } - // Add usecases they may not have tried (from recommendations): org.priorities where item type is usecase - var usecases = "- Building usecases like " - const active_usecase = org.priorities.filter((item) => item.type === "usecase" && item.active === true) - if (active_usecase.length > 0) { - for (var i = 0; i < active_usecase.length; i++) { - if (active_usecase[i].name.includes("Suggested Usecase: ")) { - usecases += active_usecase[i].name.replace("Suggested Usecase: ", "", -1) + ", " - } else { - usecases += active_usecase[i].name + ", " - } - } + if (your_apps.length <= 15) { + your_apps = ""; + } - usecases = usecases.substring(0, usecases.length - 2) - } + if (usecases.length <= 30) { + usecases = ""; + } - if (your_apps.length <= 15) { - your_apps = "" - } + var workflow_amount = "a few"; + var admins = ""; - if (usecases.length <= 30) { - usecases = "" - } + // Loop users + var lastLogin = 0; + for (var i = 0; i < users.length; i++) { + if (users[i].username.includes("shuffler")) { + continue; + } - var workflow_amount = "a few" - var admins = "" + if (users[i].role === "admin") { + admins += users[i].username + ","; + } - // Loop users - var lastLogin = 0 - for (var i = 0; i < users.length; i++) { - if (users[i].username.includes("shuffler")) { - continue - } + const data = users[i]; + for (var i = 0; i < data.login_info.length; i++) { + if (data.login_info[i].timestamp > lastLogin) { + lastLogin = data.login_info[i].timestamp; + } + } + } - if (users[i].role === "admin") { - admins += users[i].username + "," - } + // Remove last comma + admins = admins.substring(0, admins.length - 1); - const data = users[i] - for (var i = 0; i < data.login_info.length; i++) { - if (data.login_info[i].timestamp > lastLogin) { - lastLogin = data.login_info[i].timestamp - } - } - } + if (your_apps.length > 5) { + your_apps += "%0D%0A"; + } + if (usecases.length > 5) { + usecases += "%0D%0A"; + } - // Remove last comma - admins = admins.substring(0, admins.length - 1) + // Get drift username from userdata.username before @ in email + const username = userdata.username.substring( + 0, + userdata.username.indexOf("@"), + ); - if (your_apps.length > 5) { - your_apps += "%0D%0A" - } + // Check if timestamp is more than 2 weeks ago and add "a while back" to the message + const timeComparison = 1209600; + const extra_timestamp_text = + lastLogin === 0 + ? 0 + : Date.now() / 1000 - lastLogin > timeComparison + ? " a while back" + : ""; + console.log("LAST LOGIN: " + lastLogin, extra_timestamp_text); - if (usecases.length > 5) { - usecases += "%0D%0A" - } + // Check if cloud sync is active, and if so, add a message about it + const cloudSyncInfo = + selectedOrganization.cloud_sync === true + ? "- Scale your onprem installation" + : ""; - // Get drift username from userdata.username before @ in email - const username = userdata.username.substring(0, userdata.username.indexOf("@")) - - // Check if timestamp is more than 2 weeks ago and add "a while back" to the message - const timeComparison = 1209600 - const extra_timestamp_text = lastLogin === 0 ? 0 : (Date.now()/1000 - lastLogin) > timeComparison ? " a while back" : "" - console.log("LAST LOGIN: " + lastLogin, extra_timestamp_text) - - // Check if cloud sync is active, and if so, add a message about it - const cloudSyncInfo = selectedOrganization.cloud_sync === true ? "- Scale your onprem installation" : "" - - var body = `Hey,%0D%0A%0D%0AI noticed you tried to use Shuffle${extra_timestamp_text}, and thought you may be interested in a POC. It looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting what you wanted out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A + var body = `Hey,%0D%0A%0D%0AI noticed you tried to use Shuffle${extra_timestamp_text}, and thought you may be interested in a POC. It looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting what you wanted out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A Some of the things we can help with:%0D%0A ${your_apps} @@ -516,18 +688,17 @@ ${usecases} - Multi-Tenancy and creating special usecases%0D%0A ${cloudSyncInfo}%0D%0A -If you're interested, please let me know a time that works for you, or set up a call here: https://drift.me/${username}` - - return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}` - } +If you're interested, please let me know a time that works for you, or set up a call here: https://drift.me/${username}`; + return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}`; + }; const changeDistribution = (data) => { - //changeDistributed(data, !isDistributed) - console.log("Should change distribution to be shared among suborgs") - - editAuthenticationConfig(data.id, "suborg_distribute") - } + //changeDistributed(data, !isDistributed) + console.log("Should change distribution to be shared among suborgs"); + + editAuthenticationConfig(data.id, "suborg_distribute"); + }; const deleteAuthentication = (data) => { toast("Deleting auth " + data.label); @@ -553,7 +724,7 @@ If you're interested, please let me know a time that works for you, or set up a }, 1000); //toast("Successfully deleted authentication!") } - }) + }), ) .catch((error) => { console.log("Error in userdata: ", error); @@ -590,19 +761,24 @@ If you're interested, please let me know a time that works for you, or set up a }, 1500); //toast("Successfully stopped schedule!") } - }) + }), ) .catch((error) => { console.log("Error in userdata: ", error); }); }; - - if (userdata.support === true && selectedOrganization.id !== "" && selectedOrganization.id !== undefined && selectedOrganization.id !== null && selectedOrganization.id !== userdata.active_org.id) { - toast("Refreshing window to fix org support access") - window.location.reload() - return null - } + if ( + userdata.support === true && + selectedOrganization.id !== "" && + selectedOrganization.id !== undefined && + selectedOrganization.id !== null && + selectedOrganization.id !== userdata.active_org.id + ) { + toast("Refreshing window to fix org support access"); + window.location.reload(); + return null; + } const handleVerify2FA = (userId, code) => { const data = { @@ -681,7 +857,7 @@ If you're interested, please let me know a time that works for you, or set up a } else { console.log("Cloud sync fail?"); toast( - "Failed stopping sync. Try again, and contact support if this persists." + "Failed stopping sync. Try again, and contact support if this persists.", ); } @@ -748,7 +924,7 @@ If you're interested, please let me know a time that works for you, or set up a } else { toast("Cloud Syncronization successfully set up!"); setOrgSyncResponse( - "Successfully started syncronization. Cloud features you now have access to can be seen below." + "Successfully started syncronization. Cloud features you now have access to can be seen below.", ); } @@ -783,22 +959,21 @@ If you're interested, please let me know a time that works for you, or set up a .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - // Check if .reason exists - if (responseJson.reason !== undefined) { - toast("Failed changing authentication: " + responseJson.reason); - } else { - toast("Failed changing authentication"); - } + // Check if .reason exists + if (responseJson.reason !== undefined) { + toast("Failed changing authentication: " + responseJson.reason); + } else { + toast("Failed changing authentication"); + } } else { //toast("Successfully password!") setSelectedUserModalOpen(false); getAppAuthentication(); - - setSelectedAuthentication({}); - setSelectedAuthenticationModalOpen(false); + setSelectedAuthentication({}); + setSelectedAuthenticationModalOpen(false); } - }) + }), ) .catch((error) => { toast("Err: " + error.toString()); @@ -812,9 +987,8 @@ If you're interested, please let me know a time that works for you, or set up a image, defaults, sso_config, - lead_info, + lead_info, ) => { - const data = { name: name, description: description, @@ -822,7 +996,7 @@ If you're interested, please let me know a time that works for you, or set up a image: image, defaults: defaults, sso_config: sso_config, - lead_info: lead_info, + lead_info: lead_info, }; const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; @@ -842,11 +1016,15 @@ If you're interested, please let me know a time that works for you, or set up a if (responseJson["success"] === false) { toast("Failed updating org: ", responseJson.reason); } else { - if (lead_info === undefined || lead_info === null || lead_info === []) { - toast("Successfully edited org!"); - } + if ( + lead_info === undefined || + lead_info === null || + lead_info === [] + ) { + toast("Successfully edited org!"); + } } - }) + }), ) .catch((error) => { toast("Err: " + error.toString()); @@ -856,8 +1034,11 @@ If you're interested, please let me know a time that works for you, or set up a const editAuthenticationConfig = (id, parentAction) => { const data = { id: id, - action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere", - } + action: + parentAction !== undefined && parentAction !== null + ? parentAction + : "assign_everywhere", + }; const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config"; @@ -883,7 +1064,7 @@ If you're interested, please let me know a time that works for you, or set up a getAppAuthentication(); }, 1000); } - }) + }), ) .catch((error) => { toast("Err: " + error.toString()); @@ -915,9 +1096,7 @@ If you're interested, please let me know a time that works for you, or set up a toast("Failed creating suborg. Please try again"); } } else { - toast( - "Successfully created suborg. Reloading in 3 seconds!" - ); + toast("Successfully created suborg. Reloading in 3 seconds!"); setSelectedUserModalOpen(false); setTimeout(() => { @@ -927,7 +1106,7 @@ If you're interested, please let me know a time that works for you, or set up a setOrgName(""); setModalOpen(false); - }) + }), ) .catch((error) => { toast("Err: " + error.toString()); @@ -961,7 +1140,7 @@ If you're interested, please let me know a time that works for you, or set up a toast("Successfully updated password!"); setSelectedUserModalOpen(false); } - }) + }), ) .catch((error) => { toast("Err: " + error.toString()); @@ -990,8 +1169,10 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { toast("Failed to deactivate user: " + responseJson.reason); - } else if (responseJson.success === false) { - toast("Failed to deactivate user. Please contact support@shuffler.io if this persists.") + } else if (responseJson.success === false) { + toast( + "Failed to deactivate user. Please contact support@shuffler.io if this persists.", + ); } else { toast("Changed activation for user " + data.id); } @@ -1002,26 +1183,29 @@ If you're interested, please let me know a time that works for you, or set up a }); }; - - 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; - } - } + 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; + } + } if (orgId.length === 0) { - toast("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout."); + 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", credentials: "include", @@ -1037,7 +1221,9 @@ If you're interested, please let me know a time that works for you, or set up a }) .then((responseJson) => { if (responseJson["success"] === false) { - toast("Failed getting your org. If this persists, please contact support."); + toast( + "Failed getting your org. If this persists, please contact support.", + ); } else { if ( responseJson.sync_features === undefined || @@ -1046,64 +1232,67 @@ If you're interested, please let me know a time that works for you, or set up a responseJson.sync_features = {}; } - if (responseJson.lead_info !== undefined && responseJson.lead_info !== null) { - var leads = [] - if (responseJson.lead_info.contacted) { - leads.push("contacted") - } + if ( + responseJson.lead_info !== undefined && + responseJson.lead_info !== null + ) { + var leads = []; + if (responseJson.lead_info.contacted) { + leads.push("contacted"); + } - if (responseJson.lead_info.customer) { - leads.push("customer") - } + if (responseJson.lead_info.customer) { + leads.push("customer"); + } - if (responseJson.lead_info.old_customer) { - leads.push("old customer") - } + if (responseJson.lead_info.old_customer) { + leads.push("old customer"); + } - if (responseJson.lead_info.old_lead) { - leads.push("old lead") - } + if (responseJson.lead_info.old_lead) { + leads.push("old lead"); + } - if (responseJson.lead_info.tech_partner) { - leads.push("tech partner") - } + if (responseJson.lead_info.tech_partner) { + leads.push("tech partner"); + } - if (responseJson.lead_info.creator) { - leads.push("creator") - } + if (responseJson.lead_info.creator) { + leads.push("creator"); + } - if (responseJson.lead_info.opensource) { - leads.push("open source") - } + if (responseJson.lead_info.opensource) { + leads.push("open source"); + } - if (responseJson.lead_info.demo_done) { - leads.push("demo done") - } + if (responseJson.lead_info.demo_done) { + leads.push("demo done"); + } - if (responseJson.lead_info.pov) { - leads.push("pov") - } + if (responseJson.lead_info.pov) { + leads.push("pov"); + } - if (responseJson.lead_info.lead) { - leads.push("lead") - } + if (responseJson.lead_info.lead) { + leads.push("lead"); + } - if (responseJson.lead_info.student) { - leads.push("student") - } + if (responseJson.lead_info.student) { + leads.push("student"); + } - if (responseJson.lead_info.internal) { - leads.push("internal") - } + if (responseJson.lead_info.internal) { + leads.push("internal"); + } - if (responseJson.lead_info.sub_org) { - leads.push("sub_org") - } + if (responseJson.lead_info.sub_org) { + leads.push("sub_org"); + } - setSelectedStatus(leads) - } + setSelectedStatus(leads); + } - setSelectedOrganization(responseJson) + setSelectedOrganization(responseJson); var lists = { active: { triggers: [], @@ -1134,12 +1323,13 @@ If you're interested, please let me know a time that works for you, or set up a }; const handleGetSubOrgs = (orgId) => { - if (orgId.length === 0) { - toast("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout."); + toast( + "Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.", + ); return; } - + fetch(`${globalUrl}/api/v1/orgs/${orgId}/suborgs`, { method: "GET", credentials: "include", @@ -1147,76 +1337,80 @@ If you're interested, please let me know a time that works for you, or set up a "Content-Type": "application/json", }, }) - .then((response) => { - if (!response.ok) { - throw new Error('Failed to fetch sub organizations'); - } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success === false) { - //toast("Failed getting your org. If this persists, please contact support."); - } else { - const { subOrgs, parentOrg } = responseJson; - setSubOrgs(subOrgs); - setParentOrg(parentOrg); - } - }) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch sub organizations"); + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + //toast("Failed getting your org. If this persists, please contact support."); + } else { + const { subOrgs, parentOrg } = responseJson; + setSubOrgs(subOrgs); + setParentOrg(parentOrg); + } + }) .catch((error) => { console.log("Error getting sub orgs: ", error); //toast("Error getting sub organizations"); }); }; - const handleClickChangeOrg = (orgId) => { - // Don't really care about the logout - //name: org.name, - //orgId = "asd" - const data = { - org_id: orgId, - } - - localStorage.setItem("globalUrl", "") - localStorage.setItem("getting_started_sidebar", "open"); - - fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { - mode: 'cors', - credentials: 'include', - crossDomain: true, - method: 'POST', - body: JSON.stringify(data), - withCredentials: true, - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - }) - .then(function(response) { - if (response.status !== 200) { - console.log("Error in response") - } - - return response.json(); - }).then(function(responseJson) { - if (responseJson.success === true) { - if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) { - localStorage.setItem("globalUrl", responseJson.region_url) - //globalUrl = responseJson.region_url - } - - setTimeout(() => { - window.location.reload() - }, 2000) - toast("Successfully changed active organization - refreshing!") - } else { - toast("Failed changing org: "+responseJson.reason) - } - }) - .catch(error => { - console.log("error changing: ", error) - //removeCookie("session_token", {path: "/"}) - }) - } + const handleClickChangeOrg = (orgId) => { + // Don't really care about the logout + //name: org.name, + //orgId = "asd" + const data = { + org_id: orgId, + }; + localStorage.setItem("globalUrl", ""); + localStorage.setItem("getting_started_sidebar", "open"); + + fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { + mode: "cors", + credentials: "include", + crossDomain: true, + method: "POST", + body: JSON.stringify(data), + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + if ( + responseJson.region_url !== undefined && + responseJson.region_url !== null && + responseJson.region_url.length > 0 + ) { + localStorage.setItem("globalUrl", responseJson.region_url); + //globalUrl = responseJson.region_url + } + + setTimeout(() => { + window.location.reload(); + }, 2000); + toast("Successfully changed active organization - refreshing!"); + } else { + toast("Failed changing org: " + responseJson.reason); + } + }) + .catch((error) => { + console.log("error changing: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; const inviteUser = (data) => { //console.log("INPUT: ", data); @@ -1243,21 +1437,27 @@ If you're interested, please let me know a time that works for you, or set up a response.json().then((responseJson) => { if (responseJson["success"] === false) { setLoginInfo("Error: " + responseJson.reason); - toast("Failed to send email (2). Please try again and contact support if this persists.") + toast( + "Failed to send email (2). Please try again and contact support if this persists.", + ); } else { setLoginInfo(""); setModalOpen(false); setTimeout(() => { getUsers(); }, 1000); - - toast("Invite sent! They will show up in the list when they have accepted the invite.") + + toast( + "Invite sent! They will show up in the list when they have accepted the invite.", + ); } - }) + }), ) .catch((error) => { console.log("Error in userdata: ", error); - toast("Failed to send email. Please try again and contact support if this persists.") + toast( + "Failed to send email. Please try again and contact support if this persists.", + ); }); }; @@ -1289,7 +1489,7 @@ If you're interested, please let me know a time that works for you, or set up a getUsers(); }, 1000); } - }) + }), ) .catch((error) => { console.log("Error in userdata: ", error); @@ -1343,7 +1543,7 @@ If you're interested, please let me know a time that works for you, or set up a getEnvironments(); }, 1500); } - }) + }), ) .catch((error) => { console.log("Error in backend data: ", error); @@ -1370,7 +1570,7 @@ If you're interested, please let me know a time that works for you, or set up a setModalOpen(false); getEnvironments(); } - }) + }), ) .catch((error) => { console.log("Error when deleting: ", error); @@ -1378,14 +1578,11 @@ If you're interested, please let me know a time that works for you, or set up a }; const rerunCloudWorkflows = (environment) => { - toast("Starting execution reruns. This can run in the background.") - fetch( - `${globalUrl}/api/v1/environments/${environment.id}/rerun`, - { - method: "GET", - credentials: "include", - } - ) + toast("Starting execution reruns. This can run in the background."); + fetch(`${globalUrl}/api/v1/environments/${environment.id}/rerun`, { + method: "GET", + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); @@ -1409,14 +1606,16 @@ If you're interested, please let me know a time that works for you, or set up a const abortEnvironmentWorkflows = (environment) => { //console.log("Aborting all workflows started >10 minutes ago, not finished"); - toast("Clearing the queue - this may take some time. A new will show up when finished.") + toast( + "Clearing the queue - this may take some time. A new will show up when finished.", + ); fetch( `${globalUrl}/api/v1/environments/${environment.id}/stop?deleteall=true`, { method: "GET", credentials: "include", - } + }, ) .then((response) => { if (response.status !== 200) { @@ -1424,9 +1623,9 @@ If you're interested, please let me know a time that works for you, or set up a toast("Failed aborting dangling workflows"); return; } else { - toast("Successfully cleared the queue") + toast("Successfully cleared the queue"); - getEnvironments() + getEnvironments(); } return response.json(); @@ -1506,7 +1705,7 @@ If you're interested, please let me know a time that works for you, or set up a setModalOpen(false); getEnvironments(); } - }) + }), ) .catch((error) => { console.log("Error when deleting: ", error); @@ -1541,7 +1740,7 @@ If you're interested, please let me know a time that works for you, or set up a setModalOpen(false); getEnvironments(); } - }) + }), ) .catch((error) => { console.log("Error in userdata: ", error); @@ -1550,6 +1749,46 @@ If you're interested, please let me know a time that works for you, or set up a var localData = ""; + const handleDeleteAccount = (userID) => { + if (userID === undefined || userID === null || userID === "") { + return; + } + + const url = `${globalUrl}/api/v1/users/${userID}/remove`; + fetch(url, { + mode: "cors", + method: "DELETE", + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + toast.success( + "Deleted their account. Would reload users in a few seconds.", + ); + + setTimeout(() => { + getUsers(); + }); + } else { + toast.error(`${data.reason}`); + } + }) + .catch((error) => { + console.error( + "There was a problem with deleting the account. Please try again:", + error, + ); + toast.error( + "There was a problem with the delete request. Please try again", + ); + }); + }; const getSchedules = () => { fetch(globalUrl + "/api/v1/workflows/schedules", { @@ -1627,18 +1866,30 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { setEnvironments(responseJson); - // Helper info for users in case they have a large queue and don't know about queue flushing - if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { - for (var i = 0; i < responseJson.length; i++) { - const env = responseJson[i]; + // Helper info for users in case they have a large queue and don't know about queue flushing + if ( + responseJson !== undefined && + responseJson !== null && + responseJson.length > 0 + ) { + for (var i = 0; i < responseJson.length; i++) { + const env = responseJson[i]; - // Check if queuesize is too large - if (env.queue !== undefined && env.queue !== null && env.queue > 100) { - toast("Queue size for " + env.name + " is very large. We recommend you to reduce it by flushing the queue before continuing."); - break - } - } - } + // Check if queuesize is too large + if ( + env.queue !== undefined && + env.queue !== null && + env.queue > 100 + ) { + toast( + "Queue size for " + + env.name + + " is very large. We recommend you to reduce it by flushing the queue before continuing.", + ); + break; + } + } + } }) .catch((error) => { toast(error.toString()); @@ -1737,7 +1988,7 @@ If you're interested, please let me know a time that works for you, or set up a const admin_views = { 0: "organization", 1: "cloud_sync", - 2: "priorities", + 2: "priorities", 3: "billing", 4: "branding", }; @@ -1777,7 +2028,7 @@ If you're interested, please let me know a time that works for you, or set up a navigate(`/admin?tab=${views[newValue]}`); setModalUser({}); - } + }; if (firstRequest) { setFirstRequest(false); @@ -1800,26 +2051,26 @@ If you're interested, please let me know a time that works for you, or set up a const adminTab = params["admin_tab"]; if (adminTab !== null && adminTab !== undefined) { - for (var key in Object.keys(admin_views)) { + for (var key in Object.keys(admin_views)) { const value = admin_views[key]; if (value === adminTab) { - setAdminTab(parseInt(key)); + setAdminTab(parseInt(key)); setConfig("", 0); break; } } - } else { - const foundTab = params["tab"]; - if (foundTab !== null && foundTab !== undefined) { - for (var key in Object.keys(views)) { - const value = views[key]; - if (value === foundTab) { - setConfig("", key); - break; - } - } - } - } + } else { + const foundTab = params["tab"]; + if (foundTab !== null && foundTab !== undefined) { + for (var key in Object.keys(views)) { + const value = views[key]; + if (value === foundTab) { + setConfig("", key); + break; + } + } + } + } } } @@ -1871,11 +2122,11 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { toast("Failed setting user: " + responseJson.reason); - } else if (responseJson.success === false) { - toast("Failed to update user") + } else if (responseJson.success === false) { + toast("Failed to update user"); } else { //toast("Set the user field " + field + " to " + value); - toast("Successfully updated user field " + field) + toast("Successfully updated user field " + field); if (field !== "suborgs") { setSelectedUserModalOpen(false); @@ -1891,7 +2142,7 @@ If you're interested, please let me know a time that works for you, or set up a const userId = user.id; const data = { user_id: userId }; - toast("Generating new API key") + toast("Generating new API key"); var fetchdata = { method: "POST", @@ -1900,13 +2151,13 @@ If you're interested, please let me know a time that works for you, or set up a Accept: "application/json", }, credentials: "include", - } + }; if (userId === userdata.id) { - fetchdata.method = "GET" - } else { - fetchdata.body = JSON.stringify(data) - } + fetchdata.method = "GET"; + } else { + fetchdata.body = JSON.stringify(data); + } fetch(globalUrl + "/api/v1/generateapikey", fetchdata) .then((response) => { @@ -1948,62 +2199,80 @@ If you're interested, please let me know a time that works for you, or set up a > - Edit authentication for {selectedAuthentication.app.name.replaceAll("_", " ")} ( + Edit authentication for{" "} + {selectedAuthentication.app.name.replaceAll("_", " ")} ( {selectedAuthentication.label}) - - You can not see the previous values for an authentication while editing. This is to keep your data secure. You can overwrite one- or multiple fields at a time. - + + You can not see the previous values for an authentication while + editing. This is to keep your data secure. You can overwrite one- or + multiple fields at a time. + - - Authentication Label - - { - selectedAuthentication.label = e.target.value - }} - /> + + Authentication Label + + { + selectedAuthentication.label = e.target.value; + }} + /> - - {selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app" ? -
- - 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. - -
- : null } + + {selectedAuthentication.type === "oauth" || + selectedAuthentication.type === "oauth2" || + selectedAuthentication.type === "oauth2-app" ? ( +
+ + 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. + +
+ ) : 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 - } - } + {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 ( @@ -2052,15 +2321,15 @@ If you're interested, please let me know a time that works for you, or set up a style={{ borderRadius: "0px" }} onClick={() => { var error = false; - var fails = 0 + var fails = 0; for (var key in authenticationFields) { const item = authenticationFields[key]; if (item.value.length === 0) { - fails += 1 + fails += 1; console.log("ITEM: ", item); //var currentnode = cy.getElementById(data.id) var textfield = document.getElementById( - `authentication-${key}` + `authentication-${key}`, ); if (textfield !== null && textfield !== undefined) { console.log("HANDLE ERROR FOR KEY ", key); @@ -2069,13 +2338,17 @@ If you're interested, please let me know a time that works for you, or set up a } } - if (selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app") { - selectedAuthentication.fields = [] - } + if ( + selectedAuthentication.type === "oauth" || + selectedAuthentication.type === "oauth2" || + selectedAuthentication.type === "oauth2-app" + ) { + selectedAuthentication.fields = []; + } if (error && fails === authenticationFields.length) { - toast("Updating auth with new name only") - saveAuthentication(selectedAuthentication); + toast("Updating auth with new name only"); + saveAuthentication(selectedAuthentication); } else { toast("Saving new version of this authentication"); selectedAuthentication.fields = authenticationFields; @@ -2144,20 +2417,16 @@ If you're interested, please let me know a time that works for you, or set up a ) : null; - - - - const editUserModal = ( { setSelectedUserModalOpen(false); - setImage2FA(""); - setValue2FA(""); - setSecret2FA(""); - setShow2faSetup(false); + setImage2FA(""); + setValue2FA(""); + setSecret2FA(""); + setShow2faSetup(false); }} PaperProps={{ style: { @@ -2305,6 +2574,80 @@ If you're interested, please let me know a time that works for you, or set up a ? "Disable 2FA" : "Enable 2FA"} + + {isCloud && userdata.support && selectedUser.id != userdata.id ? ( + + ) : null} + + {showDeleteAccountTextbox ? ( + { + setDeleteAccountText(e.target.value); + }} + /> + ) : null}
{show2faSetup ? (
{ const [expanded, setExpanded] = React.useState(false); - const [showEdit, setShowEdit] = React.useState(false); - const [newValue, setNewValue] = React.useState(-100); + const [showEdit, setShowEdit] = React.useState(false); + const [newValue, setNewValue] = React.useState(-100); const primary = props.data.primary; const secondary = props.data.secondary; const primaryIcon = props.data.icon; - const secondaryIcon = props.data.active ? + const secondaryIcon = props.data.active ? ( - : + ) : ( + ); - const submitFeatureEdit = (sync_features) => { - if (!userdata.support) { - console.log("User does not have support access and can't edit features"); - return - } + const submitFeatureEdit = (sync_features) => { + if (!userdata.support) { + console.log( + "User does not have support access and can't edit features", + ); + return; + } - sync_features.editing = true - const data = { - org_id: selectedOrganization.id, - sync_features: sync_features, - }; + sync_features.editing = true; + const data = { + org_id: selectedOrganization.id, + sync_features: sync_features, + }; - const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; - 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 updating org: ", responseJson.reason); - } else { - toast("Successfully edited org!"); - } - }) - ) - .catch((error) => { - toast("Err: " + error.toString()); - }); - } + const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; + 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 updating org: ", responseJson.reason); + } else { + toast("Successfully edited org!"); + } + }), + ) + .catch((error) => { + toast("Err: " + error.toString()); + }); + }; - const enableFeature = () => { - console.log("Enabling "+primary) + const enableFeature = () => { + console.log("Enabling " + primary); - console.log(selectedOrganization.sync_features) - // Check if primary is in sync_features - var tmpprimary = primary.replaceAll(" ", "_") - if (!(tmpprimary in selectedOrganization.sync_features)) { - console.log("Primary not in sync_features: "+tmpprimary) - return - } + console.log(selectedOrganization.sync_features); + // Check if primary is in sync_features + var tmpprimary = primary.replaceAll(" ", "_"); + if (!(tmpprimary in selectedOrganization.sync_features)) { + console.log("Primary not in sync_features: " + tmpprimary); + return; + } - if (props.data.active) { - selectedOrganization.sync_features[tmpprimary].active = false - } else { - selectedOrganization.sync_features[tmpprimary].active = true - } + if (props.data.active) { + selectedOrganization.sync_features[tmpprimary].active = false; + } else { + selectedOrganization.sync_features[tmpprimary].active = true; + } - setSelectedOrganization(selectedOrganization) - forceUpdate(Math.random()) - submitFeatureEdit(selectedOrganization.sync_features) - } - - const submitEdit = (e) => { - e.preventDefault(); - e.stopPropagation(); + setSelectedOrganization(selectedOrganization); + forceUpdate(Math.random()); + submitFeatureEdit(selectedOrganization.sync_features); + }; - // Check if primary is in sync_features - var tmpprimary = primary.replaceAll(" ", "_") - if (!(tmpprimary in selectedOrganization.sync_features)) { - console.log("Primary not in sync_features: "+tmpprimary) - return - } + const submitEdit = (e) => { + e.preventDefault(); + e.stopPropagation(); - // Make it into a number - var tmp = parseInt(newValue) - if (isNaN(tmp)) { - console.log("Not a number: "+newValue) - return - } + // Check if primary is in sync_features + var tmpprimary = primary.replaceAll(" ", "_"); + if (!(tmpprimary in selectedOrganization.sync_features)) { + console.log("Primary not in sync_features: " + tmpprimary); + return; + } - selectedOrganization.sync_features[tmpprimary].limit = tmp + // Make it into a number + var tmp = parseInt(newValue); + if (isNaN(tmp)) { + console.log("Not a number: " + newValue); + return; + } - setSelectedOrganization(selectedOrganization) - forceUpdate(Math.random()) - submitFeatureEdit(selectedOrganization.sync_features) - } + selectedOrganization.sync_features[tmpprimary].limit = tmp; + + setSelectedOrganization(selectedOrganization); + forceUpdate(Math.random()); + submitFeatureEdit(selectedOrganization.sync_features); + }; return ( - + { - setExpanded(!expanded); - }} - > + style={{ cursor: "pointer" }} + onClick={() => { + setExpanded(!expanded); + }} + > {primaryIcon} - {isCloud && userdata.support === true ? - - { - e.preventDefault(); - e.stopPropagation(); + {isCloud && userdata.support === true ? ( + + { + e.preventDefault(); + e.stopPropagation(); - if (showEdit) { - setShowEdit(false) - return - } + if (showEdit) { + setShowEdit(false); + return; + } - console.log("Edit") - - setShowEdit(true) - }} - /> - - : null} - - { - if (!isCloud || userdata.support !== true) { - return - } + console.log("Edit"); - e.preventDefault(); - e.stopPropagation(); + setShowEdit(true); + }} + /> + + ) : null} + + { + if (!isCloud || userdata.support !== true) { + return; + } - enableFeature() - }} - > - {secondaryIcon} - - + e.preventDefault(); + e.stopPropagation(); + + enableFeature(); + }} + > + {secondaryIcon} + + - {expanded ? + {expanded ? (
Usage:  @@ -2573,50 +2918,59 @@ If you're interested, please let me know a time that works for you, or set up a "Unlimited" ) : ( - {props.data.usage} / {props.data.limit === "" ? "Unlimited" : props.data.limit} + {props.data.usage} /{" "} + {props.data.limit === "" ? "Unlimited" : props.data.limit} )} {/* Data sharing: {props.data.data_collection} */} - Description: {secondary} + + Description: {secondary} +
- : null} + ) : null} - - {showEdit ? - { - console.log("Submit") - submitEdit(e) - }}> - - - { - setNewValue(event.target.value) - }} - /> - - - - : null} + {showEdit ? ( + { + console.log("Submit"); + submitEdit(e); + }} + > + + { + setNewValue(event.target.value); + }} + /> + + + + ) : null}
); @@ -2667,7 +3021,7 @@ If you're interested, please let me know a time that works for you, or set up a cloud sync @@ -2711,7 +3065,7 @@ If you're interested, please let me know a time that works for you, or set up a enableCloudSync( cloudSyncApikey, selectedOrganization, - selectedOrganization.cloud_sync + selectedOrganization.cloud_sync, ); }} color="primary" @@ -2747,25 +3101,37 @@ If you're interested, please let me know a time that works for you, or set up a ); var regiontag = "eu"; - if (userdata.region_url !== undefined && userdata.region_url !== null && userdata.region_url.length > 0) { + if ( + userdata.region_url !== undefined && + userdata.region_url !== null && + userdata.region_url.length > 0 + ) { const regionsplit = userdata.region_url.split("."); if (regionsplit.length > 2 && !regionsplit[0].includes("shuffler")) { - const namesplit = regionsplit[0].split("/"); - - regiontag = namesplit[namesplit.length - 1]; + const namesplit = regionsplit[0].split("/"); + + regiontag = namesplit[namesplit.length - 1]; + if (regiontag === "california") { + regiontag = "us"; + } } } - + const organizationView = curTab === 0 && selectedOrganization.id !== undefined ? (
) : (
- {/* + {/* */} - {userdata.support === true ? - - {/**/} - - - Status - - - - : null} - {isCloud ? - - { - toast("Region change is not directly implemented yet, and requires support help.") + {userdata.support === true ? ( + + {/**/} + + + Status + + + + ) : null} + {isCloud ? ( + + { + if (userdata.support === false) { + toast( + "Region change is not directly implemented yet, and requires support help.", + ); - if (window.drift !== undefined) { - window.drift.api.startInteraction({ - interactionId: 386411, - }) - } - }} - > - {regiontag} - - - : null} + if (window.drift !== undefined) { + window.drift.api.startInteraction({ + interactionId: 386411, + }); + } + } else { + // Show region change modal + console.log("Should open region change modal"); + setRegionChangeModalOpen(true); + } + }} + > + {regiontag} + + + ) : null} + + - {selectedOrganization.defaults !== undefined && selectedOrganization.defaults.documentation_reference !== undefined && selectedOrganization.defaults.documentation_reference !== null && selectedOrganization.defaults.documentation_reference.includes("http") ? - - - - - - - - : null} + aria-label={"Open org docs"} + > + + + + + + + ) : null} {selectedOrganization.name.length > 0 ? ( ) : (
)} - { - const newValue = parseInt(inputValue); - setAdminTab(newValue); + { + const newValue = parseInt(inputValue); + setAdminTab(newValue); - //const setConfig = (event, inputValue) => { - navigate(`/admin?admin_tab=${admin_views[newValue]}`); - }} - aria-label="disabled tabs example" - > - - Edit Details - - /> - - Cloud Synchronization - - /> - - Priorities - - /> - - Billing & Stats - - /> - - Branding (Beta) - - /> - + //const setConfig = (event, inputValue) => { + navigate(`/admin?admin_tab=${admin_views[newValue]}`); + }} + aria-label="disabled tabs example" + > + Edit Details /> + Cloud Synchronization /> + Priorities /> + Billing & Stats /> + Branding (Beta) /> + - {adminTab === 0 ? ( - - ) - : adminTab === 1 ? ( -
- - Cloud syncronization - - What does{" "} - - cloud sync - {" "} - do? Cloud syncronization is a way of getting more out of Shuffle. - Shuffle will ALWAYS make every option open source, but - features relying on other users can't be done without a - collaborative approach. - {isCloud ? ( -
-
- - Currently syncronizing:{" "} - {selectedOrganization.cloud_sync_active === true - ? "True" - : "False"} - - {selectedOrganization.cloud_sync_active ? ( - - Syncronization interval:{" "} - {selectedOrganization.sync_config.interval === 0 - ? "60" - : selectedOrganization.sync_config.interval} - - ) : null} - - Your Apikey - -
- - { - setShowApiKey(!showApiKey) - }} - > - {showApiKey ? : } - - - ) - }} - required - fullWidth={true} - disabled={true} - autoComplete="cloud apikey" - id="apikey_field" - margin="normal" - placeholder="Cloud Apikey" - variant="outlined" - defaultValue={userSettings.apikey} - type={!isCloud || showApiKey ? "text" : "password"} - /> - {selectedOrganization.cloud_sync_active ? ( - - ) : null} -
-
-
- ) : ( -
-
- { - setCloudSyncApikey(event.target.value); - }} - /> - -
- {orgSyncResponse.length > 0 ? ( - - Message from Shuffle Cloud: {orgSyncResponse} - - ) : null} -
- )} - - Features - - - Features and Limitations that are currently available to you in your Cloud or Hybrid Organization. App Executions (App Runs) reset monthly. If the organization is a customer or in a trial, these features limitations are not always enforced. - - + {adminTab === 0 ? ( + + ) : adminTab === 1 ? ( +
+ + Cloud syncronization + + What does{" "} + + cloud sync + {" "} + do? Cloud syncronization is a way of getting more out of + Shuffle. Shuffle will ALWAYS make every option open + source, but features relying on other users can't be done + without a collaborative approach. + {isCloud ? ( +
+
+ + Currently syncronizing:{" "} + {selectedOrganization.cloud_sync_active === true + ? "True" + : "False"} + + {selectedOrganization.cloud_sync_active ? ( + + Syncronization interval:{" "} + {selectedOrganization.sync_config.interval === 0 + ? "60" + : selectedOrganization.sync_config.interval} + + ) : null} + + Your Apikey + +
+ + { + setShowApiKey(!showApiKey); + }} + > + {showApiKey ? ( + + ) : ( + + )} + + + ), + }} + required + fullWidth={true} + disabled={true} + autoComplete="cloud apikey" + id="apikey_field" + margin="normal" + placeholder="Cloud Apikey" + variant="outlined" + defaultValue={userSettings.apikey} + type={!isCloud || showApiKey ? "text" : "password"} + /> + {selectedOrganization.cloud_sync_active ? ( + + ) : null} +
+
+
+ ) : ( +
+
+ { + setCloudSyncApikey(event.target.value); + }} + /> + +
+ {orgSyncResponse.length > 0 ? ( + + Message from Shuffle Cloud: {orgSyncResponse} + + ) : null} +
+ )} + + Features + + + Features and Limitations that are currently available to you + in your Cloud or Hybrid Organization. App Executions (App + Runs) reset monthly. If the organization is a customer or in a + trial, these features limitations are not always enforced. + + + {selectedOrganization.sync_features === undefined || + selectedOrganization.sync_features === null + ? null + : Object.keys(selectedOrganization.sync_features).map( + function (key, index) { + if ( + key === "schedule" || + key === "apps" || + key === "updates" || + key === "editing" + ) { + return null; + } - {selectedOrganization.sync_features === undefined || - selectedOrganization.sync_features === null - ? null - : Object.keys(selectedOrganization.sync_features).map(function ( - key, - index - ) { + const item = selectedOrganization.sync_features[key]; + if (item === null || item === undefined) { + return null; + } - if (key === "schedule" || key === "apps" || key === "updates" || key === "editing") { - return null; - } + const newkey = key.replaceAll("_", " "); + // Name rewrites as these are structs + var newname = ""; + if (newkey.toLowerCase() === "shuffle gpt") { + newname = "Shuffle AI"; + } - const item = selectedOrganization.sync_features[key]; - if (item === null) { - return null - } + const griditem = { + primary: newkey, + secondary: + item.description === undefined || + item.description === null || + item.description.length === 0 + ? "Not defined yet" + : item.description, + limit: item.limit, + usage: + item.usage === undefined || item.usage === null + ? 0 + : item.usage, + data_collection: "None", + active: item.active, + icon: , - const newkey = key.replaceAll("_", " "); - const griditem = { - primary: newkey, - secondary: - item.description === undefined || - item.description === null || - item.description.length === 0 - ? "Not defined yet" - : item.description, - limit: item.limit, - usage: item.usage === undefined || - item.usage === null ? 0 : item.usage, - data_collection: "None", - active: item.active, - icon: , - }; + newname: newname, + }; - return ( - - - - ); - })} - -
- ) - : adminTab === 2 ? - - : adminTab === 3 ? - - : adminTab === 4 ? - - : null - } + return ( + + + + ); + }, + )} +
+
+ ) : adminTab === 2 ? ( + + ) : adminTab === 3 ? ( + + ) : adminTab === 4 ? ( + + ) : null} - -
)}
@@ -3291,7 +3716,11 @@ If you're interested, please let me know a time that works for you, or set up a > - {curTab === 1 ? "Add user" : curTab === 7 ? "Add Sub-Organization" : "Add environment"} + {curTab === 1 + ? "Add user" + : curTab === 7 + ? "Add Sub-Organization" + : "Add environment"} @@ -3452,7 +3881,7 @@ If you're interested, please let me know a time that works for you, or set up a Add, edit, block or change passwords.{" "} @@ -3485,112 +3914,128 @@ If you're interested, please let me know a time that works for you, or set up a }} /> - - {logsViewModal ? - { - setLogsViewModal(false); - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: "1200px", - minHeight: "320px", - }, - }} - > - - User Logs - - - {/* ask user for which IP they want to see logs for by iterating of user.login_info */} - - - User IP - + {logsViewModal ? ( + { + setLogsViewModal(false); + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: "1200px", + minHeight: "320px", + }, + }} + > + + User Logs + + + {/* ask user for which IP they want to see logs for by iterating of user.login_info */} + + + User IP + - { + setIpSelected(event.target.value); + await getLogs(event.target.value, userLogViewing.id); + }} + > + {(() => { + const uniqueIPs = new Set(); + + return userLogViewing.login_info.map((data, index) => { + if ( + data.ip.includes("127.0.0.1") || + uniqueIPs.has(data.ip) + ) { + return null; + } + + uniqueIPs.add(data.ip); + + return ( + + {data.ip} + + ); + }); + })()} + + + {logsLoading && ipSelected.length !== 0 ? ( +
+ + Loading logs +
+ ) : null} + + + {logs.map((data, index) => ( + // redirect user to logs + // using request id or trace id + - {(() => { - const uniqueIPs = new Set(); - - return userLogViewing.login_info.map((data, index) => { - if (data.ip.includes("127.0.0.1") || uniqueIPs.has(data.ip)) { - return null; - } - - uniqueIPs.add(data.ip); - - return ( - - {data.ip} - - ); - }); - })()} - -
- {logsLoading && ipSelected.length !== 0 ? -
- - Loading logs -
- : null} - - - {logs.map((data, index) => ( - // redirect user to logs - // using request id or trace id - - - - - - ))} - - -
- -
- : null} - + + + + + ))} + +
+ + ) : null} @@ -3635,13 +4080,12 @@ If you're interested, please let me know a time that works for you, or set up a ) : null} - {users === undefined || users === null ? null @@ -3651,231 +4095,248 @@ If you're interested, please let me know a time that works for you, or set up a bgColor = "#1f2023"; } - const timeNow = new Date().getTime(); - - // Get the highest timestamp in data.login_info - var lastLogin = "N/A" - if (data.login_info !== undefined && data.login_info !== null) { - var loginInfo = 0 - for (var i = 0; i < data.login_info.length; i++) { - if (data.login_info[i].timestamp > loginInfo) { - loginInfo = data.login_info[i].timestamp - } - } + const timeNow = new Date().getTime(); - if (loginInfo > 0) { - lastLogin = new Date(loginInfo * 1000).toISOString().slice(0, 10) + " (" + data.login_info.length + ")" - } - } - - var userData = data.username - if (userdata.support === true) { - userData = { - setLogsViewModal(true) - setUserLogViewing(data) - }} - >{data.username} - } - - return ( - - - - - { - const elementName = "copy_element_shuffle"; - var copyText = - document.getElementById(elementName); - if ( - copyText !== null && - copyText !== undefined - ) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast( - "Can only copy over HTTPS (port 3443)" - ); - return; - } - - navigator.clipboard.writeText(data.apikey); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999 - ); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - - toast("Apikey copied to clipboard"); - } - }} - > - - - - ) + // Get the highest timestamp in data.login_info + var lastLogin = "N/A"; + if (data.login_info !== undefined && data.login_info !== null) { + var loginInfo = 0; + for (var i = 0; i < data.login_info.length; i++) { + if (data.login_info[i].timestamp > loginInfo) { + loginInfo = data.login_info[i].timestamp; + } } - /> - { - console.log("VALUE: ", e.target.value); - setUser(data.id, "role", e.target.value); - }} + if (loginInfo > 0) { + lastLogin = + new Date(loginInfo * 1000).toISOString().slice(0, 10) + + " (" + + data.login_info.length + + ")"; + } + } + + var userData = data.username; + if (userdata.support === true) { + userData = ( + { + setLogsViewModal(true); + setUserLogViewing(data); }} > - - Org Admin - - - Org User - - - Org Reader - - - } - style={{ minWidth: 135, maxWidth: 135, marginRight: 15 }} - /> - - - - {selectedOrganization.child_orgs !== undefined && - selectedOrganization.child_orgs !== null && - selectedOrganization.child_orgs.length > 0 ? ( - - ) : null} - - { - setSelectedUserModalOpen(true); - setSelectedUser(data); + {data.username} + + ); + } - // Find matching orgs between current org and current user's access to those orgs - if ( - userdata.orgs !== undefined && - userdata.orgs !== null && - userdata.orgs.length > 0 && - selectedOrganization.child_orgs !== undefined && - selectedOrganization.child_orgs !== null && - selectedOrganization.child_orgs.length > 0 - ) { - var active = []; - for (var key in userdata.orgs) { - const found = - selectedOrganization.child_orgs.find( - (item) => item.id === userdata.orgs[key].id - ); - if (found !== null && found !== undefined) { - if ( - data.orgs === undefined || - data.orgs === null - ) { - continue; - } + return ( + + - const subfound = data.orgs.find( - (item) => item === found.id - ); - if ( - subfound !== null && - subfound !== undefined - ) { - active.push(subfound); - } - } - } + + { + const elementName = "copy_element_shuffle"; + var copyText = + document.getElementById(elementName); + if ( + copyText !== null && + copyText !== undefined + ) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast( + "Can only copy over HTTPS (port 3443)", + ); + return; + } - setMatchingOrganizations(active); + navigator.clipboard.writeText(data.apikey); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999, + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + toast("Apikey copied to clipboard"); + } + }} + > + + + + ) } - }} - > - - - {/**/} - - - - - ); - })} + + + + + + ); + })}
) : null; @@ -3911,16 +4373,16 @@ If you're interested, please let me know a time that works for you, or set up a //setShow2faSetup(true); }; - - - const filesView = curTab !== 3 ? null : - + const filesView = + curTab !== 3 ? null : ( + + ); const schedulesView = curTab === 5 ? ( @@ -3931,7 +4393,7 @@ If you're interested, please let me know a time that works for you, or set up a Schedules used in Workflows. Makes locating and control easier.{" "} @@ -3980,10 +4442,13 @@ If you're interested, please let me know a time that works for you, or set up a 0 ? + schedule.environment === "cloud" || + schedule.environment === "" || + schedule.frequency.length > 0 ? ( schedule.frequency - : + ) : ( {schedule.seconds} seconds + ) } /> {schedule.workflow_id} } />

App Authentication

- Control the authentication options for individual apps. + Control the authentication options for individual apps.   @@ -4181,15 +4646,16 @@ If you're interested, please let me know a time that works for you, or set up a */} - + {authentication === undefined || authentication === null @@ -4222,7 +4688,8 @@ If you're interested, please let me know a time that works for you, or set up a ]; } - const isDistributed = data.suborg_distributed === true ? true : false; + const isDistributed = + data.suborg_distributed === true ? true : false; return ( @@ -4290,7 +4757,8 @@ If you're interested, please let me know a time that works for you, or set up a style={{ minWidth: 125, maxWidth: 125, - overflow: "hidden", + overflow: "auto", + marginRight: 10, }} /> { updateAppAuthentication(data); }} - disabled={data.org_id !== selectedOrganization.id ? true : false} + disabled={ + data.org_id !== selectedOrganization.id ? true : false + } > @@ -4318,14 +4788,17 @@ If you're interested, please let me know a time that works for you, or set up a > { editAuthenticationConfig(data.id); }} > - + ) : ( @@ -4337,17 +4810,21 @@ If you're interested, please let me know a time that works for you, or set up a {}} - disabled={data.org_id !== selectedOrganization.id ? true : false} + disabled={ + data.org_id !== selectedOrganization.id + ? true + : false + } > - + )} { deleteAuthentication(data); }} @@ -4356,32 +4833,39 @@ If you're interested, please let me know a time that works for you, or set up a - {selectedOrganization.id !== undefined && data.org_id !== selectedOrganization.id ? - - - - : - - { - changeDistribution(data, !isDistributed) - }} - /> - - } + {selectedOrganization.id !== undefined && + data.org_id !== selectedOrganization.id ? ( + + + + ) : ( + + { + changeDistribution(data, !isDistributed); + }} + /> + + )} ); @@ -4402,7 +4886,7 @@ If you're interested, please let me know a time that works for you, or set up a headers: { "Content-Type": "application/json; charset=utf-8", }, - }) + }) .then((response) => { return response.json(); }) @@ -4411,10 +4895,13 @@ If you're interested, please let me know a time that works for you, or set up a if (responseJson.success === true) { setLogs(responseJson.logs); } else { - if (responseJson.success === false || responseJson.reason !== undefined) { - console.log("Reason given: ", responseJson.reason) - toast("Failed getting logs: " + responseJson.reason) - setLogs([]) + if ( + responseJson.success === false || + responseJson.reason !== undefined + ) { + console.log("Reason given: ", responseJson.reason); + toast("Failed getting logs: " + responseJson.reason); + setLogs([]); } else { toast("Failed getting logs"); } @@ -4430,7 +4917,7 @@ If you're interested, please let me know a time that works for you, or set up a }); }; - const changeRecommendation = (recommendation, action) => { + const changeRecommendation = (recommendation, action) => { const data = { action: action, name: recommendation.name, @@ -4456,22 +4943,27 @@ If you're interested, please let me know a time that works for you, or set up a }) .then((responseJson) => { if (responseJson.success === true) { - if (checkLogin !== undefined) { - checkLogin() - getEnvironments() - } + if (checkLogin !== undefined) { + checkLogin(); + getEnvironments(); + } } else { - if (responseJson.success === false && responseJson.reason !== undefined) { - toast("Failed change recommendation: ", responseJson.reason) - } else { - toast("Failed change recommendation"); - } + if ( + responseJson.success === false && + responseJson.reason !== undefined + ) { + toast("Failed change recommendation: ", responseJson.reason); + } else { + toast("Failed change recommendation"); + } } }) .catch((error) => { - toast("Failed dismissing alert. Please contact support@shuffler.io if this persists."); + toast( + "Failed dismissing alert. Please contact support@shuffler.io if this persists.", + ); }); - } + }; const environmentView = curTab === 6 ? ( @@ -4479,10 +4971,12 @@ If you're interested, please let me know a time that works for you, or set up a

Environments

- Decides what Orborus environment to run your workflow actions. If you have scale problems, talk to our team: support@shuffler.io.  + Decides what Orborus environment to run your workflow actions. If + you have scale problems, talk to our team: + support@shuffler.io. 
@@ -4521,14 +5015,14 @@ If you're interested, please let me know a time that works for you, or set up a }} /> - + 0) { - foundIndex = userdata.priorities.findIndex(prio => prio.name.includes("CPU") && prio.active === true) + // Check if there's a notification for it in userdata.priorities + var showCPUAlert = false; + var foundIndex = -1; + if ( + userdata !== undefined && + userdata !== null && + userdata.priorities !== undefined && + userdata.priorities !== null && + userdata.priorities.length > 0 + ) { + foundIndex = userdata.priorities.findIndex( + (prio) => prio.name.includes("CPU") && prio.active === true, + ); - if (foundIndex >= 0 && userdata.priorities[foundIndex].name.endsWith(environment.Name)) { - showCPUAlert = true - } - } + if ( + foundIndex >= 0 && + userdata.priorities[foundIndex].name.endsWith( + environment.Name, + ) + ) { + showCPUAlert = true; + } + } - const queueSize = environment.queue !== undefined && environment.queue !== null ? environment.queue < 0 ? 0 : environment.queue > 1000 ? ">1000" : environment.queue : 0 + const queueSize = + environment.queue !== undefined && environment.queue !== null + ? environment.queue < 0 + ? 0 + : environment.queue > 1000 + ? ">1000" + : environment.queue + : 0; return ( - - - - - - : environment.run_type === "docker" ? - - - - : environment.run_type === "k8s" ? - - - - : - - - - } - style={{ - minWidth: 50, - maxWidth: 50, - overflow: "hidden", - }} - /> - - - - : - - - - - - } - style={{ - minWidth: 85, - maxWidth: 85, - overflow: "hidden", - }} - /> - - - Not running -
- : environment.running_ip.split(":")[0] - : "N/A" - } - style={{ - minWidth: 150, - maxWidth: 150, - overflow: "hidden", - }} - /> + + + + + + ) : environment.run_type === "docker" ? ( + + + + ) : environment.run_type === "k8s" ? ( + + + + ) : ( + + + + ) + } + style={{ + minWidth: 50, + maxWidth: 50, + overflow: "hidden", + }} + /> + + + + ) : ( + + + + + + ) + } + style={{ + minWidth: 85, + maxWidth: 85, + overflow: "hidden", + }} + /> + + Not running
+ ) : ( + environment.running_ip.split(":")[0] + ) + ) : ( + "N/A" + ) + } + style={{ + minWidth: 150, + maxWidth: 150, + overflow: "hidden", + }} + /> - - { - if (environment.Type === "cloud") { - toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.") - return - } + + { + if (environment.Type === "cloud") { + toast( + "No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.", + ); + return; + } - if (props.userdata.active_org === undefined || props.userdata.active_org === null) { - toast("No active organization yet. Are you logged in?") - return - } + if ( + props.userdata.active_org === undefined || + props.userdata.active_org === null + ) { + toast( + "No active organization yet. Are you logged in?", + ); + return; + } - const elementName = "copy_element_shuffle"; - const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth - const newUrl = globalUrl === "https://shuffler.io" ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" : globalUrl + const elementName = "copy_element_shuffle"; + const auth = + environment.auth === "" + ? "cb5st3d3Z!3X3zaJ*Pc" + : environment.auth; + const newUrl = + globalUrl === "https://shuffler.io" + ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" + : globalUrl; - const commandData = `docker run --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${newUrl}" --name="shuffle-orborus" -d ghcr.io/shuffle/shuffle-orborus:latest` - var copyText = document.getElementById(elementName); - if (copyText !== null && copyText !== undefined) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast("Can only copy over HTTPS (port 3443)"); - return; - } + const commandData = `docker run --restart=always --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${newUrl}" --name="shuffle-orborus" -d ghcr.io/shuffle/shuffle-orborus:latest`; + var copyText = + document.getElementById(elementName); + if ( + copyText !== null && + copyText !== undefined + ) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast( + "Can only copy over HTTPS (port 3443)", + ); + return; + } - navigator.clipboard.writeText(commandData); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999 - ); /* For mobile devices */ + navigator.clipboard.writeText(commandData); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999, + ); /* For mobile devices */ - /* Copy the text inside the text field */ - document.execCommand("copy"); + /* Copy the text inside the text field */ + document.execCommand("copy"); - toast("Orborus command copied to clipboard"); - } - }} - > - - - - } - /> + toast("Orborus command copied to clipboard"); + } + }} + > + + + + } + /> - - + + + {environment.default ? null : ( + + )} + + +
+ + + - )} - - -
- - - - - -
-
- - - {showCPUAlert === false ? null : - -
-
- - 90% CPU the server(s) hosting the Shuffle App Runner (Orborus) was found. - - - Need help with High Availability and Scale? Read documentation and Get in touch. - -
-
- -
-
-
- } - + if ( + isCloud && + environment.Name.toLowerCase() === "cloud" + ) { + rerunCloudWorkflows(environment); + } else { + abortEnvironmentWorkflows(environment); + } + }} + color="primary" + > + {isCloud && + environment.Name.toLowerCase() === "cloud" + ? "Rerun" + : "Clear"} + +
+
+
+ + + {showCPUAlert === false ? null : ( + +
+
+ + 90% CPU the server(s) hosting the Shuffle App + Runner (Orborus) was found. + + + Need help with High Availability and Scale?{" "} + + Read documentation + {" "} + and{" "} + + Get in touch + + . + +
+
+ +
+
+
+ )} + ); })} - {/**/} + {/**/}
) : null; const imagesize = 40; const imageStyle = { - width: imagesize, - height: imagesize, - pointerEvents: "none", + width: imagesize, + height: imagesize, + pointerEvents: "none", }; const organizationsTab = @@ -4853,7 +5487,22 @@ If you're interested, please let me know a time that works for you, or set up a

Organizations

- Control sub organizations (tenants)! {isCloud ? "You can only make a sub organization if you are a customer of shuffle or running a POC of the platform. Please contact support@shuffler.io to try it out." : ""}. Learn more + Control sub organizations (tenants)!{" "} + {isCloud + ? "You can only make a sub organization if you are a customer of shuffle or running a POC of the platform. Please contact support@shuffler.io to try it out." + : ""} + .{" "} + + Learn more +
- - - ); - })()} - - ) : null - } - - {subOrgs.length > 0 ? ( - - -
-

- Sub Organizations of the Current Organization ({subOrgs.length}) -

-
+ {" "} + Your Parent Organization + +
+ + {(() => { + const image = + parentOrg.image === "" ? ( + {parentOrg.name} + ) : ( + {parentOrg.name} + ); + const bgColor = "#27292d"; - + return ( + + + + + + + + + + - - - - - - - - {subOrgs.map((data, index) => { - const image = - data.image === "" ? ( - {data.name} - ) : ( - {data.name} + + + ); + })()} + + ) : null} - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } + {subOrgs.length > 0 ? ( + + +
+

+ Sub Organizations of the Current Organization ({subOrgs.length}) +

+
- return ( - + + + + - - - ); - })} -
- - - ) : null - } + + {subOrgs.map((data, index) => { + const image = + data.image === "" ? ( + {data.name} + ) : ( + {data.name} + ); + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } - + return ( + + + + -
-

- All Tenants -

-
+ +
+ ); + })} +
+ + + ) : null} - + + +
+

+ All Tenants +

+
+ + ) : null; - const cacheOrgView = + const cacheOrgView = curTab === 4 ? (
- +
) : null; @@ -5296,7 +5953,7 @@ If you're interested, please let me know a time that works for you, or set up a - App Auth + App Auth /> - Datastore + Datastore /> { }, "open": false, "attachedTo": "", - }); + }) + + // New for generated stuff + const integrationApps = [{ + "id": "integration", + "name": "Integration Framework", + "type": "ACTION", + "app_version": "1.0.0", + "loop_versions": ["1.0.0"], + "authentication": { + "type": "", + }, + "description": "Support-use only", + "actions": [{ + "name": "Cases", + "description": "Available actions for case management", + "label": "Cases", + "parameters": [{ + "name": "action", + "value": "list_tickets", + "options": [ + "list_tickets", + "get_ticket", + "create_ticket", + ], + "required": true, + }, + { + "name": "fields", + "value": "", + "required": false, + "multiline": true, + }, + /*{ + "name": "options", + "value": "deduplicate,enrich", + "required": false, + "multiselect": true, + "options": [ + "deduplicate", + "enrich", + ] + }*/ + ] + }] + }] + + /* + { + "name": "Email", + "label": "Email", + "parameters": [{ + "name": "action", + "value": "list_email", + "options": [ + "list_email", + "send_mail", + ], + "required": true, + }], + }] + }] + */ // For code editor const [codeEditorModalOpen, setCodeEditorModalOpen] = React.useState(false); @@ -577,8 +638,6 @@ const AngularWorkflow = (defaultprops) => { return } - console.log("Checking to update app with useeffect.") - for (let appkey in apps) { const curapp = apps[appkey] if (curapp.name !== selectedApp.name) { @@ -610,6 +669,7 @@ const AngularWorkflow = (defaultprops) => { setSelectedApp(curapp) } + break } @@ -1539,34 +1599,38 @@ const AngularWorkflow = (defaultprops) => { curworkflowAction.position = cyelements[cyelementsKey].position(); // workaround to fix some edgecases - if ( - curworkflowAction.parameters === "" || - curworkflowAction.parameters === null - ) { - curworkflowAction.parameters = []; - } + if ( + curworkflowAction.parameters === "" || + curworkflowAction.parameters === null + ) { + curworkflowAction.parameters = []; + } - if ( - curworkflowAction.example === undefined || - curworkflowAction.example === "" || - curworkflowAction.example === null - ) { - if (cyelements[cyelementsKey].data().example !== undefined) { - curworkflowAction.example = cyelements[cyelementsKey].data().example; - } - } + if ( + curworkflowAction.example === undefined || + curworkflowAction.example === "" || + curworkflowAction.example === null + ) { + if (cyelements[cyelementsKey].data().example !== undefined) { + curworkflowAction.example = cyelements[cyelementsKey].data().example; + } + } // Override just in this place curworkflowAction.errors = []; curworkflowAction.isValid = true; - // Cleans up OpenAPI items - var newparams = []; - for (let parametersKey in curworkflowAction.parameters) { - const thisitem = curworkflowAction.parameters[parametersKey]; - if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) { - continue; - } + // Cleans up OpenAPI items + var newparams = []; + for (let parametersKey in curworkflowAction.parameters) { + const thisitem = curworkflowAction.parameters[parametersKey]; + if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) { + continue; + } + + if (thisitem.value !== undefined && thisitem.value !== null && Array.isArray(thisitem.value)) { + thisitem.value = thisitem.value.join(",") + } newparams.push(thisitem); } @@ -1578,17 +1642,17 @@ const AngularWorkflow = (defaultprops) => { useworkflow.triggers = []; } - var curworkflowTrigger = useworkflow.triggers.find( - (a) => a.id === cyelements[cyelementsKey].data()["id"] - ); - if (curworkflowTrigger === undefined) { - curworkflowTrigger = cyelements[cyelementsKey].data(); - } + var curworkflowTrigger = useworkflow.triggers.find( + (a) => a.id === cyelements[cyelementsKey].data()["id"] + ); + if (curworkflowTrigger === undefined) { + curworkflowTrigger = cyelements[cyelementsKey].data(); + } - curworkflowTrigger.position = cyelements[cyelementsKey].position(); - if (curworkflowTrigger.canConnect === false) { - continue - } + curworkflowTrigger.position = cyelements[cyelementsKey].position(); + if (curworkflowTrigger.canConnect === false) { + continue + } newTriggers.push(curworkflowTrigger); } else if (type === "COMMENT") { @@ -2087,6 +2151,7 @@ const AngularWorkflow = (defaultprops) => { const pretend_apps = [{ "name": "TBD", + "id": "TBD", "app_name": "TBD", "app_version": "TBD", "description": "TBD", @@ -2113,6 +2178,7 @@ const AngularWorkflow = (defaultprops) => { console.log("No response") const pretend_apps = [{ "name": "TBD", + "id": "TBD", "app_name": "TBD", "app_version": "TBD", "description": "TBD", @@ -2131,42 +2197,53 @@ const AngularWorkflow = (defaultprops) => { return } - // Used for e.g. Liquid testing + // Used for e.g. Liquid testing const foundTools = responseJson.find((app) => app.name === "Shuffle Tools") if (foundTools !== undefined && foundTools !== null) { setToolsApp(foundTools) } setApps(responseJson); + // Set localstorage for the apps in the "apps" key + if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { + try { + localStorage.setItem("apps", JSON.stringify(responseJson)) + } catch (e) { + console.log("Failed to set apps in localstorage: ", e) + } + } + + var handledPrioritizedApps = responseJson.filter((app) => internalIds.includes(app.name.toLowerCase())); + handledPrioritizedApps = [].concat(integrationApps, handledPrioritizedApps) if (isCloud) { setFilteredApps(responseJson.filter((app) => !internalIds.includes(app.name.toLowerCase()))); - setPrioritizedApps(responseJson.filter((app) => internalIds.includes(app.name.toLowerCase()))); + setPrioritizedApps(handledPrioritizedApps) } else { var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name.toLowerCase())) setFilteredApps(tmpFiltered) - setPrioritizedApps(responseJson.filter((app) => internalIds.includes(app.name.toLowerCase()))); + setPrioritizedApps(handledPrioritizedApps) } setAppsLoaded(true) // Remove all cytoscape triggers first? if (cy !== undefined && cy !== null) { - cy.removeListener("select"); + cy.removeListener("select") } // Re-adding cytoscape triggers if (cy !== undefined && cy !== null) { cy.on("select", "node", (e) => { - onNodeSelect(e, appAuthentication); - }); + onNodeSelect(e, appAuthentication) + }) } }) .catch((error) => { - console.log("App loading error: " + error.toString()); + console.log("App loading error: " + error.toString()) setAppsLoaded(true) - //toast("App loading error: "+error.toString()); + //toast("App loading error: "+error.toString()) }); }; @@ -2575,7 +2652,6 @@ const AngularWorkflow = (defaultprops) => { const node = cy.getElementById(chunkJson.id) if (node !== undefined && node !== null) { - console.log("Node already exists: ", node) return } @@ -3455,9 +3531,9 @@ const AngularWorkflow = (defaultprops) => { cy.add(decoratorNode).unselectify(); } else { - console.log("Node already exists - don't add descriptor node"); + //console.log("Node already exists - don't add descriptor node"); } - } + } } originalLocation = { @@ -4014,7 +4090,7 @@ const AngularWorkflow = (defaultprops) => { ) if (curapp === undefined || curapp === null) { - console.log("Couldn't find ID - checking with name & version") + console.log("Couldn't find app with that ID - checking with name & version") curapp = newapps.find((a) => a.name === curaction.app_name && @@ -4024,34 +4100,60 @@ const AngularWorkflow = (defaultprops) => { ) } + if (curapp === undefined || curapp === null) { + curapp = integrationApps.find((a) => + a.name === curaction.app_name && + (a.app_version === curaction.app_version || + (a.loop_versions !== null && + a.loop_versions.includes(curaction.app_version))) + ) + } + if (curaction.template === true && curaction.name !== undefined) { //newapps. const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase() console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname) - curaction.matching_actions = [] - for (var newAppskey in newapps) { - for (let actionsSubkey in newapps[newAppskey].actions) { - const tmpaction = newapps[newAppskey].actions[actionsSubkey] - if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { - console.log("MATCH!: ", newapps[newAppskey]) - curaction.matching_actions.push({ - "app_name": newapps[newAppskey].name, - "app_version": newapps[newAppskey].app_version, - "app_id": newapps[newAppskey].id, - "action": tmpaction, - "large_image": newapps[newAppskey].large_image, - "app_index": newAppskey, - "action_index": actionsSubkey, - }) - } + curaction.matching_actions = [] + for (var newAppskey in newapps) { + for (let actionsSubkey in newapps[newAppskey].actions) { + const tmpaction = newapps[newAppskey].actions[actionsSubkey] + if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { + console.log("MATCH!: ", newapps[newAppskey]) + curaction.matching_actions.push({ + "app_name": newapps[newAppskey].name, + "app_version": newapps[newAppskey].app_version, + "app_id": newapps[newAppskey].id, + "action": tmpaction, + "large_image": newapps[newAppskey].large_image, + "app_index": newAppskey, + "action_index": actionsSubkey, + }) + } + } + } + } + + if (!curapp || curapp === undefined) { + // Check local storage has it + const foundapps = localStorage.getItem("apps") + if (foundapps !== null && foundapps !== undefined) { + const parsedapps = JSON.parse(foundapps) + if (parsedapps !== null && parsedapps !== undefined && parsedapps.length > 0) { + for (let appkey in parsedapps) { + if (parsedapps[appkey].name === curaction.app_name) { + curapp = parsedapps[appkey] + break } } } - if (!curapp || curapp === undefined) { - console.log("APPS - couldn't find it: ", newapps) + } else { + console.log("No apps found in local storage") + } + } + if (!curapp || curapp === undefined) { const tmpapp = { name: curaction.app_name, app_name: curaction.app_name, @@ -4361,7 +4463,7 @@ const AngularWorkflow = (defaultprops) => { } else { if (refresh === true) { setHighlightedApp(appid) - //toast("App activated for your organization! Refresh the page to use the app.") + //toast("App activated for your organisation! Refresh the page to use the app.") getApps() } @@ -6037,7 +6139,6 @@ const AngularWorkflow = (defaultprops) => { currentNode.data.isDescriptor ) { found = true; - console.log("FOUND THE NODE!"); break; } } @@ -8075,6 +8176,7 @@ const AngularWorkflow = (defaultprops) => { const AppView = (props) => { const { allApps, prioritizedApps, filteredApps, extraApps } = props; + //extraApps, const [visibleApps, setVisibleApps] = React.useState( Array.prototype.concat.apply( @@ -8090,9 +8192,9 @@ const AngularWorkflow = (defaultprops) => { const app = props.app; const [hover, setHover] = React.useState(false); - if (app.id === "" || app.name === "") { - return null - } + if (app.id === "" || app.name === "") { + return null + } const maxlen = 24; var newAppname = app.name; @@ -8103,9 +8205,9 @@ const AngularWorkflow = (defaultprops) => { newAppname = newAppname.replaceAll("_", " "); - if (app.large_image === undefined || app.large_image === null || app.large_image === "") { - app.large_image = theme.palette.defaultImage - } + if (app.large_image === undefined || app.large_image === null || app.large_image === "") { + app.large_image = theme.palette.defaultImage + } const image = app.large_image !== undefined && app.large_image !== null && app.large_image !== "" ? app.large_image : theme.palette.defaultImage @@ -8611,11 +8713,14 @@ const AngularWorkflow = (defaultprops) => { event.target.blur(event); } }} + onChange={(event) => { + runSearch(event.target.value) + }} onBlur={(event) => { //navigate(`?q=${event.target.value}`) - runSearch(event.target.value); + //runSearch(event.target.value) }} /> {visibleApps.length > extraApps.length ? ( @@ -8625,6 +8730,10 @@ const AngularWorkflow = (defaultprops) => { return null; } + if (app.id === "integration" && userdata.support !== true) { + return null + } + var extraMessage = "" if (index == 2) { extraMessage =
@@ -8655,7 +8764,7 @@ const AngularWorkflow = (defaultprops) => { }} > - Click one of the relevant public apps below to Activate it for your organization. + Click one of the relevant public apps below to Activate it for your organisation. { console.log("CLICKED") @@ -8667,19 +8776,22 @@ const AngularWorkflow = (defaultprops) => {
: -
} +
+ + Apps need to be activated before they can be used. Search from our 2500+ apps to activate them for your organisation. + +
+ }
) : apps.length > 0 ? (
{ console.log("Should load in extra apps?") }} > - Couldn't find the apps you were looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization. + Couldn't find the apps you were looking for? Searching unactivated apps. Click one of these apps to Activate it for your organisation. { console.log("CLICKED") @@ -8741,12 +8853,20 @@ const AngularWorkflow = (defaultprops) => { return } - console.log("action input: ", e) + if (selectedApp.actions.length === 1) { + // Find if there's a new app + const newApp = apps.find((app) => (app.name === selectedApp.name && app.app_version !== selectedApp.app_version) || app.id == selectedApp.id) + if (newApp !== undefined && newApp !== null) { - const newaction = selectedApp.actions.find( - (a) => a.name === e.target.value - ); + if (selectedApp.actions !== undefined && selectedApp.actions !== null && selectedApp.actions.length > 1) { + setSelectedApp(newApp) + } + selectedApp.actions = newApp.actions + } + } + + const newaction = selectedApp.actions.find((a) => a.name === e.target.value) if (newaction === undefined || newaction === null) { toast("Failed to find the action you selected. Please try again or contact support@shuffler.io if it persists."); return; @@ -8833,7 +8953,6 @@ const AngularWorkflow = (defaultprops) => { if (newSelectedAction.app_name === "Shuffle Tools") { const iconInfo = GetIconInfo(newSelectedAction); - console.log("ICONINFO: ", iconInfo); const svg_pin = ``; const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); newSelectedAction.large_image = svgpin_Url; @@ -8860,8 +8979,6 @@ const AngularWorkflow = (defaultprops) => { // Further checks if those fields are already set in a previously used action newSelectedAction = RunAutocompleter(newSelectedAction); - console.log("newaction: ", newaction) - if ( newaction.return !== undefined && newaction.return !== null && @@ -13927,6 +14044,10 @@ const AngularWorkflow = (defaultprops) => { return null } + if (userdata.active_org === undefined || userdata.active_org === null) { + return null + } + const isCorrectOrg = 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 return ( @@ -13959,7 +14080,7 @@ const AngularWorkflow = (defaultprops) => { Warning: Change { - toast("Changing to correct organization. Please wait a few seconds.") + toast("Changing to correct organisation. Please wait a few seconds.") localStorage.setItem("globalUrl", ""); localStorage.setItem("getting_started_sidebar", "open"); @@ -13994,7 +14115,7 @@ const AngularWorkflow = (defaultprops) => { window.location.reload(); }, 2000); - toast("Successfully changed active organization - refreshing!"); + toast("Successfully changed active organisation - refreshing!"); } else { if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) { toast(responseJson.reason); @@ -14316,7 +14437,7 @@ const AngularWorkflow = (defaultprops) => { ) } - const shownErrors = !isMobile && !workflow.public && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors ? + const shownErrors = !isMobile && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors && (!workflow.public || userdata.support === true) ?
{ {/**/} - - Workflow Issues: {workflow.errors.length} { )} {/*userdata.avatar === creatorProfile.github_avatar ? null :*/} - +
) @@ -17006,6 +17123,10 @@ const AngularWorkflow = (defaultprops) => { return "You can't use localhost in apps. Use the external ip or url of the server instead" } + if (stringjson.includes("manifest unknown")) { + return "The app's Docker Image is not available in the environment yet. Re-run the app to force a re-download of the app. If the problem persists, contact support" + } + if (result.status !== 200 && stringjson.includes("192.168") || stringjson.includes("172.16") || stringjson.includes("10.0")) { return "Consider whether your Orborus environment can connect to a local IP or not." } @@ -17241,7 +17362,7 @@ const AngularWorkflow = (defaultprops) => { width: imgsize, height: imgsize, border: `2px solid ${statusColor}`, - filter: curapp === undefined ? "grayscale(100%)" : null, + filter: curapp === undefined ? "grayscale(100%)" : null, }} /> )} @@ -19079,6 +19200,8 @@ const AngularWorkflow = (defaultprops) => {
const changeActionParameterCodeMirror = (event, count, data, actionlist) => { + // Check if event.target.value is an array. If it is, split with comma + if (data.startsWith("${") && data.endsWith("}")) { // PARAM FIX - Gonna use the ID field, even though it's a hack const paramcheck = selectedAction.parameters.find(param => param.name === "body") diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index f8588bbb..354a60c3 100755 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -2073,7 +2073,7 @@ const AppCreator = (defaultprops) => { }; if (item.action_label !== undefined && item.action_label !== "" && item.action_label !== "No Label") { - console.log("Action label: ", item.action_label) + //console.log("Action label: ", item.action_label) data.paths[item.url][item.method.toLowerCase()]["x-label"] = item.action_label } diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index ca0fc949..ae4c9b93 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -82,11 +82,16 @@ const chipStyle = { // Fixes names by making them uppercase and such // Used for labels. A lot of places don't use this yet export const FixName = (name) => { + if (name === undefined || name === null) { + return "" + } + const newAppname = ( name.charAt(0).toUpperCase() + name.substring(1) - ).replaceAll("_", " "); - return newAppname; -}; + ).replaceAll("_", " ") + + return newAppname +} // Takes input of e.g. $node.data.#.asd and a matching value from a json blob @@ -1832,7 +1837,11 @@ const Apps = (props) => { }) .then((responseJson) => { if (responseJson.success === false) { - toast("Failed to activate the app") + if (responseJson.reason !== undefined) { + toast("Failed to activate the app: "+responseJson.reason); + } else { + toast("Failed to activate the app"); + } } else { //toast("App activated for your organization! Refresh the page to use the app.") if (appExists) { @@ -2052,7 +2061,7 @@ const Apps = (props) => { to={`/apps/edit/${selectedApp.id}`} style={{ textDecoration: "none", color: "inherit" }} > - {selectedApp.name} + {FixName(selectedApp.name)} ) : null} diff --git a/frontend/src/views/Search.jsx b/frontend/src/views/Search.jsx index 1f696aad..b19050c7 100644 --- a/frontend/src/views/Search.jsx +++ b/frontend/src/views/Search.jsx @@ -1,33 +1,35 @@ import React, { useState, useEffect } from "react"; import theme from '../theme.jsx'; -import {isMobile} from "react-device-detect"; +import { isMobile } from "react-device-detect"; import AppGrid from "../components/AppGrid.jsx" import WorkflowGrid from "../components/WorkflowGrid.jsx" import CreatorGrid from "../components/CreatorGrid.jsx" import DocsGrid from "../components/DocsGrid.jsx" +import DiscordChat from "../components/DiscordChat.jsx"; import { useNavigate } from "react-router-dom"; -import { - Tabs, - Tab, +import { + Tabs, + Tab, } from "@mui/material"; import { Apps as AppsIcon, Code as CodeIcon, + Chat as ChatIcon, EmojiObjects as EmojiObjectsIcon, - Description as DescriptionIcon, + Description as DescriptionIcon, } from "@mui/icons-material"; // Should be different if logged in :| const Search = (props) => { - const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader} = props; + const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader } = props; let navigate = useNavigate(); - const [curTab, setCurTab] = useState(0); - const iconStyle = { marginRight: isHeader ? null : 10 }; + const [curTab, setCurTab] = useState(0); + const iconStyle = { marginRight: isHeader ? null : 10 }; useEffect(() => { if (serverside !== true && window.location.search !== undefined && window.location.search !== null) { @@ -56,7 +58,7 @@ const Search = (props) => { maxWidth: 1024, scrollX: "hidden", overflowX: "hidden", - justifyContent: isHeader ? "center" : null, + justifyContent: isHeader ? "center" : null, } const boxStyle = { @@ -68,49 +70,52 @@ const Search = (props) => { paddingRight: isHeader ? null : 30, paddingBottom: isHeader ? null : 30, paddingTop: hidemargins === true ? 0 : isHeader ? null : 30, - display: "flex", + display: "flex", flexDirection: "column", overflowX: "hidden", minHeight: 400, } const views = { - 0: "apps", - 1: "workflows", - 2: "docs", - 3: "creators", - } + 0: "apps", + 1: "workflows", + 2: "docs", + 3: "creators", + 4: "discord", + } const setConfig = (event, inputValue) => { const newValue = parseInt(inputValue) - setCurTab(newValue) - if (newValue === 0) { - document.title = "Shuffle - search - apps"; - } else if (newValue === 1) { - document.title = "Shuffle - search - workflows"; - } else if (newValue === 2) { - document.title = "Shuffle - search - documentation"; - } else if (newValue === 3) { - document.title = "Shuffle - search - creators"; - } else { - document.title = "Shuffle - search"; - } + setCurTab(newValue) + if (newValue === 0) { + document.title = "Shuffle - search - apps"; + } else if (newValue === 1) { + document.title = "Shuffle - search - workflows"; + } else if (newValue === 2) { + document.title = "Shuffle - search - documentation"; + } else if (newValue === 3) { + document.title = "Shuffle - search - creators"; + } else if (newValue === 4) { + document.title = "Shuffle - search - Discord Chat"; + }else { + document.title = "Shuffle - search"; + } + - const urlSearchParams = new URLSearchParams(window.location.search) const params = Object.fromEntries(urlSearchParams.entries()) const foundQuery = params["q"] var extraQ = "" if (foundQuery !== null && foundQuery !== undefined) { - extraQ = "&q="+foundQuery + extraQ = "&q=" + foundQuery } - + if ((serverside === false || serverside === undefined) && window.location.pathname.includes("/search")) { - navigate(`/search?tab=${views[newValue]}`+extraQ) + navigate(`/search?tab=${views[newValue]}` + extraQ) } - } + } if (isLoaded === false) { return null @@ -118,18 +123,18 @@ const Search = (props) => { // Random names for type & autoComplete. Didn't research :^) - const landingpageDataBrowser = -
+ const landingpageDataBrowser = +
@@ -143,36 +148,46 @@ const Search = (props) => { /> - Docs + Docs /> - Creators + Creators /> + + Discord Chat + + /> + - {curTab === 0 ? + {curTab === 0 ? - : - curTab === 1 ? - window.location.pathname === "/search" ? - + : + curTab === 1 ? + window.location.pathname === "/search" ? + + : + : - - : - curTab === 2 ? - - : - curTab === 3 ? - - : - null} + curTab === 2 ? + + : + curTab === 3 ? + + : + curTab === 4 ? + +: + + null}
//{/*alternativeView={true} />*/} - const loadedCheck = isLoaded ? + const loadedCheck = isLoaded ?
{landingpageDataBrowser}
@@ -181,7 +196,7 @@ const Search = (props) => {
// #1f2023? - return( + return (
{loadedCheck}
diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index be99104c..094b207e 100755 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -9,14 +9,16 @@ import { Button, Divider, TextField, + Modal, } from "@mui/material"; //import { useAlert -import { ToastContainer, toast } from "react-toastify" +import { ToastContainer, toast } from "react-toastify"; +import "../codeeditor-index.css"; import { FileCopy, Visibility, VisibilityOff } from "@mui/icons-material"; import IconButton from "@mui/material/IconButton"; import { Tooltip } from "@mui/material"; - +import CloseIcon from "@mui/icons-material/Close"; const Settings = (props) => { const { globalUrl, isLoaded, userdata, setUserData } = props; @@ -44,14 +46,13 @@ const Settings = (props) => { // Used for error messages etc const [passwordFormMessage, setPasswordFormMessage] = useState(""); - const [firstrequest, setFirstRequest] = useState(true); - const [userSettings, setUserSettings] = useState({}); - const [showApiKey, setShowApiKey] = useState(false); const [apiKeyCopied, setApiKeyCopied] = useState(false); + const [accountDeleteButtonClicked, setAccountDeleteButtonClicked] = useState(false); + const handleCopyApiKey = () => { navigator.clipboard.writeText(userSettings.apikey); setApiKeyCopied(true); @@ -135,6 +136,241 @@ const Settings = (props) => { return currentOwner; }; + const handleAccountDelete = () => { + setAccountDeleteButtonClicked(true); + }; + + const DeleteAccountPopUp = () => { + const [userDeleteAccepted, setUserDeleteAccepted] = useState(false); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [disabled, setDisabled] = useState(true); + const [open, setOpen] = useState(true); + + const boxStyling = { + position: "relative", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + zIndex: "9999", + backgroundColor: "#1a1a1a", + color: "white", + padding: 20, + borderRadius: 5, + boxShadow: "0 0 10px rgba(0, 0, 0, 0.3)", + width: 430, + height: 430, + }; + + const closeIconButtonStyling = { + color: "white", + border: "none", + backgroundColor: "transparent", + marginLeft: "90%", + width: 20, + height: 20, + cursor: "pointer", + }; + + const handlePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + const buttonStyle = { + marginTop: 20, + height: 50, + border: "none", + width: "100%", + fontSize: 16, + backgroundColor: disabled ? "gray" : "red", + color: "white", + cursor: disabled === false && "pointer", + }; + const checkboxStyle = { + position: "relative", + cursor: "pointer", + display: "inline-block", + width: 20, + height: 20, + backgroundColor: "#ccc", + borderRadius: 4, + }; + + const checkmarkStyle = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + content: "", + width: 10, + height: 10, + backgroundColor: "#fff", + borderRadius: 2, + display: "none", + }; + + + const handlePasswordChange = (e) => { + setPassword(e.target.value); + }; + + const handleCheckBoxEvent = () => { + setUserDeleteAccepted(!userDeleteAccepted); + }; + + useEffect(() => { + if (password.length > 8 && userDeleteAccepted) { + setDisabled(false); + } else { + setDisabled(true); + } + }, [password, userDeleteAccepted]); + + function removeAllCookies() { + + var cookies = document.cookie.split(";"); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i]; + var eqPos = cookie.indexOf("="); + var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/"; + } + } + + const handleDeleteAccount = () => { + const baseURL = globalUrl; + const userID = userdata.id; + + const url = `${baseURL}/api/v1/users/${userID}/remove`; + + fetch(url, { + mode: "cors", + method: "DELETE", + body: JSON.stringify({ password }), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + toast.success( + "Your account delete successfully! redirecting in 3 sec.." + ); + removeAllCookies(); + + window.location.pathname = "/"; + } else { + toast.error(`${data.reason}`); + } + }) + .catch((error) => { + console.error( + "There was a problem with your fetch operation:", + error + ); + }); + }; + + return ( + +
+ +

Account

+ {/*
*/} +
+ +
    +
  • + +
  • +
  • + +
  • +
+
+ + +
+
+ + + {showPassword ? : } + + ), + }} + /> +
+ +
+
+ + ); + }; + const onPasswordChange = () => { const data = { username: userSettings.username, @@ -727,7 +963,8 @@ const Settings = (props) => { > Submit password change -

{passwordFormMessage}

+

{passwordFormMessage}

+ {isCloud && ( <> @@ -820,6 +1057,22 @@ const Settings = (props) => {
+

Danger Area

+ + {loadedValidationWorkflows !== undefined && loadedValidationWorkflows !== null ? loadedValidationWorkflows.map((data, index) => { @@ -831,6 +1084,7 @@ const Settings = (props) => { }) : null} + {accountDeleteButtonClicked && } ); @@ -888,47 +1142,47 @@ const Settings = (props) => { console.log("redirect: ", redirectUri) - const client_id = "3d272b1b782b100b1e61" - const username = userdata.id; - const scopes = "read:user"; + const client_id = "3d272b1b782b100b1e61" + const username = userdata.id; + const scopes = "read:user"; - const url = `https://github.com/login/oauth/authorize?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=username%3D${username}%26type%3Dgithub` + const url = `https://github.com/login/oauth/authorize?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=username%3D${username}%26type%3Dgithub` - console.log("URL: ", url); + console.log("URL: ", url); - var newwin = window.open(url, "", "width=800,height=600"); + var newwin = window.open(url, "", "width=800,height=600"); - // Check whether we got a callback somewhere - //var id = setInterval(function () { - // fetch( - // globalUrl + "/api/v1/triggers/gmail/" + selectedTrigger.id, - // { - // method: "GET", - // headers: { "content-type": "application/json" }, - // credentials: "include", - // } - // ) - // .then((response) => { - // if (response.status !== 200) { - // throw new Error("No trigger info :o!"); - // } + // Check whether we got a callback somewhere + //var id = setInterval(function () { + // fetch( + // globalUrl + "/api/v1/triggers/gmail/" + selectedTrigger.id, + // { + // method: "GET", + // headers: { "content-type": "application/json" }, + // credentials: "include", + // } + // ) + // .then((response) => { + // if (response.status !== 200) { + // throw new Error("No trigger info :o!"); + // } - // return response.json(); - // }) - // .then((responseJson) => { - // console.log("RESPONSE: "); - // setTriggerAuthentication(responseJson); - // clearInterval(id); - // newwin.close(); - // setGmailFolders(); - // }) - // .catch((error) => { - // console.log(error.toString()); - // }); - //}, 2500); + // return response.json(); + // }) + // .then((responseJson) => { + // console.log("RESPONSE: "); + // setTriggerAuthentication(responseJson); + // clearInterval(id); + // newwin.close(); + // setGmailFolders(); + // }) + // .catch((error) => { + // console.log(error.toString()); + // }); + //}, 2500); - //saveWorkflow(workflow); - } + //saveWorkflow(workflow); + } const loadedCheck = isLoaded && !firstrequest ? ( diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index bf21e96b..707249e7 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -128,6 +128,7 @@ const useStyles = makeStyles((theme) => ({ export const GetIconInfo = (action) => { // Finds the icon based on the action. Should be verbs. const iconList = [ + { key: "cases", values: ["cases"] }, { key: "cache_add", values: ["set_cache"] }, { key: "cache_get", values: ["get_cache"] }, { key: "filter", values: ["filter"] }, @@ -222,6 +223,13 @@ export const GetIconInfo = (action) => { const defaultColor = "#f76b1c"; const defaultGradient = ["#fad961", "#f76b1c"]; const parsedIcons = { + cases: { + icon: "M11 3C6.58 3 3 4.79 3 7C3 9.21 6.58 11 11 11C15.42 11 19 9.21 19 7C19 4.79 15.42 3 11 3ZM3 9V12C3 14.21 6.58 16 11 16C15.42 16 19 14.21 19 12V9C19 11.21 15.42 13 11 13C6.58 13 3 11.21 3 9ZM3 14V17C3 19.21 6.58 21 11 21C12.41 21 13.79 20.81 15 20.46V17.46C13.79 17.81 12.41 18 11 18C6.58 18 3 16.21 3 14ZM20 14V17H17V19H20V22H22V19H25V17H22V14", + iconColor: "white", + iconBackgroundColor: "#8acc3f", + originalIcon: "", + fillGradient: ["#8acc3f", "#459622"], + }, cache_add: { icon: "M11 3C6.58 3 3 4.79 3 7C3 9.21 6.58 11 11 11C15.42 11 19 9.21 19 7C19 4.79 15.42 3 11 3ZM3 9V12C3 14.21 6.58 16 11 16C15.42 16 19 14.21 19 12V9C19 11.21 15.42 13 11 13C6.58 13 3 11.21 3 9ZM3 14V17C3 19.21 6.58 21 11 21C12.41 21 13.79 20.81 15 20.46V17.46C13.79 17.81 12.41 18 11 18C6.58 18 3 16.21 3 14ZM20 14V17H17V19H20V22H22V19H25V17H22V14", iconColor: "white",