A bunch of commits moved from cloud -> onprem to sync up current work on 2.0

This commit is contained in:
Frikky
2024-10-08 11:08:28 +02:00
parent f2a0aac31d
commit 8154fb8ad4
34 changed files with 2081 additions and 1284 deletions
+107 -106
View File
@@ -1714,6 +1714,112 @@ class AppBase:
return file_ids return file_ids
def validate_condition(self, sourcevalue, check, destinationvalue):
if check == "=" or check == "==" or check.lower() == "equals":
if str(sourcevalue).lower() == str(destinationvalue).lower():
return True
elif check == "!=" or check.lower() == "does not equal":
if str(sourcevalue).lower() != str(destinationvalue).lower():
return True
elif check.lower() == "startswith":
if str(sourcevalue).lower().startswith(str(destinationvalue).lower()):
return True
elif check.lower() == "endswith":
if str(sourcevalue).lower().endswith(str(destinationvalue).lower()):
return True
elif check.lower() == "contains":
if destinationvalue.lower() in sourcevalue.lower():
return True
elif check.lower() == "is empty" or check.lower() == "is_empty":
try:
if len(json.loads(sourcevalue)) == 0:
return True
except Exception as e:
self.logger.info(f"[ERROR] Failed to check if empty as list: {e}")
if len(str(sourcevalue)) == 0:
return True
elif check.lower() == "contains_any_of":
newvalue = [destinationvalue.lower()]
if "," in destinationvalue:
newvalue = destinationvalue.split(",")
elif ", " in destinationvalue:
newvalue = destinationvalue.split(", ")
for item in newvalue:
if not item:
continue
if item.strip() in sourcevalue:
return True
# FIXME: This will be buggy if using > and >= operators in the future.
elif check.lower() == "larger than" or check.lower() == "bigger than" or check == ">" or check == ">=":
try:
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
if int(sourcevalue) > int(destinationvalue):
return True
except AttributeError as e:
self.logger.info("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e))
try:
destinationvalue = len(json.loads(destinationvalue))
except Exception as e:
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
try:
# Check if it's a list in autocast and if so, check the length
if len(json.loads(sourcevalue)) > int(destinationvalue):
return True
except Exception as e:
self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}")
# FIXME: This will be buggy if using < and <= operators in the future.
elif check.lower() == "smaller than" or check.lower() == "less than" or check == "<" or check == "<=":
self.logger.info("In smaller than check: %s %s" % (sourcevalue, destinationvalue))
try:
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
if int(sourcevalue) < int(destinationvalue):
return True
except AttributeError as e:
pass
try:
destinationvalue = len(json.loads(destinationvalue))
except Exception as e:
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
try:
# Check if it's a list in autocast and if so, check the length
if len(json.loads(sourcevalue)) < int(destinationvalue):
return True
except Exception as e:
self.logger.info(f"[WARNING] Failed to check if smaller than as list: {e}")
elif check.lower() == "re" or check.lower() == "matches regex":
try:
found = re.search(str(destinationvalue), str(sourcevalue))
except re.error as e:
return False
except Exception as e:
return False
if found == None:
return False
return True
else:
self.logger.error("[DEBUG] Condition: can't handle %s yet. Setting to true" % check)
return False
#async def execute_action(self, action): #async def execute_action(self, action):
def execute_action(self, action): def execute_action(self, action):
# !!! Let this line stay - its used for some horrible codegeneration / stitching !!! # # !!! Let this line stay - its used for some horrible codegeneration / stitching !!! #
@@ -2596,7 +2702,6 @@ class AppBase:
return returndata, is_loop return returndata, is_loop
# Sending self as it's not a normal function # Sending self as it's not a normal function
def parse_liquid(template, self): def parse_liquid(template, self):
@@ -3083,109 +3188,6 @@ class AppBase:
return "", parameter["value"], is_loop return "", parameter["value"], is_loop
def run_validation(sourcevalue, check, destinationvalue):
#self.logger.info("[DEBUG] Checking %s '%s' %s" % (sourcevalue, check, destinationvalue))
if check == "=" or check.lower() == "equals":
if str(sourcevalue).lower() == str(destinationvalue).lower():
return True
elif check == "!=" or check.lower() == "does not equal":
if str(sourcevalue).lower() != str(destinationvalue).lower():
return True
elif check.lower() == "startswith":
if str(sourcevalue).lower().startswith(str(destinationvalue).lower()):
return True
elif check.lower() == "endswith":
if str(sourcevalue).lower().endswith(str(destinationvalue).lower()):
return True
elif check.lower() == "contains":
if destinationvalue.lower() in sourcevalue.lower():
return True
elif check.lower() == "is empty" or check.lower() == "is_empty":
try:
if len(json.loads(sourcevalue)) == 0:
return True
except Exception as e:
self.logger.info(f"[ERROR] Failed to check if empty as list: {e}")
if len(str(sourcevalue)) == 0:
return True
elif check.lower() == "contains_any_of":
newvalue = [destinationvalue.lower()]
if "," in destinationvalue:
newvalue = destinationvalue.split(",")
elif ", " in destinationvalue:
newvalue = destinationvalue.split(", ")
for item in newvalue:
if not item:
continue
if item.strip() in sourcevalue:
return True
elif check.lower() == "larger than" or check.lower() == "bigger than":
try:
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
if int(sourcevalue) > int(destinationvalue):
return True
except AttributeError as e:
self.logger.info("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e))
try:
destinationvalue = len(json.loads(destinationvalue))
except Exception as e:
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
try:
# Check if it's a list in autocast and if so, check the length
if len(json.loads(sourcevalue)) > int(destinationvalue):
return True
except Exception as e:
self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}")
elif check.lower() == "smaller than" or check.lower() == "less than":
self.logger.info("In smaller than check: %s %s" % (sourcevalue, destinationvalue))
try:
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
if int(sourcevalue) < int(destinationvalue):
return True
except AttributeError as e:
pass
try:
destinationvalue = len(json.loads(destinationvalue))
except Exception as e:
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
try:
# Check if it's a list in autocast and if so, check the length
if len(json.loads(sourcevalue)) < int(destinationvalue):
return True
except Exception as e:
self.logger.info(f"[WARNING] Failed to check if smaller than as list: {e}")
elif check.lower() == "re" or check.lower() == "matches regex":
try:
found = re.search(str(destinationvalue), str(sourcevalue))
except re.error as e:
return False
except Exception as e:
return False
if found == None:
return False
return True
else:
self.logger.error("[DEBUG] Condition: can't handle %s yet. Setting to true" % check)
return False
def check_branch_conditions(action, fullexecution, self): def check_branch_conditions(action, fullexecution, self):
# relevantbranches = workflow.branches where destination = action # relevantbranches = workflow.branches where destination = action
@@ -3286,8 +3288,7 @@ class AppBase:
self.logger.error("[ERROR] Skipping '%s' -> %s -> '%s' because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"])) self.logger.error("[ERROR] Skipping '%s' -> %s -> '%s' because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"]))
continue continue
# Configuration = negated because of WorkflowAppActionParam.. validation = self.validate_condition(sourcevalue, condition["condition"]["value"], destinationvalue)
validation = run_validation(sourcevalue, condition["condition"]["value"], destinationvalue)
try: try:
if condition["condition"]["configuration"]: if condition["condition"]["configuration"]:
validation = not validation validation = not validation
+26 -3
View File
@@ -571,14 +571,20 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
//return //return
} }
if len(actionResult.ExecutionId) == 0 {
resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Provide execution_id and authorization"}`)))
return
}
ctx := context.Background() ctx := context.Background()
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId) workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
if err != nil { if err != nil || workflowExecution.ExecutionId != actionResult.ExecutionId {
if len(actionResult.ExecutionId) > 0 { if len(actionResult.ExecutionId) > 0 {
log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err) log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err)
} }
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
return return
} }
@@ -637,9 +643,26 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
} }
} }
if workflowExecution.Workflow.Sharing == "form" {
newWorkflow := shuffle.Workflow{
Name: workflowExecution.Workflow.Name,
ID: workflowExecution.Workflow.ID,
Owner: workflowExecution.Workflow.Owner,
OrgId: workflowExecution.Workflow.OrgId,
Sharing: workflowExecution.Workflow.Sharing,
Description: workflowExecution.Workflow.Description,
InputQuestions: workflowExecution.Workflow.InputQuestions,
InputMarkdown: workflowExecution.Workflow.InputMarkdown,
}
workflowExecution.Results = []shuffle.ActionResult{}
workflowExecution.Workflow = newWorkflow
}
newjson, err := json.Marshal(workflowExecution) newjson, err := json.Marshal(workflowExecution)
if err != nil { if err != nil {
resp.WriteHeader(401) resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`)))
return return
} }
+1 -1
View File
@@ -1129,7 +1129,7 @@ const AppFramework = (props) => {
}, [newSelectedApp]) }, [newSelectedApp])
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const imgSize = 50; const imgSize = 50;
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
+1 -1
View File
@@ -63,7 +63,7 @@ const AppSelection = props => {
document.title = "Choose your apps" document.title = "Choose your apps"
const ref = useRef() const ref = useRef()
let navigate = useNavigate(); let navigate = useNavigate();
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
useEffect(() => { useEffect(() => {
if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) { if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
+1 -1
View File
@@ -24,7 +24,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
const Appsearch = props => { const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
//const theme = useTheme(); //const theme = useTheme();
+60 -29
View File
@@ -42,6 +42,7 @@ import {
Delete, Delete,
RestaurantRounded, RestaurantRounded,
Cloud, Cloud,
CheckCircle
} from "@mui/icons-material"; } from "@mui/icons-material";
//import { useAlert //import { useAlert
@@ -71,6 +72,7 @@ const Billing = (props) => {
const [currentAppRunsInNumber, setCurrentAppRunsInNumber] = useState(0); const [currentAppRunsInNumber, setCurrentAppRunsInNumber] = useState(0);
const [alertThresholds, setAlertThresholds] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.AlertThreshold !== undefined && selectedOrganization.Billing.AlertThreshold !== null ? selectedOrganization.Billing.AlertThreshold : [{ percentage: '', count: '', Email_send: false }]); const [alertThresholds, setAlertThresholds] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.AlertThreshold !== undefined && selectedOrganization.Billing.AlertThreshold !== null ? selectedOrganization.Billing.AlertThreshold : [{ percentage: '', count: '', Email_send: false }]);
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const [deleteAlertVerification, setDeleteAlertVerification] = useState(false);
useEffect(() => { useEffect(() => {
if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) { if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) {
@@ -353,13 +355,13 @@ const Billing = (props) => {
if (subscription.name === "Enterprise" && subscription.active === true) { if (subscription.name === "Enterprise" && subscription.active === true) {
top_text = "Current Plan" top_text = "Current Plan"
newPaperstyle.border = "1px solid #f85a3e" // newPaperstyle.border = "1px solid #f85a3e"
} }
var showSupport = false var showSupport = false
if (subscription.name.includes("default")) { if (subscription.name.includes("default")) {
top_text = "Custom Contract" top_text = "Custom Contract"
newPaperstyle.border = "1px solid #f85a3e" newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
showSupport = true showSupport = true
} }
@@ -379,7 +381,7 @@ const Billing = (props) => {
if (highlight === true) { if (highlight === true) {
// Add an "Upgrade now" button // Add an "Upgrade now" button
newPaperstyle.border = "1px solid #f85a3e" newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
} }
if (hovered) { if (hovered) {
@@ -820,7 +822,8 @@ const Billing = (props) => {
height: 40, height: 40,
fontSize: 16, fontSize: 16,
color: "white", color: "white",
backgroundImage: userdata.has_card_available ? null : "linear-gradient(to right, #f86a3e, #f34079)", backgroundColor: userdata.has_card_available ? null : "#f86743",
// backgroundImage: userdata.has_card_available ? null : "linear-gradient(to right, #f86a3e, #f34079)",
textTransform: "none", textTransform: "none",
}} }}
@@ -852,7 +855,7 @@ const Billing = (props) => {
height: 40, height: 40,
fontSize: 16, fontSize: 16,
color: "white", color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", backgroundColor: "#f86743",
textTransform: 'none' textTransform: 'none'
}} }}
onClick={() => { onClick={() => {
@@ -1036,11 +1039,11 @@ const Billing = (props) => {
<Paper style={{ <Paper style={{
padding: 20, padding: 20,
// maxWidth: 400, // maxWidth: 400,
minWidth: 340, width: 340,
height: 480, height: 480,
backgroundColor: hovered ? "#232427" : theme.palette.platformColor, backgroundColor: hovered ? "#232427" : theme.palette.platformColor,
borderRadius: theme.palette.borderRadius * 2, borderRadius: theme.palette.borderRadius * 2,
border: "1px solid #f85a3e", border: "1px solid rgba(255,255,255,0.3)",
marginRight: 10, marginRight: 10,
marginTop: 15, marginTop: 15,
}} }}
@@ -1133,8 +1136,8 @@ const Billing = (props) => {
height: 40, height: 40,
fontSize: 16, fontSize: 16,
color: "white", color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", backgroundColor: "#f86743",
textTransform: 'none' textTransform: 'none',
}} }}
onClick={() => { onClick={() => {
if (Cloud) { if (Cloud) {
@@ -1188,7 +1191,7 @@ const Billing = (props) => {
height: 40, height: 40,
fontSize: 16, fontSize: 16,
color: "white", color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", backgroundColor: "#f86743",
textTransform: 'none', textTransform: 'none',
cursor: getProfessionalServices ? 'pointer' : 'not-allowed', cursor: getProfessionalServices ? 'pointer' : 'not-allowed',
opacity: getProfessionalServices ? 1 : 0.6, opacity: getProfessionalServices ? 1 : 0.6,
@@ -1315,7 +1318,7 @@ const Billing = (props) => {
width: 340, width: 340,
backgroundColor: hovered ? "#232427" : theme.palette.platformColor, backgroundColor: hovered ? "#232427" : theme.palette.platformColor,
borderRadius: theme.palette.borderRadius * 2, borderRadius: theme.palette.borderRadius * 2,
border: "1px solid #f85a3e", border: "1px solid rgba(255,255,255,0.3)",
marginRight: 10, marginRight: 10,
marginTop: 15, marginTop: 15,
}} }}
@@ -1373,7 +1376,7 @@ const Billing = (props) => {
height: 40, height: 40,
fontSize: 16, fontSize: 16,
color: "white", color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", backgroundColor: "#f86743",
textTransform: 'none' textTransform: 'none'
}} }}
onClick={() => { onClick={() => {
@@ -1381,6 +1384,8 @@ const Billing = (props) => {
ReactGA.event({ ReactGA.event({
category: "Billing", category: "Billing",
action: "click_public_training_button", action: "click_public_training_button",
label: "Public Training",
userId: userdata?.id
}); });
} }
navigate("/training") navigate("/training")
@@ -1399,21 +1404,24 @@ const Billing = (props) => {
height: 40, height: 40,
fontSize: 16, fontSize: 16,
color: "white", color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", backgroundColor: "#f86743",
textTransform: 'none' textTransform: 'none'
}} }}
onClick={() => { onClick={() => {
if (Cloud) { if (Cloud) {
ReactGA.event({ ReactGA.event({
category: "Billing", category: "Billing",
action: "click_public_training_button", action: "click_private_training_button",
label: "Private Training",
userId: userdata?.id,
}); });
} }
setOpenPrivateTraining(true) setOpenPrivateTraining(true);
}} }}
> >
Private Training Private Training
</Button> </Button>
<Dialog open={openPrivateTraining} <Dialog open={openPrivateTraining}
onClose={() => setOpenPrivateTraining(false)} onClose={() => setOpenPrivateTraining(false)}
fullWidth fullWidth
@@ -1818,6 +1826,7 @@ const Billing = (props) => {
// Update currentIndex based on remaining elements // Update currentIndex based on remaining elements
const findCurrentIndex = newAlertThresholds.some(threshold => threshold.Email_send === false); const findCurrentIndex = newAlertThresholds.some(threshold => threshold.Email_send === false);
setCurrentIndex(findCurrentIndex ? newAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1); setCurrentIndex(findCurrentIndex ? newAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1);
toast.info("Alert Threshold deleted successfully. Don't forget to save your changes.");
}; };
const HandleEditOrgForAlertThreshold = (orgId) => { const HandleEditOrgForAlertThreshold = (orgId) => {
@@ -1997,15 +2006,6 @@ const Billing = (props) => {
/> />
</span> </span>
: null} : null}
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null :
<ConsultationManagement
globalUrl={globalUrl}
userdata={userdata}
selectedOrganization={selectedOrganization}
/> : null}
<TrainingService />
{isCloud && {isCloud &&
selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== undefined &&
@@ -2261,7 +2261,29 @@ const Billing = (props) => {
</div> </div>
) : null*/} ) : null*/}
<div style={{ marginTop: 20, marginLeft: 10 }}> {!isChildOrg && isCloud && (
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 10 }}>
<Typography style={{ marginBottom: 5 }} variant="h4">
Professional Services
</Typography>
<Typography color="textSecondary">
We offer priority support through consultations and training to help you make the most of our product. If you have any questions, please reach out to us at support@shuffler.io.
</Typography>
<div style={{ display: 'flex', flexDirection: 'row', marginTop: 5 }}>
{billingInfo.subscription !== undefined && billingInfo.subscription !== null ? (
isChildOrg ? null : (
<ConsultationManagement
globalUrl={globalUrl}
userdata={userdata}
selectedOrganization={selectedOrganization}
/>
)
) : null}
<TrainingService />
</div>
</div>
)}
<div style={{ marginTop: isCloud && 40, marginLeft: 10 }}>
<Typography <Typography
style={{ marginBottom: 5 }} style={{ marginBottom: 5 }}
variant="h4" variant="h4"
@@ -2303,7 +2325,9 @@ const Billing = (props) => {
: " " + 0 + " "} : " " + 0 + " "}
app runs. app runs.
</Typography> </Typography>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10 }}>
Please note: Once your app runs reach the set alert threshold, all admins in the organization will receive an email notification.
</Typography>
<div style={{ marginTop: 15 }}> <div style={{ marginTop: 15 }}>
{alertThresholds.map((threshold, index) => ( {alertThresholds.map((threshold, index) => (
<div key={index} style={{ display: 'flex', alignItems: 'center' }}> <div key={index} style={{ display: 'flex', alignItems: 'center' }}>
@@ -2354,6 +2378,7 @@ const Billing = (props) => {
margin="normal" margin="normal"
variant="outlined" variant="outlined"
/> />
<span style={{ marginLeft: alertThresholds[index].Email_send === true ? 10 : 35, color: 'green' }}>{alertThresholds[index].Email_send === true && <Tooltip title="We have already sent alert for this threshold."><CheckCircle /></Tooltip>}</span>
{ {
alertThresholds.length > 1 && alertThresholds.length > 1 &&
( (
@@ -2362,18 +2387,24 @@ const Billing = (props) => {
disableElevation disableElevation
sx={{ sx={{
padding: 0, padding: 0,
color: 'red',
'&:hover': { '&:hover': {
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
}} }}
onClick={() => { handleDeleteAlertThreshold(index) }} onClick={() => { setDeleteAlertVerification(true) }}
> >
<DeleteIcon /> <DeleteIcon sx={{ color: theme.palette.secondary.main }} />
</Button> </Button>
) )
} }
<Dialog open={deleteAlertVerification} onClose={() => setDeleteAlertVerification(false)} sx={{ '& .MuiBackdrop-root': { backgroundColor: 'rgba(0, 0, 0, 0.3)', }, }}>
<DialogTitle>Are you sure you want to delete this threshold?</DialogTitle>
<DialogActions>
<Button style={{ textTransform: 'none', fontSize: 16 }} color="primary" onClick={() => setDeleteAlertVerification(false)}>Cancel</Button>
<Button style={{ textTransform: 'none', fontSize: 16 }} color="secondary" onClick={() => { handleDeleteAlertThreshold(index); setDeleteAlertVerification(false) }} >Delete</Button>
</DialogActions>
</Dialog>
</div> </div>
))} ))}
</div> </div>
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import theme from "../theme.jsx"; import theme from "../theme.jsx";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import ReactJson from "react-json-view"; import ReactJson from "react-json-view-ssr";
import { import {
Typography, Typography,
+21 -5
View File
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks"; import { useInterval } from "react-powerhooks";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import theme from "../theme.jsx"; import theme from "../theme.jsx";
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
import { import {
InputAdornment, InputAdornment,
@@ -79,7 +80,7 @@ const ConfigureWorkflow = (props) => {
useEffect(() => { useEffect(() => {
if (requiredActions.length === 0) { if (requiredActions.length === 0) {
if (setConfigurationFinished !== undefined) { if (setConfigurationFinished !== undefined) {
setConfigurationFinished(true) setConfigurationFinished(true)
} }
} }
}, [requiredActions]) }, [requiredActions])
@@ -141,17 +142,18 @@ const ConfigureWorkflow = (props) => {
// Where is this from? // Where is this from?
if (workflow === undefined || workflow === null || workflow.id === undefined) { if (workflow === undefined || workflow === null || workflow.id === undefined) {
return null; //console.log("Workflow is undefined or null: ", workflow)
return null
} }
if (apps === undefined || apps === null) { if (apps === undefined || apps === null) {
console.log("Apps is undefined or null: ", apps) console.log("Apps is undefined or null: ", apps)
return null; return null
} }
if (appAuthentication === undefined || appAuthentication === null) { if (appAuthentication === undefined || appAuthentication === null) {
console.log("App authentication is undefined or null: ", appAuthentication) console.log("App authentication is undefined or null: ", appAuthentication)
return null; return null
} }
const getApp = (actionId, appId) => { const getApp = (actionId, appId) => {
@@ -1386,9 +1388,23 @@ const ConfigureWorkflow = (props) => {
: null : null
} }
<div style={{marginTop: 10, }} />
{/*
<WorkflowValidationTimeline
workflow={workflow}
apps={apps}
getParents={undefined}
execution={undefined}
/>
<div style={{marginBottom: 10, }} />
*/}
{requiredActions.length > 0 ? ( {requiredActions.length > 0 ? (
<span> <span>
<Typography variant="body2" style={{}}> <Typography variant="body2" color="textSecondary">
Please configure the following steps to help us complete your workflow. This can also be done later. Please configure the following steps to help us complete your workflow. This can also be done later.
</Typography> </Typography>
+141 -16
View File
@@ -1,22 +1,57 @@
import React from "react"; import React, { useState, useEffect, } from "react";
import { import {
Card, Card,
CardContent, CardContent,
IconButton, IconButton,
Typography, Typography,
Switch, Switch,
Tooltip,
Select,
MenuItem,
Divider,
FormLabel,
} from "@mui/material"; } from "@mui/material";
import EditIcon from "@mui/icons-material/Edit";
import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
import {
Edit as EditIcon,
} from "@mui/icons-material";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
import theme from '../theme.jsx'; import theme from '../theme.jsx';
const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, ...otherProps }) => {
const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, availableDetection, ruleMapping, setRuleMapping, ...otherProps }) => {
const [openCodeEditor, setOpenCodeEditor] = React.useState(false); const [openCodeEditor, setOpenCodeEditor] = React.useState(false);
const [fileData, setFileData] = React.useState(""); const [fileData, setFileData] = React.useState("");
const [isEnabled, setIsEnabled] = React.useState(otherProps.is_enabled); const [isEnabled, setIsEnabled] = React.useState(otherProps.is_enabled);
const [filteredBarchart, setFilteredBarchart] = React.useState(null)
const [responseValue, setResponseValue] = React.useState("No response action")
const isCloud = ["localhost:3002", "shuffler.io"].includes(window.location.host); const isCloud = ["localhost:3002", "shuffler.io"].includes(window.location.host);
console.log("Rulemapping: ", ruleMapping)
useEffect(() => {
//const url = `${globalUrl}/api/v1/stats/app_executions_test2`
//const resp = LoadStats(globalUrl, ruleName)
//const resp = LoadStats(globalUrl, "app_executions_test2")
const resp = LoadStats(globalUrl, "app_executions_cloud")
resp.then((data) => {
if (data === undefined) {
setFilteredBarchart([])
} else {
setFilteredBarchart(data)
}
})
if (ruleMapping !== undefined && ruleMapping !== null && ruleMapping.value !== undefined && ruleMapping.value !== null) {
console.log("FIX MAPPING FROM ruleMapping.value: ", ruleMapping)
}
}, [])
console.log("Response Value: ", responseValue)
const handleSwitchChange = (event) => { const handleSwitchChange = (event) => {
if (folderDisabled) { if (folderDisabled) {
toast.warn("Enable the directory to enable individual rules"); toast.warn("Enable the directory to enable individual rules");
@@ -32,7 +67,8 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
toggleRule(file_id, !newIsEnabled, globalUrl, () => { toggleRule(file_id, !newIsEnabled, globalUrl, () => {
setIsEnabled(newIsEnabled); setIsEnabled(newIsEnabled);
}) })
}; }
const UpdateText = (text) => { const UpdateText = (text) => {
fetch(`${globalUrl}/api/v1/files/${file_id}/edit`, { fetch(`${globalUrl}/api/v1/files/${file_id}/edit`, {
@@ -64,32 +100,121 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
<Card style={{ <Card style={{
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
minHeight: 100, minHeight: 100,
marginBottom: 10,
paddingBottom: 0,
}}> }}>
<CardContent> <CardContent
style={{
padding: "10px 30px 0px 30px",
}}
>
<div <div
style={{ style={{
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginBottom: 16, color: "white",
color: "white",
}} }}
> >
<Typography variant="h6">{ruleName}</Typography> <Typography variant="h6">{ruleName.replaceAll("_", " ")} ({filteredBarchart === null || filteredBarchart.total === undefined ? 0 : filteredBarchart.total})</Typography>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
<EditIcon /> <Select
</IconButton> MenuProps={{
<Switch disableScrollLock: true,
checked={isEnabled && !folderDisabled} }}
onChange={handleSwitchChange} labelId="Response Action"
disabled={false} value={responseValue}
/> SelectDisplayProps={{
style: {
color: "rgba(255,255,255,0.4)",
},
}}
fullWidth
onChange={(e) => {
toast("Changing response: " + e.target.value)
console.log("Target: ", e.target.value)
setResponseValue(e.target.value)
// FIXME: Handle:
// 1. Get the current cache for the detection
// 2. Create a new mapping for Detection -> Response
}}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
height: 40,
borderRadius: theme.palette.borderRadius,
}}
>
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value="No response action"
>
<em>No selected response</em>
</MenuItem>
<Divider />
{availableDetection === undefined || availableDetection === null ? null : availableDetection.map((data, index) => {
return (
<MenuItem
key={index}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
overflowX: "auto",
}}
value={data.name}
>
{data.name}
</MenuItem>
)
})}
</Select>
<Tooltip title="Edit Rule" placement="top">
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title={isEnabled && !folderDisabled ? "Disable Rule" : "Enable Rule"} placement="top">
<Switch
checked={isEnabled && !folderDisabled}
onChange={handleSwitchChange}
disabled={false}
/>
</Tooltip>
</div> </div>
</div> </div>
<div style={{
overflow: 'visible',
zIndex: 10,
//border: "1px solid rgba(255,255,255,0.3)",
borderRadius: theme.palette.borderRadius,
marginTop: 5,
minHeight: 40,
maxHeight: 40,
}}>
{filteredBarchart === null ? null :
<DashboardBarchart
timelineData={filteredBarchart}
/>
}
</div>
{/*
<Typography variant="body2" style={{ marginTop: '2%' }}> <Typography variant="body2" style={{ marginTop: '2%' }}>
{description} {description}
</Typography> </Typography>
*/}
<ShuffleCodeEditor <ShuffleCodeEditor
isCloud={isCloud} isCloud={isCloud}
+221 -203
View File
@@ -2,6 +2,7 @@ import React, { useEffect, useContext } from "react";
import theme from '../theme.jsx'; import theme from '../theme.jsx';
import { isMobile } from "react-device-detect" import { isMobile } from "react-device-detect"
import { MuiChipsInput } from "mui-chips-input"; import { MuiChipsInput } from "mui-chips-input";
import { toast } from "react-toastify"
import UsecaseSearch from "../components/UsecaseSearch.jsx" import UsecaseSearch from "../components/UsecaseSearch.jsx"
import WorkflowGrid from "../components/WorkflowGrid.jsx" import WorkflowGrid from "../components/WorkflowGrid.jsx"
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -63,7 +64,7 @@ import {
} from "@mui/icons-material"; } from "@mui/icons-material";
const EditWorkflow = (props) => { const EditWorkflow = (props) => {
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, } = props const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, } = props
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
@@ -79,15 +80,30 @@ const EditWorkflow = (props) => {
const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "") const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "")
const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day')) const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day'))
const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : []) const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : [])
const [inputMarkdown, setInputMarkdown] = React.useState(workflow.input_markdown !== undefined && workflow.input_markdown !== null ? workflow.input_markdown : "") const [inputMarkdown, setInputMarkdown] = React.useState(workflow.input_markdown !== undefined && workflow.input_markdown !== null ? workflow.input_markdown : "")
const [outputMarkdown, setOutputMarkdown] = React.useState(workflow.output_markdown !== undefined && workflow.output_markdown !== null ? workflow.output_markdown : "") const [outputMarkdown, setOutputMarkdown] = React.useState(workflow.output_markdown !== undefined && workflow.output_markdown !== null ? workflow.output_markdown : "")
const [scrollDone, setScrollDone] = React.useState(false)
const classes = useStyles(); const classes = useStyles();
if (scrollTo !== undefined && scrollTo !== null && scrollTo.length > 0 && scrollDone === false) {
setTimeout(() => {
const foundScroll = document.getElementById(scrollTo)
if (foundScroll !== null) {
// Smooth scroll
foundScroll.scrollIntoView({ behavior: "smooth" })
}
}, 200)
setScrollDone(true)
}
// Gets the generated workflow // Gets the generated workflow
const getGeneratedWorkflow = (workflow_id) => { const getGeneratedWorkflow = (workflow_id) => {
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { const url = `${globalUrl}/api/v1/workflows/${workflow_id}`
fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -95,54 +111,55 @@ const EditWorkflow = (props) => {
}, },
credentials: "include", credentials: "include",
}) })
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
console.log("Status not 200 when getting workflow"); console.log("Status not 200 when getting workflow");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.id === workflow_id) {
console.log("GOT WORKFLOW: ", responseJson)
if (name === "") {
innerWorkflow.name = responseJson.name
setName(responseJson.name)
} }
return response.json(); if (description === "") {
}) innerWorkflow.description = responseJson.description
.then((responseJson) => { setDescription(description)
if (responseJson.id === workflow_id) {
console.log("GOT WORKFLOW: ", responseJson)
if (name === "") {
innerWorkflow.name = responseJson.name
setName(responseJson.name)
}
if (description === "") {
innerWorkflow.description = responseJson.description
setDescription(description)
}
if (newWorkflowTags === []) {
innerWorkflow.tags = responseJson.tags
setNewWorkflowTags(responseJson.tags)
}
if (selectedUsecases === []) {
selectedUsecases = responseJson.usecase_ids
}
innerWorkflow.id = responseJson.id
innerWorkflow.blogpost = responseJson.blogpost
innerWorkflow.actions = responseJson.actions
innerWorkflow.triggers = responseJson.triggers
innerWorkflow.branches = responseJson.branches
innerWorkflow.comments = responseJson.comments
innerWorkflow.workflow_variables = responseJson.workflow_variables
innerWorkflow.execution_variables = responseJson.execution_variables
setInnerWorkflow(innerWorkflow)
setUpdate(Math.random())
} }
})
.catch((error) => { if (newWorkflowTags === []) {
//toast(error.toString()); innerWorkflow.tags = responseJson.tags
console.log("Get workflow error: ", error.toString()); setNewWorkflowTags(responseJson.tags)
}) }
}
if (selectedUsecases === []) {
selectedUsecases = responseJson.usecase_ids
}
innerWorkflow.id = responseJson.id
innerWorkflow.blogpost = responseJson.blogpost
innerWorkflow.actions = responseJson.actions
innerWorkflow.triggers = responseJson.triggers
innerWorkflow.branches = responseJson.branches
innerWorkflow.comments = responseJson.comments
innerWorkflow.workflow_variables = responseJson.workflow_variables
innerWorkflow.execution_variables = responseJson.execution_variables
setInnerWorkflow(innerWorkflow)
setUpdate(Math.random())
}
})
.catch((error) => {
//toast(error.toString());
console.log("Get workflow error: ", error.toString());
})
}
if (foundWorkflowId.length > 0) { if (foundWorkflowId.length > 0) {
getGeneratedWorkflow(foundWorkflowId) getGeneratedWorkflow(foundWorkflowId)
@@ -162,6 +179,7 @@ const EditWorkflow = (props) => {
return ( return (
<Drawer <Drawer
anchor={"right"}
open={modalOpen} open={modalOpen}
onClose={() => { onClose={() => {
setModalOpen(false); setModalOpen(false);
@@ -191,7 +209,7 @@ const EditWorkflow = (props) => {
<Tooltip title="Open Workflow Form for 'normal' users"> <Tooltip title="Open Workflow Form for 'normal' users">
<a <a
rel="noopener noreferrer" rel="noopener noreferrer"
href={`/workflows/${workflow.id}/run`} href={`/forms/${workflow.id}`}
target="_blank" target="_blank"
style={{ style={{
textDecoration: "none", textDecoration: "none",
@@ -207,17 +225,18 @@ const EditWorkflow = (props) => {
} }
</div> </div>
<Typography variant="body2" color="textSecondary" style={{marginTop: 20, maxWidth: 440,}}> <Typography variant="body2" color="textSecondary" style={{marginTop: 20, maxWidth: 440,}}>
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a> Workflows can be built from scratch, or from templates. <a href="/usecases2" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
</Typography> </Typography>
{/*
<div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}> <div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}>
<WorkflowValidationTimeline <WorkflowValidationTimeline
originalWorkflow={workflow}
apps={apps} apps={apps}
workflow={workflow} workflow={workflow}
/> />
</div> </div>
*/}
{showUpload === true ? {showUpload === true ?
<div style={{ float: "right" }}> <div style={{ float: "right" }}>
@@ -247,7 +266,7 @@ const EditWorkflow = (props) => {
</div> </div>
</DialogTitle> </DialogTitle>
<FormControl> <FormControl>
<div style={{borderTop: "1px solid rgba(255,255,255,0.5)", width: 600, position: "fixed", left: 0, bottom: 0, zIndex: 1002, backgroundColor: "rgba(53,53,53,1)", height: 75, paddingTop: 20, paddingLeft: 75, }}> <div style={{borderTop: "1px solid rgba(255,255,255,0.5)", width: 600, position: "fixed", right: 20, bottom: 0, zIndex: 1002, backgroundColor: "rgba(53,53,53,1)", height: 75, paddingTop: 20, paddingLeft: 75, }}>
{/* {/*
<Button <Button
style={{}} style={{}}
@@ -602,7 +621,7 @@ const EditWorkflow = (props) => {
userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ? userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ?
userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ? userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ?
<Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}> <Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
Your organization does not have any suborgs yet. Please <a href="/admin?tab=suborgs" style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">make one</a>, then try again. Your organization does not have any suborgs yet, OR you may not have access to any suborgs directly.. Please <a href="/admin?tab=suborgs" style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">make one</a> or get access to suborgs by admin, then try again.
</Typography> </Typography>
: :
<Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}> <Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
@@ -702,153 +721,8 @@ const EditWorkflow = (props) => {
<Divider style={{marginTop: 20, marginBottom: 20, }} /> <Divider style={{marginTop: 20, marginBottom: 20, }} />
<Typography variant="h6" style={{marginTop: 50, }}>
Input fields
</Typography>
<Typography variant="body2" color="textSecondary" style={{marginBottom: 20, }}>
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the <a href={`/workflows/${workflow.id}/run`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>workflow run page</a>. If chosen in the User Input node, these will be required fields.
</Typography>
{inputQuestions.map((data, index) => {
console.log("Inputfield: ", data)
return (
<div style={{display: "flex", }}>
<TextField
disabled={data.deleted === true}
style={{
height: 50,
flex: 2,
marginTop: 0,
marginBottom: 0,
backgroundColor: theme.palette.inputColor,
marginRight: 5,
}}
fullWidth={true}
placeholder="Question"
id="standard-required"
margin="normal"
variant="outlined"
defaultValue={data.name}
onChange={(e) => {
inputQuestions[index].name = e.target.value
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
minHeight: 50,
},
}}
/>
<TextField
disabled={data.deleted === true}
style={{
height: 50,
flex: 2,
marginTop: 0,
marginBottom: 0,
backgroundColor: theme.palette.inputColor,
marginRight: 5,
}}
fullWidth={true}
placeholder="JSON key"
id="standard-required"
margin="normal"
variant="outlined"
defaultValue={data.value}
onChange={(e) => {
inputQuestions[index].value = e.target.value
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
minHeight: 50,
},
}}
/>
<Button
color="primary"
style={{ maxWidth: 50, marginLeft: 15 }}
disabled={data.deleted === true}
variant="outlined"
onClick={() => {
// Remove current index
console.log("Removing index: ", index)
inputQuestions[index].deleted = true
setUpdate(Math.random());
}}
>
<RemoveIcon style={{}} />
</Button>
</div>
)
})}
<Button
color="primary"
style={{ maxWidth: 50, marginLeft: 15, marginTop: 20, }}
variant="outlined"
onClick={() => {
inputQuestions.push({
"name": "",
"value": "",
"deleted": false,
"required": false
})
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
>
<AddIcon style={{}} />
</Button>
{inputQuestions.length === 0 ? null :
<div>
<Typography variant="h6" style={{marginTop: 50, }}>
Input Markdown
</Typography>
<TextField
multiline
rows={3}
fullWidth
color="primary"
value={inputMarkdown}
onChange={(e) => {
setInputMarkdown(e.target.value)
workflow.input_markdown = e.target.value
setWorkflow(workflow)
setUpdate(Math.random())
}}
/>
{/*
<Typography variant="h6" style={{marginTop: 50, }}>
Output Markdown
</Typography>
<TextField
multiLine
rows={3}
fullWidth
color="primary"
/>
*/}
</div>
}
<Divider style={{marginTop: 20, marginBottom: 20, }} />
<Typography variant="body1" style={{marginTop: 50, }}> <Typography variant="body1" style={{marginTop: 50, }}>
Git Backup Repository Git Backup Repository
</Typography> </Typography>
@@ -998,8 +872,152 @@ const EditWorkflow = (props) => {
</span> </span>
</Grid> </Grid>
</Grid> </Grid>
</div>
: null} <Divider style={{marginTop: 20, marginBottom: 20, }} />
<Typography variant="h6" style={{marginTop: 50, }}>
Input fields
</Typography>
<Typography variant="body2" color="textSecondary" style={{marginBottom: 20, }}>
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the <a href={`/forms/${workflow.id}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Form page</a> for this workflow. If chosen in the User Input node, these will be required fields. Use Semi-Colon ";" to create dropdown options. The first key will be the name shown, and subsequent keys will be the available values.
</Typography>
{inputQuestions.map((data, index) => {
var showListinfo = false
if (data.value !== undefined && data.value !== null && data.value.length > 0) {
if (data.value.includes(";")) {
showListinfo = true
}
}
return (
<div style={{display: "flex", }}>
<TextField
disabled={data.deleted === true}
style={{
flex: 2,
marginTop: 0,
marginBottom: 0,
backgroundColor: theme.palette.inputColor,
marginRight: 5,
}}
fullWidth={true}
placeholder="Question"
id="standard-required"
margin="normal"
variant="outlined"
defaultValue={data.name}
onChange={(e) => {
inputQuestions[index].name = e.target.value
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
}}
/>
<TextField
disabled={data.deleted === true}
style={{
flex: 2,
marginTop: 0,
marginBottom: 0,
backgroundColor: theme.palette.inputColor,
marginRight: 5,
}}
fullWidth={true}
placeholder="$exec JSON key"
id="standard-required"
margin="normal"
variant="outlined"
helperText={showListinfo === true ? "Dropdown list" : null}
defaultValue={data.value}
onChange={(e) => {
// Replace multiple semicolon with one
e.target.value = e.target.value.replace(";;", ";")
inputQuestions[index].value = e.target.value
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
}}
/>
<Button
color="primary"
style={{ maxWidth: 50, marginLeft: 15 }}
disabled={data.deleted === true}
variant="outlined"
onClick={() => {
// Remove current index
console.log("Removing index: ", index)
inputQuestions[index].deleted = true
setUpdate(Math.random());
}}
>
<RemoveIcon style={{}} />
</Button>
</div>
)
})}
<Button
color="primary"
style={{ maxWidth: 50, marginLeft: 15, marginTop: 20, }}
variant="outlined"
disabled={inputQuestions !== undefined && inputQuestions !== null && inputQuestions.length > 5}
onClick={() => {
inputQuestions.push({
"name": "",
"value": "",
"deleted": false,
"required": false
})
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
>
<AddIcon style={{}} />
</Button>
<div id="input_markdown">
<Typography variant="h6" style={{marginTop: 50, }}>
Input Markdown
</Typography>
<Typography variant="body2" color="textSecondary" style={{marginBottom: 20, }}>
Markdown will be shown on the <a href={`/forms/${workflow.id}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Form page</a>. Output for a Workflow is also shown in Markdown, and is controlled by the LAST action that runs.
</Typography>
<TextField
multiline
minRows={3}
fullWidth
color="primary"
value={inputMarkdown}
onKeyDown={(e) => {
//console.log("KEY: ", e.key)
if (e.key === "Tab") {
e.preventDefault()
}
}}
onChange={(e) => {
setInputMarkdown(e.target.value)
workflow.input_markdown = e.target.value
setWorkflow(workflow)
setUpdate(Math.random())
}}
/>
</div>
</div>
: null}
<Tooltip color="primary" title={"Add more details"} placement="top"> <Tooltip color="primary" title={"Add more details"} placement="top">
@@ -1047,7 +1065,7 @@ const EditWorkflow = (props) => {
</span> </span>
: null} : null}
{newWorkflow === true && name.length > 2 ? {/*newWorkflow === true && name.length > 2 ?
<div style={{marginLeft: 30, }}> <div style={{marginLeft: 30, }}>
<WorkflowGrid <WorkflowGrid
maxRows={1} maxRows={1}
@@ -1062,7 +1080,7 @@ const EditWorkflow = (props) => {
onlyResults={true} onlyResults={true}
/> />
</div> </div>
: null} : null*/}
</FormControl> </FormControl>
</Drawer> </Drawer>
) )
+29 -25
View File
@@ -132,6 +132,8 @@ const Header = (props) => {
isMobile, isMobile,
serverside, serverside,
billingInfo, billingInfo,
notifications,
} = props; } = props;
const [isHeader, setIsHeader] = React.useState(false); const [isHeader, setIsHeader] = React.useState(false);
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
@@ -311,18 +313,19 @@ const Header = (props) => {
localStorage.setItem("globalUrl", responseJson.region_url); localStorage.setItem("globalUrl", responseJson.region_url);
//globalUrl = responseJson.region_url //globalUrl = responseJson.region_url
} }
if (responseJson["reason"] === "SSO_REDIRECT") { if (responseJson["reason"] === "SSO_REDIRECT") {
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
setTimeout(() => { setTimeout(() => {
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
window.location.href = responseJson["url"] window.location.href = responseJson["url"]
return return
}, 2000) }, 2000)
} else { } else {
toast("Successfully changed active organization - refreshing!");
setTimeout(() => { setTimeout(() => {
window.location.reload() window.location.reload()
}, 2000); }, 2000);
} }
toast("Successfully changed active organization - refreshing!");
} else { } else {
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) { if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
toast(responseJson.reason); toast(responseJson.reason);
@@ -416,26 +419,19 @@ const Header = (props) => {
</MenuItem> </MenuItem>
</Link> </Link>
<Link to="/admin?admin_tab=priorities" style={hrefStyle}>
<MenuItem
onClick={(event) => {
handleClose();
}}
>
<NotificationsIcon style={{ marginRight: 5 }} /> Notifications
</MenuItem>
</Link>
<Divider style={{ marginTop: 10, marginBottom: 10, }} /> <Divider style={{ marginTop: 10, marginBottom: 10, }} />
<Link to="/docs" style={hrefStyle}> <Link to="/admin?admin_tab=priorities" style={hrefStyle}>
<MenuItem <MenuItem
onClick={(event) => { onClick={(event) => {
handleClose(); handleClose();
}} }}
> >
<HelpOutlineIcon style={{ marginRight: 5 }} /> About <NotificationsIcon style={{ marginRight: 5 }} /> Notifications ({
</MenuItem> notifications === undefined || notifications === null ? 0 :
</Link> notifications?.filter((notification) => notification.read === false).length
})
</MenuItem>
</Link>
{/* {/*
<Link to="/getting-started" style={hrefStyle}> <Link to="/getting-started" style={hrefStyle}>
<MenuItem <MenuItem
@@ -457,7 +453,7 @@ const Header = (props) => {
</MenuItem> </MenuItem>
</Link> </Link>
{userdata?.public_username === undefined || userdata?.public_username === null || userdata?.public_username.length <= 0 ? null : {/*userdata?.public_username === undefined || userdata?.public_username === null || userdata?.public_username.length <= 0 ? null :
<Link to={`/creators/${userdata.public_username}`} style={hrefStyle}> <Link to={`/creators/${userdata.public_username}`} style={hrefStyle}>
<MenuItem <MenuItem
onClick={(event) => { onClick={(event) => {
@@ -467,9 +463,18 @@ const Header = (props) => {
<EmojiObjectsIcon style={{ marginRight: 5 }} /> Creator page <EmojiObjectsIcon style={{ marginRight: 5 }} /> Creator page
</MenuItem> </MenuItem>
</Link> </Link>
} */}
<Divider style={{ marginTop: 10, marginBottom: 10, }} /> <Divider style={{ marginTop: 10, marginBottom: 10, }} />
<Link to="/docs" style={hrefStyle}>
<MenuItem
onClick={(event) => {
handleClose();
}}
>
<HelpOutlineIcon style={{ marginRight: 5 }} /> About
</MenuItem>
</Link>
<MenuItem <MenuItem
style={{ color: "white" }} style={{ color: "white" }}
onClick={(event) => { onClick={(event) => {
@@ -483,7 +488,7 @@ const Header = (props) => {
<Divider style={{ marginBottom: 10, }} /> <Divider style={{ marginBottom: 10, }} />
<Typography variant="body2" color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, }}> <Typography variant="body2" color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, }}>
Version: 1.4.0 Version: 1.4.5
</Typography> </Typography>
</Menu> </Menu>
</span> </span>
@@ -910,7 +915,6 @@ const Header = (props) => {
}} }}
> >
{avatarMenu} {avatarMenu}
{/*notificationMenu*/}
{supportMenu} {supportMenu}
{logoCheck} {logoCheck}
</span> </span>
+4 -4
View File
@@ -167,7 +167,7 @@ const AuthenticationOauth2 = (props) => {
//console.log("APP: ", selectedApp) //console.log("APP: ", selectedApp)
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") { if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
handleOauth2Request( handleOauth2Request(
"efe4c3fe-84a1-4821-a84f-23a6cfe8e72d", "fd55c175-aa30-4fa6-b303-09a29fb3f750",
"", "",
"https://graph.microsoft.com", "https://graph.microsoft.com",
["Mail.ReadWrite", "Mail.Send", "offline_access"], ["Mail.ReadWrite", "Mail.Send", "offline_access"],
@@ -524,7 +524,7 @@ const AuthenticationOauth2 = (props) => {
//alert('"Secure Payment" window closed!'); //alert('"Secure Payment" window closed!');
if (getAppAuthentication !== undefined) { if (getAppAuthentication !== undefined) {
getAppAuthentication(true, true, true); getAppAuthentication(true, true, true, selectedAction.id)
} }
toast("Authentication successful!") toast("Authentication successful!")
@@ -538,7 +538,7 @@ const AuthenticationOauth2 = (props) => {
setFinalized(true) setFinalized(true)
} }
} else { } else {
console.log("Not closed") //console.log("Not closed")
} }
}, 1000); }, 1000);
//do { //do {
@@ -739,7 +739,7 @@ const AuthenticationOauth2 = (props) => {
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<span style={{}}> <span style={{}}>
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. {authenticationType.type === "oauth2-app" ? null : <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b>&nbsp;-&nbsp;</span>} Oauth2 requires a client ID and secret to authenticate, defined in the remote system. <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b>&nbsp;-&nbsp;</span>
<a <a
target="_blank" target="_blank"
rel="norefferer" rel="norefferer"
@@ -323,6 +323,44 @@ const OrgHeaderexpanded = (props) => {
setSSORequired(event.target.checked); setSSORequired(event.target.checked);
}; };
const HandleTestSSO = () => {
const url = `${globalUrl}/api/v1/orgs/${selectedOrganization?.id}/change`;
const data = {
org_id: selectedOrganization?.id,
sso_test: true,
}
fetch(url, {
mode: "cors",
credentials: "include",
crossDomain: true,
method: "POST",
body: JSON.stringify(data),
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
}).then((response) => {
if (response.status !== 200) {
toast.error("Failed to test sso. Please try again later or contact support@shuffler.io if issue persist.")
return
}
return response.json();
}).then((responjson) => {
if (responjson["reason"] === "SSO_REDIRECT") {
setTimeout(() => {
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
window.location.href = responjson["url"];
return
}, 2000)
} else {
toast.error("No SSO found for this org. Please set up sso for this org.")
}
}).catch((err) => {
console.log("error for sso test is: ", err)
})
}
return ( return (
<div style={{ textAlign: "center" }}> <div style={{ textAlign: "center" }}>
<Grid container spacing={3} style={{ textAlign: "left" }}> <Grid container spacing={3} style={{ textAlign: "left" }}>
@@ -677,6 +715,42 @@ const OrgHeaderexpanded = (props) => {
{SSORequired ? 'Required' : 'Optional'} {SSORequired ? 'Required' : 'Optional'}
</div> </div>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 20, marginLeft: 10, width: "100%", borderBottom: '1px solid #414347', paddingBottom: 10 }}>
<Typography variant="body1" style={{ margin: '5px 0px 5px 10px' }}>
You can test your SSO configuration by clicking the button below. Before testing, ensure you have set Open ID Connect or SAML SSO credentials.
</Typography>
<Tooltip
title={
!(
ssoEntrypoint.length > 0 ||
ssoCertificate.length > 0 ||
openidAuthorization.length > 0 ||
openidClientId.length > 0
)
? "Please ensure all SSO credentials are set before testing."
: ""
}
>
<span style={{ width: 100 }}>
<Button
variant="outlined"
color="primary"
style={{ width: 100, textTransform: 'none', margin: 10 }}
disabled={
!(
ssoEntrypoint.length > 0 ||
ssoCertificate.length > 0 ||
openidAuthorization.length > 0 ||
openidClientId.length > 0
)
}
onClick={HandleTestSSO}
>
Test SSO
</Button>
</span>
</Tooltip>
</div>
<Grid item xs={12} style={{}}> <Grid item xs={12} style={{}}>
<Typography variant="h6" style={{ textAlign: "center", }}>OpenID connect</Typography> <Typography variant="h6" style={{ textAlign: "center", }}>OpenID connect</Typography>
<Grid container style={{ marginTop: 10, }}> <Grid container style={{ marginTop: 10, }}>
+113 -236
View File
@@ -90,9 +90,10 @@ import {
Circle as CircleIcon, Circle as CircleIcon,
SquareFoot as SquareFootIcon, SquareFoot as SquareFootIcon,
Storage as StorageIcon, Storage as StorageIcon,
Check as CheckIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
const useStyles = makeStyles({ export const useStyles = makeStyles({
notchedOutline: { notchedOutline: {
borderColor: "#f85a3e !important", borderColor: "#f85a3e !important",
}, },
@@ -1500,92 +1501,7 @@ const ParsedAction = (props) => {
<DescriptionIcon style={{ color: "rgba(255,255,255,0.7)" }} /> <DescriptionIcon style={{ color: "rgba(255,255,255,0.7)" }} />
</Tooltip> </Tooltip>
</IconButton> </IconButton>
{/*
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
onClick={() => {}}
>
<a
href="https://shuffler.io/docs/workflows#nodes"
rel="norefferer"
target="_blank"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
<Tooltip
color="primary"
title="What are actions?"
placement="top"
>
<HelpOutlineIcon style={{ color: "rgba(255,255,255,0.7)" }} />
</Tooltip>
</a>
</IconButton>
*/}
{/*
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
onClick={() => {
//setAuthenticationModalOpen(true);
console.log("Should enable/disable magic!")
console.log("Action: ", selectedAction)
if (selectedAction.run_magic_output === undefined) {
selectedAction.run_magic_output = true
} else {
if (selectedAction.run_magic_output === true) {
selectedAction.run_magic_output = false
} else {
selectedAction.run_magic_output = true
}
}
setSelectedAction(selectedAction)
setUpdate(Math.random());
}}
>
<Tooltip
color="primary"
title={selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "Click to enable magic parsing" : "Click to disable magic parsing"}
placement="top"
>
<AutoFixHighIcon style={{ color: selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "rgba(255,255,255,0.7)" : "#f86a3e"}} />
</Tooltip>
</IconButton>
*/}
{/*
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
onClick={() => {
}}
>
<Tooltip
color="primary"
title={"Find related tworkflows"}
placement="top"
>
<a href={`https://shuffler.io/search?tab=workflows&q=${selectedAction.app_name}`} target="_blank">
<SearchIcon style={{ color: "rgba(255,255,255,0.7)"}} />
</a>
</Tooltip>
</IconButton>
*/}
<IconButton <IconButton
style={{ style={{
marginTop: "auto", marginTop: "auto",
@@ -1602,6 +1518,10 @@ const ParsedAction = (props) => {
aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
//} //}
setAutocompleting(true) setAutocompleting(true)
setTimeout(() => {
setAutocompleting(false)
}, 3000)
}} }}
> >
<Tooltip <Tooltip
@@ -1998,7 +1918,7 @@ const ParsedAction = (props) => {
for (let [key,keyval] in Object.entries(selectedAction.parameters)) { for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
if (selectedAction.parameters[key].configuration === false) { if (selectedAction.parameters[key].configuration === false) {
console.log("FIELDSKIP: ", selectedAction.parameters[key].name) //console.log("FIELDSKIP: ", selectedAction.parameters[key].name)
continue continue
} }
@@ -2085,7 +2005,18 @@ const ParsedAction = (props) => {
}} }}
value={data} value={data}
> >
{data.last_modified === true ?
{data?.validation?.valid === true ?
<Tooltip title="Authentication has been validated" placement="top">
<Chip
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer", borderColor: green, }}
label={"Valid"}
variant="outlined"
color="secondary"
/>
</Tooltip>
: null }
{data?.last_modified === true ?
<Chip <Chip
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}} style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
label={"Latest"} label={"Latest"}
@@ -2093,14 +2024,14 @@ const ParsedAction = (props) => {
color="secondary" color="secondary"
/> />
: null} : null}
{data.app.app_version !== undefined && data.app.app_version !== null && data.app.app_version !== "" && data.app.app_version !== "undefined" ? {/*data.app.app_version !== undefined && data.app.app_version !== null && data.app.app_version !== "" && data.app.app_version !== "undefined" ?
<Chip <Chip
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}} style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
label={data.app.app_version} label={data.app.app_version}
variant="outlined" variant="outlined"
color="secondary" color="secondary"
/> />
: null} : null*/}
{data.label} {data.label}
</MenuItem> </MenuItem>
); );
@@ -2342,6 +2273,7 @@ const ParsedAction = (props) => {
value={selectedAction} value={selectedAction}
classes={{ inputRoot: classes.inputRoot }} classes={{ inputRoot: classes.inputRoot }}
groupBy={(option) => { groupBy={(option) => {
// FIXME: Sorting
// Most popular // Most popular
// Is categorized // Is categorized
// Uncategorized // Uncategorized
@@ -2364,7 +2296,6 @@ const ParsedAction = (props) => {
}, },
}} }}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
//console.log("Option contains?: ", inputValue, options)
const lowercaseValue = inputValue.toLowerCase() const lowercaseValue = inputValue.toLowerCase()
options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue))
@@ -2449,22 +2380,6 @@ const ParsedAction = (props) => {
extraUrl = descSplit[descSplit.length-1] extraUrl = descSplit[descSplit.length-1]
} }
//for (let [line,lineval] in Object.entries(descSplit)) {
// if (descSplit[line].includes("http") && descSplit[line].includes("://")) {
// const urlsplit = descSplit[line].split("/")
// try {
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/")
// } catch (e) {
// //console.log("Failed - running with -1")
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/")
// }
// //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line])
// //break
// }
//}
if (extraUrl.length > 0) { if (extraUrl.length > 0) {
if (extraUrl.includes(" ")) { if (extraUrl.includes(" ")) {
extraUrl = extraUrl.split(" ")[0] extraUrl = extraUrl.split(" ")[0]
@@ -2492,6 +2407,7 @@ const ParsedAction = (props) => {
); );
}} }}
renderInput={(params) => { renderInput={(params) => {
if (params.inputProps?.value) { if (params.inputProps?.value) {
const prefixes = ["Post", "Put", "Patch"]; const prefixes = ["Post", "Put", "Patch"];
for (let prefix of prefixes) { for (let prefix of prefixes) {
@@ -2512,84 +2428,86 @@ const ParsedAction = (props) => {
} }
const actionDescription = ( const actionDescription = null
<Box /*(
p={1.5} <Box
borderRadius={3} p={1.5}
boxShadow={2} borderRadius={3}
backgroundColor={theme.palette.textFieldStyle} boxShadow={2}
display="flex" backgroundColor={theme.palette.textFieldStyle}
flexDirection="column" display="flex"
> flexDirection="column"
<Box display="flex" alignItems="center" justifyContent="space-between">
<Typography variant="body1" style={{ flexGrow: 1 }}>
{params.inputProps.value}
</Typography>
<IconButton size="small"
onMouseDown={(event) => {
event.preventDefault();
event.stopPropagation();
}}
onClick={() => {
setHiddenDescription(true)
const inputElement = document.getElementById(uiBox);
if (inputElement) {
inputElement.focus();
}
}}>
<CloseIcon fontSize="small" />
</IconButton>
</Box>
<Divider sx={{ backgroundColor: theme.palette.surfaceColor, marginTop: "5px", marginBottom : "10px", height: "3px" }}/>
<Box display="flex" flexDirection="column">
<Typography variant="body2" mb={0.5}>
<strong>Description: </strong> {selectedAction?.description}
</Typography>
</Box>
</Box>
);
return (
<Tooltip title={actionDescription}
placement="right"
open={!hiddenDescription}
PopperProps={{
sx: {
'& .MuiTooltip-tooltip': {
backgroundColor: 'transparent',
boxShadow: 'none',
},
'& .MuiTooltip-arrow': {
color: 'transparent',
},
},
}}
> >
<TextField <Box display="flex" alignItems="center" justifyContent="space-between">
{...params} <Typography variant="body1" style={{ flexGrow: 1 }}>
{params.inputProps.value}
</Typography>
<IconButton size="small"
onMouseDown={(event) => {
event.preventDefault();
event.stopPropagation();
}}
data-lpignore="true" onClick={() => {
autocomplete="off" setHiddenDescription(true)
dataLPIgnore="true" const inputElement = document.getElementById(uiBox);
autoComplete="off" if (inputElement) {
inputElement.focus();
}
}}
>
<CloseIcon fontSize="small" />
</IconButton>
</Box>
<Divider sx={{ backgroundColor: theme.palette.surfaceColor, marginTop: "5px", marginBottom : "10px", height: "3px" }}/>
<Box display="flex" flexDirection="column">
<Typography variant="body2" mb={0.5}>
<strong>Description: </strong> {selectedAction?.description}
</Typography>
</Box>
</Box>
)
*/
color="primary" return (
id="checkbox-search" <Tooltip title={actionDescription}
variant="body1" placement="right"
style={{ open={!hiddenDescription}
backgroundColor: theme.palette.inputColor, PopperProps={{
borderRadius: theme.palette.borderRadius, sx: {
'& .MuiTooltip-tooltip': {
backgroundColor: 'transparent',
boxShadow: 'none',
},
'& .MuiTooltip-arrow': {
color: 'transparent',
},
},
}} }}
label={isIntegration ? "Choose a category" : "Find Actions"} >
variant="outlined" <TextField
name={`disable_autocomplete_${Math.random()}`} {...params}
/>
</Tooltip> data-lpignore="true"
); autocomplete="off"
}} dataLPIgnore="true"
/> autoComplete="off"
color="primary"
id="checkbox-search"
variant="body1"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
label={isIntegration ? "Choose a category" : "Find Actions"}
variant="outlined"
name={`disable_autocomplete_${Math.random()}`}
/>
</Tooltip>
);
}}
/>
) : null} ) : null}
{/*setNewSelectedAction !== undefined ? {/*setNewSelectedAction !== undefined ?
@@ -2939,58 +2857,24 @@ const ParsedAction = (props) => {
} }
} }
/* if (selectedAction.parameters === undefined || selectedAction.parameters === null || selectedAction.parameters.length !== selectedActionParameters.length) {
if (
(selectedAction.auth_not_required !== undefined && !selectedAction.auth_not_required) &&
selectedActionParameters[count] !== undefined &&
selectedActionParameters[count] !== null &&
selectedActionParameters[count].value !== undefined &&
selectedAction.parameters[count] !== undefined &&
selectedAction.parameters[count] !== null &&
selectedAction.parameters[count].value !== undefined &&
selectedAction.selectedAuthentication !== undefined &&
selectedAction.selectedAuthentication.fields !== undefined &&
selectedAction.selectedAuthentication.fields[data.name] !==
undefined
) {
*/
/*
if (selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) {
// This sets the placeholder in the frontend. (Replaced in backend)
selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name];
selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name];
setSelectedAction(selectedAction);
//setUpdate(Math.random())
if (authWritten) {
return null
}
authWritten = true
return (
<Typography
key={count}
id="skip_auth"
variant="body2"
color="textSecondary"
style={{ marginTop: 5 }}
>
Authentication fields are hidden
</Typography>
)
}
*/
//selectedAction.parameters = selectedActionParameters
console.log("PARAM BUG: ", selectedAction)
}
//!selectedAction.auth_not_required && //!selectedAction.auth_not_required &&
if (selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) { if (selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) {
// This sets the placeholder in the frontend. (Replaced in backend) // This sets the placeholder in the frontend. (Replaced in backend)
selectedActionParameters[count].value = if (selectedActionParameters[count] !== undefined) {
selectedAction.selectedAuthentication.fields[data.name]; selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
selectedAction.parameters[count].value = }
selectedAction.selectedAuthentication.fields[data.name];
if (selectedAction.parameters[count] !== undefined) {
selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
}
setSelectedAction(selectedAction); setSelectedAction(selectedAction);
//setUpdate(Math.random()) //setUpdate(Math.random())
@@ -3064,7 +2948,7 @@ const ParsedAction = (props) => {
if (data.value.length === 0) { if (data.value.length === 0) {
if (data.name.toLowerCase() === "headers") { if (data.name.toLowerCase() === "headers") {
console.log("Should show headers field instead with + and -!") //console.log("Should show headers field instead with + and -!")
// Check if file ID exists // Check if file ID exists
// //
@@ -3360,13 +3244,6 @@ const ParsedAction = (props) => {
<IconButton size="small" <IconButton size="small"
onClick={() => { onClick={() => {
setUiBox("closed") setUiBox("closed")
/*
const inputElement = document.getElementById(uiBox);
if (inputElement) {
inputElement.focus();
}
*/
}} }}
> >
<CloseIcon fontSize="small" /> <CloseIcon fontSize="small" />
+4 -1
View File
@@ -393,7 +393,10 @@ const Priorities = (props) => {
return ( return (
<div style={{width: clickedFromOrgTab ? 1030:1000, padding: clickedFromOrgTab ? 27:null, height: clickedFromOrgTab ? "auto":null, backgroundColor: clickedFromOrgTab ? '#212121':null, borderRadius: clickedFromOrgTab ? '16px':null, }}> <div style={{width: clickedFromOrgTab ? 1030:1000, padding: clickedFromOrgTab ? 27:null, height: clickedFromOrgTab ? "auto":null, backgroundColor: clickedFromOrgTab ? '#212121':null, borderRadius: clickedFromOrgTab ? '16px':null, }}>
<h2 style={{ display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, marginTop: clickedFromOrgTab?40:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications</h2> <h2 style={{ display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, marginTop: clickedFromOrgTab?40:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications ({
notifications?.filter((notification) => showRead === true || notification.read === false).length
})</h2>
<span style={{ marginLeft: clickedFromOrgTab?null:25, color: clickedFromOrgTab?"#9E9E9E":null, }}> <span style={{ marginLeft: clickedFromOrgTab?null:25, color: clickedFromOrgTab?"#9E9E9E":null, }}>
Notifications help you find potential problems with your workflows and apps.&nbsp; Notifications help you find potential problems with your workflows and apps.&nbsp;
<a <a
+1 -1
View File
@@ -24,7 +24,7 @@ import {
const Priority = (props) => { const Priority = (props) => {
const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props; const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
let navigate = useNavigate(); let navigate = useNavigate();
if (window.location.pathname === "/workflows") { if (window.location.pathname === "/workflows") {
+1 -1
View File
@@ -71,7 +71,7 @@ const SearchData = props => {
// return null // return null
//} //}
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
// if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) { // if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) {
// setModalOpen(false) // setModalOpen(false)
// } // }
@@ -44,7 +44,7 @@ import {
import { validateJson } from "../views/Workflows.jsx"; import { validateJson } from "../views/Workflows.jsx";
import ReactJson from "react-json-view"; import ReactJson from "react-json-view-ssr";
import PaperComponent from "../components/PaperComponent.jsx"; import PaperComponent from "../components/PaperComponent.jsx";
import { padding, textAlign } from '@mui/system'; import { padding, textAlign } from '@mui/system';
+1 -1
View File
@@ -349,7 +349,7 @@ const UsecaseSearch = (props) => {
const [selectedAction, setSelectedAction] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({});
const [firstRequest, setFirstRequest] = React.useState(true); const [firstRequest, setFirstRequest] = React.useState(true);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
//const alert = useAlert() //const alert = useAlert()
useEffect(() => { useEffect(() => {
+1 -1
View File
@@ -161,7 +161,7 @@ const WelcomeForm = (props) => {
const [clickdiff, setclickdiff] = useState(0); const [clickdiff, setclickdiff] = useState(0);
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
//const alert = useAlert(); //const alert = useAlert();
let navigate = useNavigate(); let navigate = useNavigate();
+1 -1
View File
@@ -28,7 +28,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
const AppGrid = props => { const AppGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs
//const [apps, setApps] = React.useState([]); //const [apps, setApps] = React.useState([]);
@@ -47,7 +47,7 @@ const WorkflowTemplatePopup = (props) => {
const [requestSent, setRequestSent] = React.useState(false) const [requestSent, setRequestSent] = React.useState(false)
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
let navigate = useNavigate(); let navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (modalOpen !== true) { if (modalOpen !== true) {
@@ -23,6 +23,7 @@ import {
grey, grey,
} from "../views/AngularWorkflow.jsx" } from "../views/AngularWorkflow.jsx"
import WorkflowTemplatePopup2 from "../components/WorkflowTemplatePopup2.jsx"
import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import theme from "../theme.jsx"; import theme from "../theme.jsx";
const itemHeight = 24 const itemHeight = 24
@@ -63,7 +64,7 @@ export const getParentNodes = (workflow, action) => {
currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey]) currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey])
if (currentnode === undefined) { if (currentnode === undefined) {
console.log("Could not find parent node for: ", allkeys[parentkey]) //console.log("Could not find parent node for: ", allkeys[parentkey])
continue continue
} }
} }
@@ -128,11 +129,13 @@ export const getParentNodes = (workflow, action) => {
} }
const WorkflowValidationTimeline = (props) => { const WorkflowValidationTimeline = (props) => {
const { workflow, originalWorkflow, apps, getParents, execution} = props const { globalUrl, userdata, workflow, originalWorkflow, apps, getParents, execution, showHoverColor, } = props
const [hovering, setHovering] = useState(false)
const [decidedColor, setDecidedColor] = useState(grey)
const [isClicked, setIsClicked] = useState(false)
const showMiddle = false const showMiddle = false
if (workflow === undefined || workflow === null) { if (workflow === undefined || workflow === null) {
return null return null
} }
@@ -147,12 +150,10 @@ const WorkflowValidationTimeline = (props) => {
if (workflow.triggers === undefined || workflow.triggers === null) { if (workflow.triggers === undefined || workflow.triggers === null) {
workflow.triggers = [] workflow.triggers = []
} }
if (workflow.branches === undefined || workflow.branches === null) { if (workflow.branches === undefined || workflow.branches === null) {
workflow.branches = [] workflow.branches = []
} }
var results = [] var results = []
@@ -260,6 +261,12 @@ const WorkflowValidationTimeline = (props) => {
relevantactions.push(...newactions) relevantactions.push(...newactions)
} }
console.log("Relevant actions (return null if 0-1): ", relevantactions)
if (relevantactions.length <= 1) {
return null
}
// Sort according to how many parents a node has. MAY be wrong~ // Sort according to how many parents a node has. MAY be wrong~
relevantactions.sort((a, b) => { relevantactions.sort((a, b) => {
if (a.order === undefined) { if (a.order === undefined) {
@@ -279,15 +286,81 @@ const WorkflowValidationTimeline = (props) => {
var skipped = false var skipped = false
var previousTools = false var previousTools = false
var scheduleNotStarted = false
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.validation_ran === false) {
console.log("Validation didn't run. Why?")
return null
}
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null && workflow.validation.errors.length > 0) {
var newErrors = []
for (var key in workflow.validation.errors) {
const error = workflow.validation.errors[key]
if (error.type === "SCHEDULE") {
scheduleNotStarted = true
continue
}
newErrors.push(error)
}
workflow.validation.errors = newErrors
}
// Use this variable to control visualization // Use this variable to control visualization
//const showMiddle = false //const showMiddle = false
// border: workflow.validation.valid ? `2px solid ${green}` : "1px solid rgba(255,255,255,0.4)", // border: workflow.validation.valid ? `2px solid ${green}` : "1px solid rgba(255,255,255,0.4)",
var middleError = "" var middleError = ""
var startBranchColor = "" var startBranchColor = ""
var middleBranchColor = ""
const showHoverForClick = showHoverColor === true ? true : false
return ( return (
<div style={{ padding: "10px 5px 10px 5px", borderRadius: theme.palette.borderRadius, }}> <div
style={{
padding: "10px 5px 10px 5px",
borderRadius: theme.palette.borderRadius,
border: hovering === true && showHoverForClick === true ? `1px solid ${decidedColor}` : "1px solid rgba(255,255,255,0.0)",
cursor: hovering === true && showHoverForClick === true ? "pointer" : "default",
}}
onMouseEnter={() => {
if (isClicked === false) {
setHovering(true)
}
}}
onMouseLeave={() => {
if (isClicked === false) {
setHovering(false)
}
}}
onClick={() => {
if (showHoverForClick === true) {
setIsClicked(true)
}
}}
>
{isClicked === false ? null :
<WorkflowTemplatePopup2
globalUrl={globalUrl}
userdata={userdata}
isModalOpenDefault={isClicked}
workflowBuilt={true}
setIsClicked={setIsClicked}
inputWorkflowId={workflow.id}
/>
}
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{scheduleNotStarted === true ?
null
: null}
{relevantactions.map((action, index) => { {relevantactions.map((action, index) => {
action.result = {} action.result = {}
if (results !== undefined) { if (results !== undefined) {
@@ -309,8 +382,10 @@ const WorkflowValidationTimeline = (props) => {
const validate = validateJson(action.result.result) const validate = validateJson(action.result.result)
if (validate.valid) { if (validate.valid) {
if (validate.result.success === true) { if (validate.result.success === true) {
nodecolor = green
branchcolor = green branchcolor = green
} else { } else {
nodecolor = grey
branchcolor = grey branchcolor = grey
} }
} }
@@ -319,9 +394,12 @@ const WorkflowValidationTimeline = (props) => {
} else if (action.status === "SKIPPED") { } else if (action.status === "SKIPPED") {
branchcolor = grey branchcolor = grey
} else { } else {
// FIXME: How do we handle this?
if (action.status === undefined) { if (action.status === undefined) {
branchcolor = green nodecolor = grey
branchcolor = grey
} else { } else {
nodecolor = red
branchcolor = red branchcolor = red
} }
} }
@@ -389,29 +467,64 @@ const WorkflowValidationTimeline = (props) => {
} }
} }
var appgroup = []
if (action.app_name === "shuffle-subflow") {
if (action.status === "SUCCESS") {
nodecolor = green
branchcolor = green
}
if (workflow.validation.subflow_apps !== undefined && workflow.validation.subflow_apps !== null && workflow.validation.subflow_apps.length > 0) {
nodecolor = red
branchcolor = red
for (var subflowkey in workflow.validation.subflow_apps) {
const subflowApp = workflow.validation.subflow_apps[subflowkey]
founderror += "- " + subflowApp.error+"\n"
if (subflowApp.error === action.id) {
appgroup.push(subflowApp)
}
}
}
}
if (!showMiddle && relevantactions.length > 2 && index > 0 && index === relevantactions.length - 2) { if (!showMiddle && relevantactions.length > 2 && index > 0 && index === relevantactions.length - 2) {
if (founderror.length > 0) { if (founderror.length > 0) {
middleError += founderror+"\n" middleError += founderror+"\n"
middleBranchColor = branchcolor
} }
if (index === relevantactions.length-2 && relevantactions.length > 2) { if (index === relevantactions.length-2 && relevantactions.length > 2) {
const selectedIcon = middleError.length > 0 ? const selectedIcon = middleError.length > 0 ?
<Tooltip title={middleError}> <Tooltip title={
<Typography variant="body1" style={{margin: 5, whiteSpace: "pre-line", }}>
{middleError}
</Typography>
}>
<IconButton style={{width: 30, height: 30, backgroundColor: "rgba(255,255,255,0.0)", borderRadius: 30, marginTop: 2, }}> <IconButton style={{width: 30, height: 30, backgroundColor: "rgba(255,255,255,0.0)", borderRadius: 30, marginTop: 2, }}>
<ErrorOutlineIcon style={{color: "red", }} /> <ErrorOutlineIcon style={{color: "red", }} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
: null : null
return ( return selectedIcon
selectedIcon
)
} else { } else {
return null return null
} }
} }
// Returns for anything non-middle
if (relevantactions.length > 2 && index >= 1 && index < relevantactions.length - 2) {
if (founderror.length > 0) {
middleError += founderror+"\n"
}
return null
}
if (skipped && !lastitem) { if (skipped && !lastitem) {
nodecolor = grey nodecolor = grey
branchcolor = grey branchcolor = grey
@@ -423,28 +536,14 @@ const WorkflowValidationTimeline = (props) => {
branchcolor = nodecolor branchcolor = nodecolor
} }
var appgroup = []
if (action.trigger_type === "WEBHOOK") { if (action.trigger_type === "WEBHOOK") {
nodecolor = green nodecolor = green
branchcolor = green branchcolor = green
} else if (action.app_name === "shuffle-subflow") {
if (action.status === "SUCCESS") {
nodecolor = green
branchcolor = green
}
for (var subflowkey in workflow.validation.subflow_apps) {
const subflowApp = workflow.validation.subflow_apps[subflowkey]
if (subflowApp.error === action.id) {
appgroup.push(subflowApp)
}
}
} }
var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3
var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3
if (nodecolor === green) { if (nodecolor === green) {
branchcolor = green branchcolor = green
} else if (nodecolor === yellow) { } else if (nodecolor === yellow) {
@@ -455,10 +554,27 @@ const WorkflowValidationTimeline = (props) => {
if (index === 0) { if (index === 0) {
startBranchColor = branchcolor startBranchColor = branchcolor
} else if (index !== 0 && index !== relevantactions.length - 1) {
// FIXME: This doesn't work yet
middleBranchColor = branchcolor
} }
if (lastitem && middleError.length === 0) { if (lastitem) {
branchcolor = startBranchColor if (middleError.length === 0) {
branchcolor = startBranchColor
} else {
//branchcolor = middleBranchColor
}
if (founderror === "") {
nodecolor = green
}
}
// FIXME: This could mean the workflow hasn't ran yet
if (workflow.validation.valid === false && (workflow.validation.errors === undefined || workflow.validation.errors === null || workflow.validation.errors.length == 0) && (workflow.validation.subflow_apps === undefined || workflow.validation.subflow_apps === null || workflow.validation.subflow_apps.length == 0)) {
nodecolor = grey
branchcolor = grey
} }
const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : "" const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : ""
@@ -488,6 +604,14 @@ const WorkflowValidationTimeline = (props) => {
console.log("MISSING IMAGE: ", appname, image, action) console.log("MISSING IMAGE: ", appname, image, action)
} }
if (decidedColor === grey && nodecolor === green) {
setDecidedColor(red)
}
if (decidedColor !== red && nodecolor === red) {
setDecidedColor(red)
}
return ( return (
<div style={{display: "flex", flex: flex, justifyContent: "right", }}> <div style={{display: "flex", flex: flex, justifyContent: "right", }}>
{lastitem ? {lastitem ?
@@ -528,7 +652,7 @@ const WorkflowValidationTimeline = (props) => {
: :
<Tooltip title={ <Tooltip title={
<Typography variant="body1" style={{margin: 5, color: "white", }}> <Typography variant="body1" style={{margin: 5, color: "white", }}>
{founderror.length > 0 ? founderror : `App: ${appname}`} {founderror.length > 0 ? founderror : `App: ${appname} - Action: ${action.label}`}
</Typography> </Typography>
} placement="top"> } placement="top">
+60 -7
View File
@@ -467,13 +467,66 @@ const data = [
"font-size": "0px", "font-size": "0px",
}, },
}, },
{ {
selector: "node:selected", selector: "node:selected",
css: { css: {
"border-color": "#f86a3e", "border-color": "#f86a3e",
"border-width": "7px", "border-width": "7px",
}, },
}, },
{
selector: `node[buttonType="condition-drag"]`,
css: {
"width": "5px",
"height": "5px",
"background-color": "#f85a3e",
},
},
{
selector: `node[name="switch"]`,
css: {
label: function(element) {
// Load from the actual element
var nodeheight = 400
var conditions = [{
"name": "Condition 1",
"check": "X equals Y",
},
{
"name": "Condition 2",
"check": "X2 equals Y2",
},
{
"name": "Condition 3",
"check": "X3 equals Y3",
}]
conditions.push({
"name": "Else",
"check": "If all else fails",
})
const newlines = nodeheight / conditions.length
console.log("Newlines: ", newlines)
const label = conditions.map((condition) => {
return `${condition.name}\n\n\n`
}).join("\n")
return label
},
color: "white",
"border-color": "#f85a3e",
"background-color": "#1f1f1f",
"font-size": "19px",
"text-margin-x": "-110px",
"text-wrap": "wrap",
shape: "roundrectangle",
width: "100",
height: "300",
},
},
]; ];
//{ //{
+3 -3
View File
@@ -6274,7 +6274,7 @@ If you're interested, please let me know a time that works for you, or set up a
style={{ minWidth: 50, maxWidth: 50 }} style={{ minWidth: 50, maxWidth: 50 }}
/> />
<ListItemText <ListItemText
primary="License" primary="Scale"
style={{ minWidth: 85, maxWidth: 85 }} style={{ minWidth: 85, maxWidth: 85 }}
/> />
<ListItemText <ListItemText
@@ -6406,7 +6406,7 @@ If you're interested, please let me know a time that works for you, or set up a
</Tooltip> </Tooltip>
) : ( ) : (
<Tooltip <Tooltip
title="Not licensed, and can't scale.. This may cause service disruption." title="In Verbose mode. Set SHUFFLE_SWARM_CONFIG=run to Scale. This will not be as verbose. Details: https://shuffler.io/docs/configuration#scaling-shuffle"
placement="top" placement="top"
> >
<a <a
@@ -6441,7 +6441,7 @@ If you're interested, please let me know a time that works for you, or set up a
environment.running_ip.length === 0 ? ( environment.running_ip.length === 0 ? (
<div>Not running</div> <div>Not running</div>
) : ( ) : (
environment.running_ip.split(":")[0] environment.running_ip
) )
) : ( ) : (
"N/A" "N/A"
File diff suppressed because it is too large Load Diff
+31 -9
View File
@@ -544,7 +544,7 @@ const AppCreator = (defaultprops) => {
}; };
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
useEffect(() => { useEffect(() => {
if (window.location.pathname.includes("apps/edit")) { if (window.location.pathname.includes("apps/edit")) {
@@ -897,8 +897,31 @@ const AppCreator = (defaultprops) => {
}; };
if (methodvalue["x-label"] !== undefined && methodvalue["x-label"] !== null) { if (methodvalue["x-label"] !== undefined && methodvalue["x-label"] !== null) {
console.log("LABEL: ", methodvalue["x-label"])
var correctlabel = ""
const labels = methodvalue["x-label"].split(",")
for (let labelkey in labels) {
var label = labels[labelkey].trim()
if (label.toLowerCase() === "no label") {
continue
}
// Remove quotes and escapes
label = label.replace(/['"]+/g, '')
label = label.replace(/\\/g, '')
//label = label.replace("_", " ", -1)
//label = label.charAt(0).toUpperCase() + label.slice(1)
correctlabel = label
break
}
console.log("LABEL: ", correctlabel)
// FIX: Map labels only if they're actually in the category list // FIX: Map labels only if they're actually in the category list
newaction.action_label = methodvalue["x-label"] //newaction.action_label = methodvalue["x-label"]
newaction.action_label = correctlabel
} }
if (methodvalue["x-required-fields"] !== undefined && methodvalue["x-required-fields"] !== null) { if (methodvalue["x-required-fields"] !== undefined && methodvalue["x-required-fields"] !== null) {
@@ -3124,15 +3147,14 @@ const AppCreator = (defaultprops) => {
Scopes for Oauth2 Scopes for Oauth2
</Typography> </Typography>
<MuiChipsInput <MuiChipsInput
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}} required
required
InputProps={{ InputProps={{
style: { style: {
color: "white", color: "white",
maxHeight: 50, maxHeight: 160,
}, },
}} }}
style={{ maxHeight: 80, overflowX: "hidden", overflowY: "auto" }} style={{ minHeight: 160, maxHeight: 160, overflowX: "hidden", overflowY: "auto" }}
placeholder="Available Oauth2 Scopes" placeholder="Available Oauth2 Scopes"
color="primary" color="primary"
fullWidth fullWidth
@@ -3159,7 +3181,7 @@ const AppCreator = (defaultprops) => {
required required
style={{ marginTop: 0, backgroundColor: inputColor }} style={{ marginTop: 0, backgroundColor: inputColor }}
fullWidth={true} fullWidth={true}
placeholder="Field Name (key, NOT your actual API-key)" placeholder="The Key to use as the header/query - NOT your actual API-key"
type="name" type="name"
id="standard-required" id="standard-required"
margin="normal" margin="normal"
@@ -4441,7 +4463,7 @@ const AppCreator = (defaultprops) => {
setUpdate(Math.random()) setUpdate(Math.random())
} }
}} }}
value={data.action_label} value={data?.action_label?.replace(" ", "_").toLowerCase()}
style={{ style={{
border: data.action_label === undefined || data.action_label === "No Label" ? "" : `2px solid ${bgColor}`, border: data.action_label === undefined || data.action_label === "No Label" ? "" : `2px solid ${bgColor}`,
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
@@ -4463,7 +4485,7 @@ const AppCreator = (defaultprops) => {
return ( return (
<MenuItem <MenuItem
key={labelindex} key={labelindex}
value={label} value={label.replace(" ", "_").toLowerCase()}
style={{ style={{
}} }}
> >
+1 -1
View File
@@ -3035,7 +3035,7 @@ const Apps = (props) => {
height: "50px", height: "50px",
}} }}
variant="contained" variant="contained"
disabled={openApi.length === 0 || appValidation.length > 0} disabled={openApi.length === 0 || appValidation.length > 0 || validation}
color="primary" color="primary"
onClick={() => { onClick={() => {
setOpenApiError(""); setOpenApiError("");
+151 -42
View File
@@ -11,6 +11,7 @@ import { useNavigate, Link, useParams } from "react-router-dom";
//import { useAlert //import { useAlert
import { ToastContainer, toast } from "react-toastify" import { ToastContainer, toast } from "react-toastify"
import Draggable from "react-draggable"; import Draggable from "react-draggable";
import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
import { import {
Autocomplete, Autocomplete,
@@ -18,6 +19,8 @@ import {
TextField, TextField,
IconButton, IconButton,
Button, Button,
Select,
MenuItem,
Typography, Typography,
Grid, Grid,
Paper, Paper,
@@ -146,28 +149,8 @@ const inputdata = [
const LineChartWrapper = ({keys, height, width}) => { const LineChartWrapper = ({keys, height, width}) => {
const [hovered, setHovered] = useState(""); const [hovered, setHovered] = useState("");
//console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
console.log("Keys: ", keys)
var inputdata = keys.data var inputdata = keys.data
/*
const inputdata = [{
"key": "Intel",
"data": [
{ key: new Date('11/22/2019'), data: 3, metadata: {color: "orange", "name": "Intel"}},
{ key: new Date('11/24/2019'), data: 8, metadata: {color: "orange", "name": "Intel"}},
{ key: new Date('11/29/2019'), data: 2, metadata: {color: "orange", "name": "Intel"}},
]},
{
"key": "Popper",
"data": [
{ key: new Date('11/24/2019'), data: 9, },
{ key: new Date('11/29/2019'), data: 3, },
]
}
]
*/
return ( return (
<div style={{}}> <div style={{}}>
<Typography variant="h6" style={{marginBotton: 15}}> <Typography variant="h6" style={{marginBotton: 15}}>
@@ -211,7 +194,6 @@ const LineChartWrapper = ({keys, height, width}) => {
offset: '5px, 5px' offset: '5px, 5px'
}} }}
content={(data, color) => { content={(data, color) => {
console.log("DATA: ", data)
const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No" const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No"
return ( return (
@@ -350,12 +332,35 @@ const Dashboard = (props) => {
const [frameworkData, setFrameworkData] = useState(undefined); const [frameworkData, setFrameworkData] = useState(undefined);
const [widgetData, setWidgetData] = useState([]); const [widgetData, setWidgetData] = useState([]);
const [newWidgetData, setNewWidgetData] = useState([]);
const [, setUpdate] = useState(0);
let navigate = useNavigate(); let navigate = useNavigate();
const isCloud = const isCloud =
window.location.host === "localhost:3002" || window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io"; window.location.host === "shuffler.io";
useEffect(() => {
const widgetnames = ["app_executions_cloud"]
for (let widgetkey in widgetnames) {
const widgetName = widgetnames[widgetkey]
console.log("NAME: ", widgetName)
const resp = LoadStats(globalUrl, widgetName)
if (resp !== undefined) {
resp.then((data) => {
console.log("Got data in parent: ", data)
if (data === undefined) {
} else {
newWidgetData.push(data)
setNewWidgetData(newWidgetData)
}
})
}
}
}, [])
useEffect(() => { useEffect(() => {
if (selectedUsecaseCategory.length === 0) { if (selectedUsecaseCategory.length === 0) {
@@ -368,6 +373,7 @@ const Dashboard = (props) => {
} }
}, [selectedUsecaseCategory]) }, [selectedUsecaseCategory])
const checkSelectedParams = () => { const checkSelectedParams = () => {
const urlSearchParams = new URLSearchParams(window.location.search) const urlSearchParams = new URLSearchParams(window.location.search)
const params = Object.fromEntries(urlSearchParams.entries()) const params = Object.fromEntries(urlSearchParams.entries())
@@ -409,23 +415,22 @@ const Dashboard = (props) => {
}, [usecases]) }, [usecases])
const getWidget = (dashboard, widget) => { const getWidget = (dashboard, widget) => {
fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, { fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, },
credentials: "include", credentials: "include",
}) })
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
console.log("Status not 200 for framework!"); console.log("Status not 200 for framework!");
} }
return response.json(); return response.json();
}) })
.then((responseJson) => { .then((responseJson) => {
console.log("Resp: ", responseJson)
if (responseJson.success === false) { if (responseJson.success === false) {
if (responseJson.reason !== undefined) { if (responseJson.reason !== undefined) {
//toast("Failed loading: " + responseJson.reason) //toast("Failed loading: " + responseJson.reason)
@@ -441,14 +446,12 @@ const Dashboard = (props) => {
} }
const foundWidget = widgetData.findIndex(data => data.title === widget) const foundWidget = widgetData.findIndex(data => data.title === widget)
console.log("Found: ", foundWidget)
if (foundWidget !== undefined && foundWidget !== null && foundWidget >= 0) { if (foundWidget !== undefined && foundWidget !== null && foundWidget >= 0) {
widgetData[foundWidget] = tmpdata widgetData[foundWidget] = tmpdata
} else { } else {
widgetData.push(tmpdata) widgetData.push(tmpdata)
} }
console.log("Data: ", widgetData)
setWidgetData(widgetData) setWidgetData(widgetData)
} }
}) })
@@ -480,7 +483,7 @@ const Dashboard = (props) => {
useEffect(() => { useEffect(() => {
getWidget("main", "Overall") getWidget("main", "Overall")
getWidget("main", "Overall2") getWidget("main", "Overall2")
}, []); }, [])
const fetchdata = (stats_id) => { const fetchdata = (stats_id) => {
fetch(globalUrl + "/api/v1/stats/" + stats_id, { fetch(globalUrl + "/api/v1/stats/" + stats_id, {
@@ -646,7 +649,6 @@ const Dashboard = (props) => {
stats["workflow_executions"].data !== undefined stats["workflow_executions"].data !== undefined
) { ) {
setStatsRan(true); setStatsRan(true);
//console.log("NEW DATA?: ", stats)
console.log("SET WORKFLOW: ", stats["workflow_executions"]); console.log("SET WORKFLOW: ", stats["workflow_executions"]);
//var curday = startDate.getDate() //var curday = startDate.getDate()
@@ -729,6 +731,100 @@ const Dashboard = (props) => {
</div> </div>
) : null; ) : null;
const WidgetController = (props) => {
const { data, index, availableStats, } = props
const [hovering, setHovering] = useState(false)
const newname = data.key !== undefined ? data.key.replaceAll("_", " ") : ""
console.log("KEYDATA: ", data)
const loadNewStats = (newkey) => {
const resp = LoadStats(globalUrl, newkey)
if (resp !== undefined) {
resp.then((respdata) => {
if (respdata === undefined || respdata === null) {
toast("Failed to laod data. Please try again, or contact support@shuffler.io if this persists.")
} else {
newWidgetData[index] = respdata
setNewWidgetData(newWidgetData)
setUpdate(Math.random())
}
})
}
}
return (
<Draggable>
<Paper
style={{
height: "100%", width: "100%", maxWidth: 500, margin: 15, padding: "15px 15px 15px 15px", textAlign: "left",
backgroundColor: hovering ? theme.palette.inputColor : theme.palette.backgroundColor,
cursor: hovering ? "pointer" : "default",
}}
onMouseEnter={() => {
setHovering(true)
}}
onMouseLeave={() => {
setHovering(false)
}}
>
<div style={{display: "flex", justifyContent: "space-between", alignItems: "center",}}>
<Typography variant="h6">
{newname}
</Typography>
{data.available_keys === undefined || data.available_keys === null || data.available_keys.length === 0 ? null :
<Select
MenuProps={{
disableScrollLock: true,
}}
labelId="Response Action"
value={data.key}
SelectDisplayProps={{
style: {
},
}}
fullWidth
onChange={(e) => {
loadNewStats(e.target.value)
}}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
height: 40,
maxWidth: 150,
borderRadius: theme.palette.borderRadius,
}}
>
{data.available_keys.map((foundKey, index) => {
const parsedKeyName = foundKey.replaceAll("_", " ")
return (
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={foundKey}
>
<em>{parsedKeyName}</em>
</MenuItem>
)
})}
</Select>
}
</div>
<DashboardBarchart
timelineData={data}
height={50}
/>
</Paper>
</Draggable>
)
}
const data = ( const data = (
<div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}> <div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
<div style={{width: 500, margin: "auto"}}> <div style={{width: 500, margin: "auto"}}>
@@ -739,12 +835,25 @@ const Dashboard = (props) => {
: null} : null}
</div> </div>
{widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null : {/*widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
<Draggable> <Draggable>
<Paper style={{height: 350, width: 500, padding: "15px 15px 15px 15px", }}> <Paper style={{height: 350, width: 500, padding: "15px 15px 15px 15px", }}>
<LineChartWrapper keys={widgetData[0]} height={280} width={470} /> <LineChartWrapper keys={widgetData[0]} height={280} width={470} />
</Paper> </Paper>
</Draggable> </Draggable>
*/}
{newWidgetData === undefined || newWidgetData === null || newWidgetData === [] ? null :
newWidgetData.map((data, index) => {
return (
<WidgetController
key={index}
index={index}
data={data}
/>
)
})
} }
</div> </div>
); );
+12 -2
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Markdown from 'react-markdown' import Markdown from 'react-markdown'
import theme from '../theme.jsx'; import theme from '../theme.jsx';
import ReactJson from "react-json-view"; import ReactJson from "react-json-view-ssr";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
import { BrowserView, MobileView } from "react-device-detect"; import { BrowserView, MobileView } from "react-device-detect";
import { useParams, useNavigate, Link } from "react-router-dom"; import { useParams, useNavigate, Link } from "react-router-dom";
@@ -152,9 +152,19 @@ export const OuterLink = (props) => {
export const Img = (props) => { export const Img = (props) => {
var height = "auto"
var width = 750
if (props.height !== undefined && props.height !== null) {
height = props.height
}
if (props.width !== undefined && props.width !== null) {
width = props.width
}
return( return(
<img <img
style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: width, maxWidth: width, margin: "auto", marginTop: 10, marginBottom: 10, }}
alt={props.alt} alt={props.alt}
src={props.src} src={props.src}
/> />
+370 -385
View File
@@ -7,15 +7,17 @@ import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx";
import { useNavigate, Link, useParams } from "react-router-dom"; import { useNavigate, Link, useParams } from "react-router-dom";
import { validateJson, GetIconInfo } from "./Workflows.jsx"; import { validateJson, GetIconInfo } from "./Workflows.jsx";
import EditWorkflow from "../components/EditWorkflow.jsx" import EditWorkflow from "../components/EditWorkflow.jsx"
import { ToastContainer, toast } from "react-toastify" import { toast } from "react-toastify"
import { makeStyles } from '@mui/material/styles'; import { makeStyles } from '@mui/material/styles';
import { useInterval } from "react-powerhooks"; import { useInterval } from "react-powerhooks";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import theme from '../theme.jsx'; import theme from '../theme.jsx';
import rehypeRaw from "rehype-raw";
import { import {
Tooltip, Tooltip,
Select,
IconButton, IconButton,
CircularProgress, CircularProgress,
TextField, TextField,
@@ -28,6 +30,7 @@ import {
Dialog, Dialog,
DialogTitle, DialogTitle,
DialogContent, DialogContent,
MenuItem,
} from '@mui/material'; } from '@mui/material';
import { import {
@@ -45,7 +48,7 @@ const bodyDivStyle = {
width: isMobile? "100%":"500px", width: isMobile? "100%":"500px",
position: "relative", position: "relative",
marginTop: 25, paddingBottom: 250,
} }
@@ -71,7 +74,7 @@ const RunWorkflow = (defaultprops) => {
const boxStyle = { const boxStyle = {
color: "white", color: "white",
padding: 50, padding: "25px 50px 50px 50px",
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
marginBottom: 150, marginBottom: 150,
borderRadius: 25, borderRadius: 25,
@@ -102,6 +105,17 @@ const RunWorkflow = (defaultprops) => {
const [executionInfo, setExecutionInfo] = useState(""); const [executionInfo, setExecutionInfo] = useState("");
const handleValidateForm = (executionArgument) => { const handleValidateForm = (executionArgument) => {
// Check if every field exists
if (executionArgument === undefined || executionArgument === null) {
return true
}
for (var key in executionArgument) {
if (executionArgument[key] === undefined || executionArgument[key] === null || executionArgument[key] === "") {
return false
}
}
return true return true
} }
@@ -124,7 +138,10 @@ const RunWorkflow = (defaultprops) => {
return response.json(); return response.json();
}) })
.then((responseJson) => { .then((responseJson) => {
toast.success("Saved workflow") if (responseJson.success === false) {
toast.error("Failed saving workflow. Please try again.")
}
//toast.success("Saved workflow")
}) })
.catch((error) => { .catch((error) => {
toast.error("Save workflow error: " + error) toast.error("Save workflow error: " + error)
@@ -197,178 +214,25 @@ const RunWorkflow = (defaultprops) => {
*/} */}
{executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ? {executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ?
<Typography variant="h6"> <div style={{marginTop: 20, }}>
{executionData.result} <Divider />
</Typography> <Markdown
components={{
img: Img,
code: CodeHandler,
a: OuterLink,
}}
id="markdown_wrapper"
escapeHtml={false}
style={{
maxWidth: "100%", minWidth: "100%",
}}
>
{executionData.result}
</Markdown>
</div>
: null} : null}
{/*executionData.results.map((data, index) => {
if (executionData.results.length !== 1 && (data.status === "SKIPPED")) {
return null;
}
// FIXME: The latter replace doens't really work if ' is used in a string
var showResult = data.result.trim();
const validate = validateJson(showResult);
const curapp = apps.find((a) => a.name === data.action.app_name && a.app_version === data.action.app_version);
const imgsize = 50;
const statusColor = data.status === "FINISHED" || data.status === "SUCCESS" ? green : data.status === "ABORTED" || data.status === "FAILURE" ? "red" : yellow;
var imgSrc = curapp === undefined ? "" : curapp.large_image;
if (
imgSrc.length === 0 &&
workflow.actions !== undefined &&
workflow.actions !== null
) {
// Look for the node in the workflow
const action = workflow.actions.find(
(action) => action.id === data.action.id
);
if (action !== undefined && action !== null) {
imgSrc = action.large_image;
}
}
var actionimg =
curapp === null ? null : (
<img
alt={data.action.app_name}
src={imgSrc}
style={{
marginRight: 20,
width: imgsize,
height: imgsize,
border: `2px solid ${statusColor}`,
borderRadius:
executionData.start === data.action.id ? 25 : 5,
}}
/>
);
if (data.action.app_name === "shuffle-subflow") {
//const parsedImage = triggers[2].large_image;
//actionimg = (
// <img
// alt={"Shuffle Subflow"}
// src={parsedImage}
// style={{
// marginRight: 20,
// width: imgsize,
// height: imgsize,
// border: `2px solid ${statusColor}`,
// borderRadius:
// executionData.start === data.action.id ? 25 : 5,
// }}
// />
//);
} else if (data.action.app_name === "User Input") {
//actionimg = (
// <img
// alt={"Shuffle Subflow"}
// src={triggers[3].large_image}
// style={{
// marginRight: 20,
// width: imgsize,
// height: imgsize,
// border: `2px solid ${statusColor}`,
// borderRadius:
// executionData.start === data.action.id ? 25 : 5,
// }}
// />
//);
}
if (validate.valid && typeof validate.result === "string") {
validate.result = JSON.parse(validate.result);
}
if (validate.valid && typeof validate.result === "object") {
if (
validate.result.result !== undefined &&
validate.result.result !== null
) {
try {
validate.result.result = JSON.parse(validate.result.result);
} catch (e) {
//console.log("ERROR PARSING: ", e)
}
}
}
var similarActionsView = null
if (data.similar_actions !== undefined && data.similar_actions !== null) {
var minimumMatch = 85
var matching_executions = []
if (data.similar_actions !== undefined && data.similar_actions !== null) {
for (let [k,kval] in Object.entries(data.similar_actions)){
if (data.similar_actions.hasOwnProperty(k)) {
if (data.similar_actions[k].similarity > minimumMatch) {
matching_executions.push(data.similar_actions[k].execution_id)
}
}
}
}
if (matching_executions.length !== 0) {
var parsed_url = matching_executions.join(",")
similarActionsView =
<Tooltip
color="primary"
title="See executions with similar results (not identical)"
placement="top"
style={{ zIndex: 50000, marginLeft: 50, }}
>
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
paddingLeft: 0,
width: 30,
}}
onClick={() => {
//navigate(`?execution_highlight=${parsed_url}`)
}}
>
<PreviewIcon style={{ color: "rgba(255,255,255,0.5)" }} />
</IconButton>
</Tooltip>
}
}
return (
<div
key={index}
style={{
marginBottom: 20,
border:
data.action.sub_action === true
? "1px solid rgba(255,255,255,0.3)"
: "1px solid rgba(255,255,255, 0.3)",
borderRadius: theme.palette.borderRadius,
backgroundColor: theme.palette.inputColor,
padding: "15px 10px 10px 10px",
overflow: "hidden",
}}
onMouseOver={() => {
}}
onMouseOut={() => {
}}
>
<div style={{ marginBottom: 5, display: "flex" }}>
<Typography variant="body1">
<b>Status&nbsp;</b>
</Typography>
<Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
{data.status}
</Typography>
</div>
</div>
)
})*/}
</div> </div>
) )
} }
@@ -388,7 +252,7 @@ const RunWorkflow = (defaultprops) => {
var data = { var data = {
"execution_argument": executionArgument, "execution_argument": executionArgument,
"execution_source": "questions", "execution_source": "form",
} }
if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) { if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) {
@@ -467,6 +331,10 @@ const RunWorkflow = (defaultprops) => {
} }
} }
if (response.status === 401 || response.status === 403) {
toast("This Form is not available for you to run. If you this is an error, contact support@shuffler.io with a link to this form")
}
return response.json() return response.json()
}) })
.then(responseJson => { .then(responseJson => {
@@ -509,7 +377,9 @@ const RunWorkflow = (defaultprops) => {
} }
const getWorkflow = (workflow_id, selectedNode) => { const getWorkflow = (workflow_id, selectedNode) => {
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { const url = `${globalUrl}/api/v1/workflows/${workflow_id}`
fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -522,6 +392,10 @@ const RunWorkflow = (defaultprops) => {
console.log("Status not 200 for workflows :O!"); console.log("Status not 200 for workflows :O!");
} }
if (response.status === 401 || response.status === 403) {
toast("This Form is not available to you. If you think this is an error, please contact support@shuffler.io with the URL.")
}
return response.json(); return response.json();
}) })
.then((responseJson) => { .then((responseJson) => {
@@ -543,16 +417,24 @@ const RunWorkflow = (defaultprops) => {
} }
if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0) { if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0) {
var newexec = {} var newexec = {}
for (let questionkey in responseJson.input_questions) { for (let questionkey in responseJson.input_questions) {
const question = responseJson.input_questions[questionkey] const question = responseJson.input_questions[questionkey]
newexec[question.value] = ""
var multiChoiceOptions = question.value !== undefined && question.value !== null && question.value.length > 0 && question.value.includes(";") ? question.value.split(";") : []
if (multiChoiceOptions.length > 1) {
newexec[multiChoiceOptions[0]] = ""
} else {
newexec[question.value] = ""
}
} }
setExecutionArgument(newexec) setExecutionArgument(newexec)
} }
if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) { if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) {
var found = false var found = false
for (var actionkey in responseJson.actions) { for (var actionkey in responseJson.actions) {
if (responseJson.actions[actionkey].id === selectedNode) { if (responseJson.actions[actionkey].id === selectedNode) {
@@ -568,6 +450,7 @@ const RunWorkflow = (defaultprops) => {
continue continue
} }
setFoundSourcenode(responseJson.triggers[triggerkey]) setFoundSourcenode(responseJson.triggers[triggerkey])
if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0 && responseJson.triggers[triggerkey].trigger_type === "USERINPUT") { if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0 && responseJson.triggers[triggerkey].trigger_type === "USERINPUT") {
@@ -864,215 +747,277 @@ const RunWorkflow = (defaultprops) => {
const basedata = const basedata =
<div style={bodyDivStyle}> <div style={bodyDivStyle}>
<Paper style={boxStyle}> <Paper style={boxStyle}>
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? {workflow.id === undefined || workflow.id === null ?
<div style={{marginBottom: 20, }}> <div style={{paddingTop: 150, marginTop: 150, width: 250, itemAlign: "center", textAlign: "center", margin: "auto", }}>
<Markdown <CircularProgress />
components={{ <Typography variant="body1" style={{marginTop: 20, }}>
img: Img, Loding Form Details...
code: CodeHandler,
a: OuterLink,
}}
id="markdown_wrapper"
escapeHtml={false}
style={{
maxWidth: "100%", minWidth: "100%",
}}
>
{workflow.input_markdown}
</Markdown>
</div>
: null}
<form onSubmit={(e) => {onSubmit(e)}} style={{margin: "50px 0px 15px 0px",}}>
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? null :
<div>
<img
alt={workflow.name}
src={image}
style={{
marginRight: 20,
width: 100,
height: 100,
border: `2px solid ${green}`,
borderRadius: 50,
position: "absolute",
top: -50,
left: 200,
}}
/>
<Typography variant="h6" style={{marginBottom: 10, marginTop: 50, textAlign: "center", }}>
{organization}
</Typography> </Typography>
<Divider style={{marginTop: 20, marginBottom: 20, }}/> </div>
:
<div>
{disabledButtons && message.length > 0 ? null : {workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ?
<Typography color="textSecondary" style={{textAlign: "center", }}> <div style={{marginBottom: 20, }}>
{message} <Markdown
components={{
img: Img,
code: CodeHandler,
a: OuterLink,
}}
id="markdown_wrapper"
escapeHtml={false}
style={{
maxWidth: "100%", minWidth: "100%",
}}
rehypePlugins={[rehypeRaw]}
>
{workflow.input_markdown}
</Markdown>
</div>
: null}
<form onSubmit={(e) => {onSubmit(e)}} style={{margin: "25px 0px 15px 0px",}}>
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? null :
<div>
<img
alt={workflow.name}
src={image}
style={{
marginRight: 20,
width: 100,
height: 100,
border: `2px solid ${green}`,
borderRadius: 50,
position: "absolute",
top: -50,
left: 200,
}}
/>
<Typography variant="h6" style={{marginBottom: 10, marginTop: 50, textAlign: "center", }}>
{organization}
</Typography> </Typography>
} <Divider style={{marginTop: 20, marginBottom: 20, }}/>
{answer !== undefined && answer !== null ? null : {disabledButtons && message.length > 0 ? null :
<Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography> <Typography color="textSecondary" style={{textAlign: "center", }}>
} {message}
{workflowQuestion.length > 0 ?
<div style={{
backgroundColor: theme.palette.inputColor,
padding: 20,
borderRadius: theme.palette.borderRadius,
marginBottom: 35,
marginTop: 30,
}}>
<Typography variant="body1" style={{ marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
{workflowQuestion}
</Typography> </Typography>
</div> }
: null}
</div> {answer !== undefined && answer !== null ? null :
} <Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography>
}
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ? {workflowQuestion.length > 0 ?
<div style={{marginBottom: 5, }}> <div style={{
{workflow.input_questions.map((question, index) => { backgroundColor: theme.palette.inputColor,
return ( padding: 20,
<div style={{marginBottom: 5}}> borderRadius: theme.palette.borderRadius,
{question.name} marginBottom: 35,
<TextField marginTop: 30,
color="primary" }}>
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }} <Typography variant="body1" style={{ marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
label={question.value} {workflowQuestion}
required </Typography>
</div>
disabled={disabledButtons}
fullWidth={true}
placeholder=""
id="emailfield"
margin="normal"
variant="outlined"
onBlur={(e) => {
//setExecutionArgument(e.target.value)
executionArgument[question.value] = e.target.value
setUpdate(Math.random())
}}
/>
</div>
)
})}
</div>
:
answer !== undefined && answer !== null ? null :
<span>
Runtime Argument
<div style={{marginBottom: 5}}>
<TextField
color="primary"
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
multiLine
maxRows={2}
InputProps={{
style:{
height: "50px",
color: "white",
fontSize: "1em",
},
}}
fullWidth={true}
placeholder=""
id="emailfield"
margin="normal"
variant="outlined"
onChange={(e) => {
setExecutionArgument(e.target.value)
}}
/>
</div>
</span>
}
{executionRunning ?
<span style={{width: 50, height: 50, margin: "auto", alignItems: "center", justifyContent: "center", textAlign: "center", }}>
<CircularProgress style={{marginTop: 20, marginBottom: 20, marginLeft: 185, }}/>
{executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ?
<Typography variant="body2" style={{margin: "auto", marginTop: 20, marginBottom: 20, textAlign: "center", alignItem: "center", }} color="textSecondary">
Status: {executionData.status}
</Typography>
: null} : null}
</span>
</div>
}
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
<div style={{marginBottom: 5, }}>
{workflow.input_questions.map((question, index) => {
// Multiple choice checks for semicolon-splits
var multiChoiceOptions = question.value !== undefined && question.value !== null && question.value.length > 0 && question.value.includes(";") ? question.value.split(";") : []
// Remove empty keys from array
multiChoiceOptions = multiChoiceOptions.filter(function(e) { return e !== "" })
if (multiChoiceOptions.length > 1 && (executionArgument[multiChoiceOptions[0]] === undefined || executionArgument[multiChoiceOptions[0]] === null || executionArgument[multiChoiceOptions[0]] === "")) {
// Set the first item to be default
executionArgument[multiChoiceOptions[0]] = multiChoiceOptions[1]
}
return (
<div style={{marginBottom: 10}} key={index}>
{question.name}
{multiChoiceOptions.length > 1 ?
<Select
fullWidth
required
label={multiChoiceOptions[0]}
value={executionArgument[multiChoiceOptions[0]]}
onChange={(e) => {
const curQuestion = multiChoiceOptions[0]
executionArgument[curQuestion] = e.target.value
setUpdate(Math.random())
}}
>
{multiChoiceOptions.map((option, menuIndex) => {
if (index === 0) {
return null
}
return (
<MenuItem
key={menuIndex}
value={option}
>
{option}
</MenuItem>
)
})}
</Select>
:
<TextField
color="primary"
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
label={question.value}
required
disabled={disabledButtons}
fullWidth={true}
placeholder=""
id="emailfield"
margin="normal"
variant="outlined"
onChange={(e) => {
executionArgument[question.value] = e.target.value
setExecutionArgument(executionArgument)
setUpdate(Math.random())
}}
/>
}
</div>
)
})}
</div>
: :
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ? answer !== undefined && answer !== null ? null :
<span style={{marginTop: 20, }}> <span>
Runtime Argument
{disabledButtons && message.length > 0 ? <div style={{marginBottom: 5}}>
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}> <TextField
{message}. You may close this window. color="primary"
</Typography> style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
: multiLine
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}> maxRows={2}
{disabledButtons ? "Answered. You may close this window." : ""} InputProps={{
</Typography> style:{
} height: "50px",
color: "white",
{disabledButtons ? null : fontSize: "1em",
<Typography variant="body2" color="textSecondary" style={{textAlign: "center", marginTop: 10, }}> },
What do you want to do? }}
</Typography> fullWidth={true}
} placeholder=""
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}> id="emailfield"
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{flex: 1,}} onClick={() => { margin="normal"
onSubmit(null, execution_id, authorization, true) variant="outlined"
onChange={(e) => {
setButtonClicked("FINISHED") setExecutionArgument(e.target.value)
setExecutionData({ }}
status: "FINISHED", />
})
}}>Continue</Button>
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
&nbsp;or&nbsp;
</Typography>
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
onSubmit(null, execution_id, authorization, false)
setButtonClicked("ABORTED")
setExecutionData({
status: "ABORTED",
})
}}>Stop</Button>
</div> </div>
</span> </span>
: }
<div style={{display: "flex", marginTop: "15px"}}>
<Button variant="contained" type="submit" color="primary" fullWidth disabled={!handleValidateForm(executionArgument) || executionLoading}> {executionRunning ?
{executionLoading ? <span style={{width: 50, height: 50, margin: "auto", alignItems: "center", justifyContent: "center", textAlign: "center", }}>
<CircularProgress color="secondary" style={{color: "white",}} /> : "Submit"} <CircularProgress style={{marginTop: 20, marginBottom: 20, marginLeft: 185, }}/>
</Button>
{executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ?
<Typography variant="body2" style={{margin: "auto", marginTop: 20, marginBottom: 20, textAlign: "center", alignItem: "center", }} color="textSecondary">
Status: {executionData.status}
</Typography>
: null}
</span>
:
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ?
<span style={{marginTop: 20, }}>
{disabledButtons && message.length > 0 ?
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{message}. You may close this window.
</Typography>
:
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{disabledButtons ? "Answered. You may close this window." : ""}
</Typography>
}
{disabledButtons ? null :
<Typography variant="body2" color="textSecondary" style={{textAlign: "center", marginTop: 10, }}>
What do you want to do?
</Typography>
}
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}>
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{flex: 1,}} onClick={() => {
onSubmit(null, execution_id, authorization, true)
setButtonClicked("FINISHED")
setExecutionData({
status: "FINISHED",
})
}}>Continue</Button>
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
&nbsp;or&nbsp;
</Typography>
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
onSubmit(null, execution_id, authorization, false)
setButtonClicked("ABORTED")
setExecutionData({
status: "ABORTED",
})
}}>Stop</Button>
</div>
</span>
:
<div style={{display: "flex", marginTop: "15px"}}>
<Button
variant={executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ? "outlined" : "contained"}
type="submit"
color="primary"
fullWidth
disabled={!handleValidateForm(executionArgument) || executionLoading}
>
{executionLoading ?
<CircularProgress color="secondary" style={{color: "white",}} /> : "Submit"}
</Button>
</div>
}
{/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, marginLeft: 125, borderRadius: theme.palette.borderRadius, }}
onLoad={() => {
console.log("Img loaded.")
setTimeout(() => {
console.log("Img closing.")
setButtonClicked("finished")
}, 1250)
}}
/>
: null*/}
<div style={{marginTop: "10px"}}>
{executionInfo}
</div> </div>
}
{/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? {answer !== undefined && answer !== null ? null :
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, marginLeft: 125, borderRadius: theme.palette.borderRadius, }} <ShowExecutionResults executionData={executionData} />
onLoad={() => { }
console.log("Img loaded.") </form>
setTimeout(() => { </div>
console.log("Img closing.") }
setButtonClicked("finished")
}, 1250)
}}
/>
: null*/}
<div style={{marginTop: "10px"}}>
{executionInfo}
</div>
{answer !== undefined && answer !== null ? null :
<ShowExecutionResults executionData={executionData} />
}
</form>
</Paper> </Paper>
</div> </div>
@@ -1092,6 +1037,7 @@ const RunWorkflow = (defaultprops) => {
usecases={undefined} usecases={undefined}
expanded={true} expanded={true}
scrollTo={"input_markdown"}
/> />
: null} : null}
@@ -1103,20 +1049,59 @@ const RunWorkflow = (defaultprops) => {
PaperProps={{ PaperProps={{
style: { style: {
color: "white", color: "white",
minWidth: isMobile ? "90%" : 400, minWidth: isMobile ? "90%" : 500,
maxWidth: isMobile ? "90%" : 400, maxWidth: isMobile ? "90%" : 500,
minHeight: 350, minHeight: 400,
paddingTop: 25, maxHeight: 400,
paddingLeft: 50, padding: 25,
borderRadius: theme.palette.borderRadius,
//minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
//maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
}, },
}} }}
> >
<DialogTitle style={{padding: 30, paddingBottom: 0, zIndex: 1000,}}> <DialogTitle style={{padding: 30, paddingLeft: 20, paddingBottom: 0, zIndex: 1000,}}>
Share Landingpage Form Sharing Options for '{workflow.name}'
</DialogTitle> </DialogTitle>
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, paddingBottom: 200, }}> <DialogContent style={{marginTop: 20,}}>
<Typography variant="body1">
<b>General Access</b>
</Typography>
<Typography variant="body2" color="textSecondary">
Form sharing and workflow sharing are not the same. By sharing a form, you are enabling anyone with the link to fill out the form AND run the workflow. They will NOT have access to seeing workflow details. By default, anyone with access to an organization can use a form.
</Typography>
<div />
{workflow !== undefined && workflow !== null && workflow.sharing !== undefined && workflow.sharing !== null ?
<Select
fullWidth
style={{marginTop: 25, }}
value={workflow.sharing === "" ? "private" : workflow.sharing}
onChange={(e) => {
console.log("SHARING: ", e.target.value)
workflow.sharing = e.target.value
setWorkflow(workflow)
saveWorkflow(workflow)
setUpdate(Math.random())
toast("Form sharing updated.")
}}
>
<MenuItem
value={"private"}
>
Organization only
</MenuItem>
<Divider />
<MenuItem
value={"form"}
>
Anyone with the link
</MenuItem>
</Select>
: null}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -1135,19 +1120,19 @@ const RunWorkflow = (defaultprops) => {
<Button <Button
variant={"outlined"} variant={"outlined"}
color={"secondary"} color={"secondary"}
disabled
onClick={() => { onClick={() => {
setSharingOpen(true) setSharingOpen(true)
}} }}
> >
Manage Sharing Share Form
</Button> </Button>
</div> </div>
: null} : null}
{basedata} {basedata}
</div> </div>
: :
<div> <div style={{width: 100, itemAlign: "center", textAlign: "center", margin: "auto", }}>
<CircularProgress /> <CircularProgress />
</div> </div>
+8 -14
View File
@@ -24,7 +24,7 @@ const SetAuthentication = (props) => {
const [loadFail, setLoadFail] = useState(""); const [loadFail, setLoadFail] = useState("");
const [appAuthentication, setAppAuthentication] = React.useState([]); const [appAuthentication, setAppAuthentication] = React.useState([]);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
//const alert = useAlert(); //const alert = useAlert();
const parseIncomingOpenapiData = (data) => { const parseIncomingOpenapiData = (data) => {
@@ -142,8 +142,8 @@ const SetAuthentication = (props) => {
// 3. Help them set info for the app // 3. Help them set info for the app
// Make sure to test both private and public apps // Make sure to test both private and public apps
const appname = app.name !== undefined ? app.name : ""; const appname = app.name !== undefined ? app.name : ""
const appLink = "/apps/" + app.id || ""; const appLink = "/apps/" + app.id || ""
console.log("App: ", app) console.log("App: ", app)
@@ -154,21 +154,16 @@ const SetAuthentication = (props) => {
: :
<><div> <><div>
<Typography variant="h4" style={{ marginBottom: 20, }}> <Typography variant="h4" style={{ marginBottom: 20, }}>
A Shuffle Organization has invited you to: Configure <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>{appname}</a> Authentication You are invited to: Configure <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>{appname}</a> Authentication
</Typography> </Typography>
{/* What does this mean box */}
<Typography variant="h6" style={{ marginBottom: 20, }}> <Typography variant="h6" style={{ marginBottom: 20, }}>
What does this mean? What does this mean?
</Typography> </Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}> <Typography variant="body1" style={{ marginBottom: 20, color: "rgba(255,255,255,0.4)", }}>
A Shuffle Organization has invited you to configure authentication for this app so that they can use this authentication in one of their workflows. A Shuffle Organization has invited you to configure authentication for this app so that they can use this authentication in one of their workflows.
</Typography> </Typography>
<Typography variant="h6">
Authenticate Here:
</Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}> <Typography variant="body1" style={{ marginBottom: 20, }}>
{app.authentication === undefined || app.authentication === null || app.authentication.length === 0 ? {app.authentication === undefined || app.authentication === null || app.authentication.length === 0 ?
null null
@@ -195,7 +190,7 @@ const SetAuthentication = (props) => {
appAuthentication={appAuthentication} />} appAuthentication={appAuthentication} />}
</Typography> </Typography>
<Typography variant="h6" style={{ marginBottom: 20, }}> <Typography variant="h6" style={{ marginTop: 50, marginBottom: 20, }}>
What can they do with this? What can they do with this?
</Typography> </Typography>
@@ -203,12 +198,11 @@ const SetAuthentication = (props) => {
You can check the actions they want to use <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>here</a>. You can check the actions they want to use <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>here</a>.
</Typography> </Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}> <Typography variant="body1" style={{ marginBottom: 20, color: "rgba(255,255,255,0.4)",}}>
{/* Add a box below */}
<div className="collapsible-container"> <div className="collapsible-container">
<div className="collapsible-list"> <div className="collapsible-list">
{app.actions?.map((item, index) => ( {app.actions?.map((item, index) => (
<div key={index} className="collapsible-item"> <div key={index} className="collapsible-item" style={{cursor: "pointer", }}>
<div className="collapsible-label" onClick={() => handleToggle(index)}> <div className="collapsible-label" onClick={() => handleToggle(index)}>
{item.label} {item.label}
</div> </div>
+1 -1
View File
@@ -47,7 +47,7 @@ const Welcome = (props) => {
} }
}, [activeStep]) }, [activeStep])
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
const [steps, setSteps] = useState([ const [steps, setSteps] = useState([
"Help us get to know you", "Help us get to know you",
"Find your Apps", "Find your Apps",
+1 -1
View File
@@ -3528,7 +3528,7 @@ const Workflows = (props) => {
var workflowDelay = -150 var workflowDelay = -150
var appDelay = -75 var appDelay = -75
const foundPriority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) const foundPriority = userdata === undefined || userdata === null || userdata.priorities === undefined || userdata.priorities === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
return ( return (
<div style={viewStyle}> <div style={viewStyle}>
<div style={workflowViewStyle}> <div style={workflowViewStyle}>