A bunch of commits moved from cloud -> onprem to sync up current work on 2.0
This commit is contained in:
+107
-106
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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> - </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> - </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, }}>
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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.
|
Notifications help you find potential problems with your workflows and apps.
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
//{
|
//{
|
||||||
|
|||||||
@@ -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
@@ -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={{
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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("");
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
@@ -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 </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, }}>
|
|
||||||
or
|
|
||||||
</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, }}>
|
||||||
|
or
|
||||||
|
</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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
Reference in New Issue
Block a user