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

This commit is contained in:
Frikky
2024-10-08 11:08:28 +02:00
parent f2a0aac31d
commit 8154fb8ad4
34 changed files with 2081 additions and 1284 deletions
+1 -1
View File
@@ -1129,7 +1129,7 @@ const AppFramework = (props) => {
}, [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;
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
+1 -1
View File
@@ -63,7 +63,7 @@ const AppSelection = props => {
document.title = "Choose your apps"
const ref = useRef()
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(() => {
if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
+1 -1
View File
@@ -24,7 +24,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
const Appsearch = props => {
const { 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 xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
//const theme = useTheme();
+60 -29
View File
@@ -42,6 +42,7 @@ import {
Delete,
RestaurantRounded,
Cloud,
CheckCircle
} from "@mui/icons-material";
//import { useAlert
@@ -71,6 +72,7 @@ const Billing = (props) => {
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 [currentIndex, setCurrentIndex] = useState(0);
const [deleteAlertVerification, setDeleteAlertVerification] = useState(false);
useEffect(() => {
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) {
top_text = "Current Plan"
newPaperstyle.border = "1px solid #f85a3e"
// newPaperstyle.border = "1px solid #f85a3e"
}
var showSupport = false
if (subscription.name.includes("default")) {
top_text = "Custom Contract"
newPaperstyle.border = "1px solid #f85a3e"
newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
showSupport = true
}
@@ -379,7 +381,7 @@ const Billing = (props) => {
if (highlight === true) {
// Add an "Upgrade now" button
newPaperstyle.border = "1px solid #f85a3e"
newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
}
if (hovered) {
@@ -820,7 +822,8 @@ const Billing = (props) => {
height: 40,
fontSize: 16,
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",
}}
@@ -852,7 +855,7 @@ const Billing = (props) => {
height: 40,
fontSize: 16,
color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
backgroundColor: "#f86743",
textTransform: 'none'
}}
onClick={() => {
@@ -1036,11 +1039,11 @@ const Billing = (props) => {
<Paper style={{
padding: 20,
// maxWidth: 400,
minWidth: 340,
width: 340,
height: 480,
backgroundColor: hovered ? "#232427" : theme.palette.platformColor,
borderRadius: theme.palette.borderRadius * 2,
border: "1px solid #f85a3e",
border: "1px solid rgba(255,255,255,0.3)",
marginRight: 10,
marginTop: 15,
}}
@@ -1133,8 +1136,8 @@ const Billing = (props) => {
height: 40,
fontSize: 16,
color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
textTransform: 'none'
backgroundColor: "#f86743",
textTransform: 'none',
}}
onClick={() => {
if (Cloud) {
@@ -1188,7 +1191,7 @@ const Billing = (props) => {
height: 40,
fontSize: 16,
color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
backgroundColor: "#f86743",
textTransform: 'none',
cursor: getProfessionalServices ? 'pointer' : 'not-allowed',
opacity: getProfessionalServices ? 1 : 0.6,
@@ -1315,7 +1318,7 @@ const Billing = (props) => {
width: 340,
backgroundColor: hovered ? "#232427" : theme.palette.platformColor,
borderRadius: theme.palette.borderRadius * 2,
border: "1px solid #f85a3e",
border: "1px solid rgba(255,255,255,0.3)",
marginRight: 10,
marginTop: 15,
}}
@@ -1373,7 +1376,7 @@ const Billing = (props) => {
height: 40,
fontSize: 16,
color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
backgroundColor: "#f86743",
textTransform: 'none'
}}
onClick={() => {
@@ -1381,6 +1384,8 @@ const Billing = (props) => {
ReactGA.event({
category: "Billing",
action: "click_public_training_button",
label: "Public Training",
userId: userdata?.id
});
}
navigate("/training")
@@ -1399,21 +1404,24 @@ const Billing = (props) => {
height: 40,
fontSize: 16,
color: "white",
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
backgroundColor: "#f86743",
textTransform: 'none'
}}
onClick={() => {
if (Cloud) {
ReactGA.event({
category: "Billing",
action: "click_public_training_button",
action: "click_private_training_button",
label: "Private Training",
userId: userdata?.id,
});
}
setOpenPrivateTraining(true)
setOpenPrivateTraining(true);
}}
>
Private Training
</Button>
<Dialog open={openPrivateTraining}
onClose={() => setOpenPrivateTraining(false)}
fullWidth
@@ -1818,6 +1826,7 @@ const Billing = (props) => {
// Update currentIndex based on remaining elements
const findCurrentIndex = newAlertThresholds.some(threshold => threshold.Email_send === false);
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) => {
@@ -1997,15 +2006,6 @@ const Billing = (props) => {
/>
</span>
: null}
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null :
<ConsultationManagement
globalUrl={globalUrl}
userdata={userdata}
selectedOrganization={selectedOrganization}
/> : null}
<TrainingService />
{isCloud &&
selectedOrganization.subscriptions !== undefined &&
@@ -2261,7 +2261,29 @@ const Billing = (props) => {
</div>
) : 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
style={{ marginBottom: 5 }}
variant="h4"
@@ -2303,7 +2325,9 @@ const Billing = (props) => {
: " " + 0 + " "}
app runs.
</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 }}>
{alertThresholds.map((threshold, index) => (
<div key={index} style={{ display: 'flex', alignItems: 'center' }}>
@@ -2354,6 +2378,7 @@ const Billing = (props) => {
margin="normal"
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 &&
(
@@ -2362,18 +2387,24 @@ const Billing = (props) => {
disableElevation
sx={{
padding: 0,
color: 'red',
'&:hover': {
backgroundColor: 'transparent',
},
}}
onClick={() => { handleDeleteAlertThreshold(index) }}
onClick={() => { setDeleteAlertVerification(true) }}
>
<DeleteIcon />
<DeleteIcon sx={{ color: theme.palette.secondary.main }} />
</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>
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import theme from "../theme.jsx";
import { toast } from 'react-toastify';
import ReactJson from "react-json-view";
import ReactJson from "react-json-view-ssr";
import {
Typography,
+21 -5
View File
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks";
import { toast } from 'react-toastify';
import theme from "../theme.jsx";
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
import {
InputAdornment,
@@ -79,7 +80,7 @@ const ConfigureWorkflow = (props) => {
useEffect(() => {
if (requiredActions.length === 0) {
if (setConfigurationFinished !== undefined) {
setConfigurationFinished(true)
setConfigurationFinished(true)
}
}
}, [requiredActions])
@@ -141,17 +142,18 @@ const ConfigureWorkflow = (props) => {
// Where is this from?
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) {
console.log("Apps is undefined or null: ", apps)
return null;
return null
}
if (appAuthentication === undefined || appAuthentication === null) {
console.log("App authentication is undefined or null: ", appAuthentication)
return null;
return null
}
const getApp = (actionId, appId) => {
@@ -1386,9 +1388,23 @@ const ConfigureWorkflow = (props) => {
: null
}
<div style={{marginTop: 10, }} />
{/*
<WorkflowValidationTimeline
workflow={workflow}
apps={apps}
getParents={undefined}
execution={undefined}
/>
<div style={{marginBottom: 10, }} />
*/}
{requiredActions.length > 0 ? (
<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.
</Typography>
+141 -16
View File
@@ -1,22 +1,57 @@
import React from "react";
import React, { useState, useEffect, } from "react";
import {
Card,
CardContent,
IconButton,
Typography,
Switch,
Tooltip,
Select,
MenuItem,
Divider,
FormLabel,
} 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 ShuffleCodeEditor from "../components/ShuffleCodeEditor1.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 [fileData, setFileData] = React.useState("");
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);
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) => {
if (folderDisabled) {
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, () => {
setIsEnabled(newIsEnabled);
})
};
}
const UpdateText = (text) => {
fetch(`${globalUrl}/api/v1/files/${file_id}/edit`, {
@@ -64,32 +100,121 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
<Card style={{
borderRadius: theme.palette.borderRadius,
minHeight: 100,
marginBottom: 10,
paddingBottom: 0,
}}>
<CardContent>
<CardContent
style={{
padding: "10px 30px 0px 30px",
}}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
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' }}>
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
<EditIcon />
</IconButton>
<Switch
checked={isEnabled && !folderDisabled}
onChange={handleSwitchChange}
disabled={false}
/>
<Select
MenuProps={{
disableScrollLock: true,
}}
labelId="Response Action"
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 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%' }}>
{description}
</Typography>
*/}
<ShuffleCodeEditor
isCloud={isCloud}
+221 -203
View File
@@ -2,6 +2,7 @@ import React, { useEffect, useContext } from "react";
import theme from '../theme.jsx';
import { isMobile } from "react-device-detect"
import { MuiChipsInput } from "mui-chips-input";
import { toast } from "react-toastify"
import UsecaseSearch from "../components/UsecaseSearch.jsx"
import WorkflowGrid from "../components/WorkflowGrid.jsx"
import dayjs from 'dayjs';
@@ -63,7 +64,7 @@ import {
} from "@mui/icons-material";
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
@@ -79,15 +80,30 @@ const EditWorkflow = (props) => {
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 [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 [outputMarkdown, setOutputMarkdown] = React.useState(workflow.output_markdown !== undefined && workflow.output_markdown !== null ? workflow.output_markdown : "")
const [scrollDone, setScrollDone] = React.useState(false)
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
const getGeneratedWorkflow = (workflow_id) => {
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
const getGeneratedWorkflow = (workflow_id) => {
const url = `${globalUrl}/api/v1/workflows/${workflow_id}`
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -95,54 +111,55 @@ const EditWorkflow = (props) => {
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 when getting workflow");
.then((response) => {
if (response.status !== 200) {
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();
})
.then((responseJson) => {
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())
if (description === "") {
innerWorkflow.description = responseJson.description
setDescription(description)
}
})
.catch((error) => {
//toast(error.toString());
console.log("Get workflow error: ", error.toString());
})
}
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) => {
//toast(error.toString());
console.log("Get workflow error: ", error.toString());
})
}
if (foundWorkflowId.length > 0) {
getGeneratedWorkflow(foundWorkflowId)
@@ -162,6 +179,7 @@ const EditWorkflow = (props) => {
return (
<Drawer
anchor={"right"}
open={modalOpen}
onClose={() => {
setModalOpen(false);
@@ -191,7 +209,7 @@ const EditWorkflow = (props) => {
<Tooltip title="Open Workflow Form for 'normal' users">
<a
rel="noopener noreferrer"
href={`/workflows/${workflow.id}/run`}
href={`/forms/${workflow.id}`}
target="_blank"
style={{
textDecoration: "none",
@@ -207,17 +225,18 @@ const EditWorkflow = (props) => {
}
</div>
<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>
{/*
<div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}>
<WorkflowValidationTimeline
originalWorkflow={workflow}
apps={apps}
workflow={workflow}
/>
</div>
*/}
{showUpload === true ?
<div style={{ float: "right" }}>
@@ -247,7 +266,7 @@ const EditWorkflow = (props) => {
</div>
</DialogTitle>
<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
style={{}}
@@ -602,7 +621,7 @@ const EditWorkflow = (props) => {
userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ?
userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ?
<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 variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
@@ -702,152 +721,7 @@ const EditWorkflow = (props) => {
<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, }}>
Git Backup Repository
@@ -998,8 +872,152 @@ const EditWorkflow = (props) => {
</span>
</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">
@@ -1047,7 +1065,7 @@ const EditWorkflow = (props) => {
</span>
: null}
{newWorkflow === true && name.length > 2 ?
{/*newWorkflow === true && name.length > 2 ?
<div style={{marginLeft: 30, }}>
<WorkflowGrid
maxRows={1}
@@ -1062,7 +1080,7 @@ const EditWorkflow = (props) => {
onlyResults={true}
/>
</div>
: null}
: null*/}
</FormControl>
</Drawer>
)
+29 -25
View File
@@ -132,6 +132,8 @@ const Header = (props) => {
isMobile,
serverside,
billingInfo,
notifications,
} = props;
const [isHeader, setIsHeader] = React.useState(false);
const [modalOpen, setModalOpen] = useState(false);
@@ -311,18 +313,19 @@ const Header = (props) => {
localStorage.setItem("globalUrl", responseJson.region_url);
//globalUrl = responseJson.region_url
}
if (responseJson["reason"] === "SSO_REDIRECT") {
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
setTimeout(() => {
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
window.location.href = responseJson["url"]
return
}, 2000)
} else {
toast("Successfully changed active organization - refreshing!");
setTimeout(() => {
window.location.reload()
}, 2000);
}
toast("Successfully changed active organization - refreshing!");
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
toast(responseJson.reason);
@@ -416,26 +419,19 @@ const Header = (props) => {
</MenuItem>
</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, }} />
<Link to="/docs" style={hrefStyle}>
<MenuItem
onClick={(event) => {
handleClose();
}}
>
<HelpOutlineIcon style={{ marginRight: 5 }} /> About
</MenuItem>
</Link>
<Link to="/admin?admin_tab=priorities" style={hrefStyle}>
<MenuItem
onClick={(event) => {
handleClose();
}}
>
<NotificationsIcon style={{ marginRight: 5 }} /> Notifications ({
notifications === undefined || notifications === null ? 0 :
notifications?.filter((notification) => notification.read === false).length
})
</MenuItem>
</Link>
{/*
<Link to="/getting-started" style={hrefStyle}>
<MenuItem
@@ -457,7 +453,7 @@ const Header = (props) => {
</MenuItem>
</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}>
<MenuItem
onClick={(event) => {
@@ -467,9 +463,18 @@ const Header = (props) => {
<EmojiObjectsIcon style={{ marginRight: 5 }} /> Creator page
</MenuItem>
</Link>
}
*/}
<Divider style={{ marginTop: 10, marginBottom: 10, }} />
<Link to="/docs" style={hrefStyle}>
<MenuItem
onClick={(event) => {
handleClose();
}}
>
<HelpOutlineIcon style={{ marginRight: 5 }} /> About
</MenuItem>
</Link>
<MenuItem
style={{ color: "white" }}
onClick={(event) => {
@@ -483,7 +488,7 @@ const Header = (props) => {
<Divider style={{ marginBottom: 10, }} />
<Typography variant="body2" color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, }}>
Version: 1.4.0
Version: 1.4.5
</Typography>
</Menu>
</span>
@@ -910,7 +915,6 @@ const Header = (props) => {
}}
>
{avatarMenu}
{/*notificationMenu*/}
{supportMenu}
{logoCheck}
</span>
+4 -4
View File
@@ -167,7 +167,7 @@ const AuthenticationOauth2 = (props) => {
//console.log("APP: ", selectedApp)
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
handleOauth2Request(
"efe4c3fe-84a1-4821-a84f-23a6cfe8e72d",
"fd55c175-aa30-4fa6-b303-09a29fb3f750",
"",
"https://graph.microsoft.com",
["Mail.ReadWrite", "Mail.Send", "offline_access"],
@@ -524,7 +524,7 @@ const AuthenticationOauth2 = (props) => {
//alert('"Secure Payment" window closed!');
if (getAppAuthentication !== undefined) {
getAppAuthentication(true, true, true);
getAppAuthentication(true, true, true, selectedAction.id)
}
toast("Authentication successful!")
@@ -538,7 +538,7 @@ const AuthenticationOauth2 = (props) => {
setFinalized(true)
}
} else {
console.log("Not closed")
//console.log("Not closed")
}
}, 1000);
//do {
@@ -739,7 +739,7 @@ const AuthenticationOauth2 = (props) => {
</DialogTitle>
<DialogContent>
<span style={{}}>
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. {authenticationType.type === "oauth2-app" ? null : <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b>&nbsp;-&nbsp;</span>}
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b>&nbsp;-&nbsp;</span>
<a
target="_blank"
rel="norefferer"
@@ -323,6 +323,44 @@ const OrgHeaderexpanded = (props) => {
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 (
<div style={{ textAlign: "center" }}>
<Grid container spacing={3} style={{ textAlign: "left" }}>
@@ -677,6 +715,42 @@ const OrgHeaderexpanded = (props) => {
{SSORequired ? 'Required' : 'Optional'}
</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={{}}>
<Typography variant="h6" style={{ textAlign: "center", }}>OpenID connect</Typography>
<Grid container style={{ marginTop: 10, }}>
+114 -237
View File
@@ -90,9 +90,10 @@ import {
Circle as CircleIcon,
SquareFoot as SquareFootIcon,
Storage as StorageIcon,
Check as CheckIcon,
} from '@mui/icons-material';
const useStyles = makeStyles({
export const useStyles = makeStyles({
notchedOutline: {
borderColor: "#f85a3e !important",
},
@@ -1500,92 +1501,7 @@ const ParsedAction = (props) => {
<DescriptionIcon style={{ color: "rgba(255,255,255,0.7)" }} />
</Tooltip>
</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
style={{
marginTop: "auto",
@@ -1602,6 +1518,10 @@ const ParsedAction = (props) => {
aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
//}
setAutocompleting(true)
setTimeout(() => {
setAutocompleting(false)
}, 3000)
}}
>
<Tooltip
@@ -1998,7 +1918,7 @@ const ParsedAction = (props) => {
for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
if (selectedAction.parameters[key].configuration === false) {
console.log("FIELDSKIP: ", selectedAction.parameters[key].name)
//console.log("FIELDSKIP: ", selectedAction.parameters[key].name)
continue
}
@@ -2085,7 +2005,18 @@ const ParsedAction = (props) => {
}}
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
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
label={"Latest"}
@@ -2093,14 +2024,14 @@ const ParsedAction = (props) => {
color="secondary"
/>
: 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
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
label={data.app.app_version}
variant="outlined"
color="secondary"
/>
: null}
: null*/}
{data.label}
</MenuItem>
);
@@ -2342,6 +2273,7 @@ const ParsedAction = (props) => {
value={selectedAction}
classes={{ inputRoot: classes.inputRoot }}
groupBy={(option) => {
// FIXME: Sorting
// Most popular
// Is categorized
// Uncategorized
@@ -2364,7 +2296,6 @@ const ParsedAction = (props) => {
},
}}
filterOptions={(options, { inputValue }) => {
//console.log("Option contains?: ", inputValue, options)
const lowercaseValue = inputValue.toLowerCase()
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]
}
//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.includes(" ")) {
extraUrl = extraUrl.split(" ")[0]
@@ -2492,6 +2407,7 @@ const ParsedAction = (props) => {
);
}}
renderInput={(params) => {
if (params.inputProps?.value) {
const prefixes = ["Post", "Put", "Patch"];
for (let prefix of prefixes) {
@@ -2512,84 +2428,86 @@ const ParsedAction = (props) => {
}
const actionDescription = (
<Box
p={1.5}
borderRadius={3}
boxShadow={2}
backgroundColor={theme.palette.textFieldStyle}
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',
},
},
}}
const actionDescription = null
/*(
<Box
p={1.5}
borderRadius={3}
boxShadow={2}
backgroundColor={theme.palette.textFieldStyle}
display="flex"
flexDirection="column"
>
<TextField
{...params}
<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>
)
*/
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,
return (
<Tooltip title={actionDescription}
placement="right"
open={!hiddenDescription}
PopperProps={{
sx: {
'& .MuiTooltip-tooltip': {
backgroundColor: 'transparent',
boxShadow: 'none',
},
'& .MuiTooltip-arrow': {
color: 'transparent',
},
},
}}
label={isIntegration ? "Choose a category" : "Find Actions"}
variant="outlined"
name={`disable_autocomplete_${Math.random()}`}
/>
</Tooltip>
);
}}
/>
>
<TextField
{...params}
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}
{/*setNewSelectedAction !== undefined ?
@@ -2939,58 +2857,24 @@ const ParsedAction = (props) => {
}
}
/*
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>
)
}
*/
if (selectedAction.parameters === undefined || selectedAction.parameters === null || selectedAction.parameters.length !== selectedActionParameters.length) {
//selectedAction.parameters = selectedActionParameters
console.log("PARAM BUG: ", selectedAction)
}
//!selectedAction.auth_not_required &&
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];
if (selectedActionParameters[count] !== undefined) {
selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
}
if (selectedAction.parameters[count] !== undefined) {
selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
}
setSelectedAction(selectedAction);
//setUpdate(Math.random())
@@ -3064,7 +2948,7 @@ const ParsedAction = (props) => {
if (data.value.length === 0) {
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
//
@@ -3360,13 +3244,6 @@ const ParsedAction = (props) => {
<IconButton size="small"
onClick={() => {
setUiBox("closed")
/*
const inputElement = document.getElementById(uiBox);
if (inputElement) {
inputElement.focus();
}
*/
}}
>
<CloseIcon fontSize="small" />
+4 -1
View File
@@ -393,7 +393,10 @@ const Priorities = (props) => {
return (
<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, }}>
Notifications help you find potential problems with your workflows and apps.&nbsp;
<a
+1 -1
View File
@@ -24,7 +24,7 @@ import {
const Priority = (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();
if (window.location.pathname === "/workflows") {
+1 -1
View File
@@ -71,7 +71,7 @@ const SearchData = props => {
// 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" ) {
// setModalOpen(false)
// }
@@ -44,7 +44,7 @@ import {
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 { padding, textAlign } from '@mui/system';
+1 -1
View File
@@ -349,7 +349,7 @@ const UsecaseSearch = (props) => {
const [selectedAction, setSelectedAction] = React.useState({});
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()
useEffect(() => {
+1 -1
View File
@@ -161,7 +161,7 @@ const WelcomeForm = (props) => {
const [clickdiff, setclickdiff] = useState(0);
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();
let navigate = useNavigate();
+1 -1
View File
@@ -28,7 +28,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
const AppGrid = 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 xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs
//const [apps, setApps] = React.useState([]);
@@ -47,7 +47,7 @@ const WorkflowTemplatePopup = (props) => {
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();
useEffect(() => {
if (modalOpen !== true) {
@@ -23,6 +23,7 @@ import {
grey,
} from "../views/AngularWorkflow.jsx"
import WorkflowTemplatePopup2 from "../components/WorkflowTemplatePopup2.jsx"
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import theme from "../theme.jsx";
const itemHeight = 24
@@ -63,7 +64,7 @@ export const getParentNodes = (workflow, action) => {
currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey])
if (currentnode === undefined) {
console.log("Could not find parent node for: ", allkeys[parentkey])
//console.log("Could not find parent node for: ", allkeys[parentkey])
continue
}
}
@@ -128,11 +129,13 @@ export const getParentNodes = (workflow, action) => {
}
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
if (workflow === undefined || workflow === null) {
return null
}
@@ -146,13 +149,11 @@ const WorkflowValidationTimeline = (props) => {
}
if (workflow.triggers === undefined || workflow.triggers === null) {
workflow.triggers = []
workflow.triggers = []
}
if (workflow.branches === undefined || workflow.branches === null) {
workflow.branches = []
workflow.branches = []
}
var results = []
@@ -260,6 +261,12 @@ const WorkflowValidationTimeline = (props) => {
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~
relevantactions.sort((a, b) => {
if (a.order === undefined) {
@@ -279,15 +286,81 @@ const WorkflowValidationTimeline = (props) => {
var skipped = 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
//const showMiddle = false
// border: workflow.validation.valid ? `2px solid ${green}` : "1px solid rgba(255,255,255,0.4)",
var middleError = ""
var startBranchColor = ""
var middleBranchColor = ""
const showHoverForClick = showHoverColor === true ? true : false
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"}}>
{scheduleNotStarted === true ?
null
: null}
{relevantactions.map((action, index) => {
action.result = {}
if (results !== undefined) {
@@ -309,8 +382,10 @@ const WorkflowValidationTimeline = (props) => {
const validate = validateJson(action.result.result)
if (validate.valid) {
if (validate.result.success === true) {
nodecolor = green
branchcolor = green
} else {
nodecolor = grey
branchcolor = grey
}
}
@@ -319,9 +394,12 @@ const WorkflowValidationTimeline = (props) => {
} else if (action.status === "SKIPPED") {
branchcolor = grey
} else {
// FIXME: How do we handle this?
if (action.status === undefined) {
branchcolor = green
nodecolor = grey
branchcolor = grey
} else {
nodecolor = 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 (founderror.length > 0) {
middleError += founderror+"\n"
middleBranchColor = branchcolor
}
if (index === relevantactions.length-2 && relevantactions.length > 2) {
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, }}>
<ErrorOutlineIcon style={{color: "red", }} />
</IconButton>
</Tooltip>
: null
return (
selectedIcon
)
return selectedIcon
} else {
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) {
nodecolor = grey
branchcolor = grey
@@ -423,28 +536,14 @@ const WorkflowValidationTimeline = (props) => {
branchcolor = nodecolor
}
var appgroup = []
if (action.trigger_type === "WEBHOOK") {
nodecolor = 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
if (nodecolor === green) {
branchcolor = green
} else if (nodecolor === yellow) {
@@ -455,12 +554,29 @@ const WorkflowValidationTimeline = (props) => {
if (index === 0) {
startBranchColor = branchcolor
} else if (index !== 0 && index !== relevantactions.length - 1) {
// FIXME: This doesn't work yet
middleBranchColor = branchcolor
}
if (lastitem && middleError.length === 0) {
branchcolor = startBranchColor
if (lastitem) {
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 appname = action.app_name.replaceAll('_', ' ').slice(0, 16)
@@ -488,6 +604,14 @@ const WorkflowValidationTimeline = (props) => {
console.log("MISSING IMAGE: ", appname, image, action)
}
if (decidedColor === grey && nodecolor === green) {
setDecidedColor(red)
}
if (decidedColor !== red && nodecolor === red) {
setDecidedColor(red)
}
return (
<div style={{display: "flex", flex: flex, justifyContent: "right", }}>
{lastitem ?
@@ -528,7 +652,7 @@ const WorkflowValidationTimeline = (props) => {
:
<Tooltip title={
<Typography variant="body1" style={{margin: 5, color: "white", }}>
{founderror.length > 0 ? founderror : `App: ${appname}`}
{founderror.length > 0 ? founderror : `App: ${appname} - Action: ${action.label}`}
</Typography>
} placement="top">
+60 -7
View File
@@ -467,13 +467,66 @@ const data = [
"font-size": "0px",
},
},
{
selector: "node:selected",
css: {
"border-color": "#f86a3e",
"border-width": "7px",
},
},
{
selector: "node:selected",
css: {
"border-color": "#f86a3e",
"border-width": "7px",
},
},
{
selector: `node[buttonType="condition-drag"]`,
css: {
"width": "5px",
"height": "5px",
"background-color": "#f85a3e",
},
},
{
selector: `node[name="switch"]`,
css: {
label: function(element) {
// Load from the actual element
var nodeheight = 400
var conditions = [{
"name": "Condition 1",
"check": "X equals Y",
},
{
"name": "Condition 2",
"check": "X2 equals Y2",
},
{
"name": "Condition 3",
"check": "X3 equals Y3",
}]
conditions.push({
"name": "Else",
"check": "If all else fails",
})
const newlines = nodeheight / conditions.length
console.log("Newlines: ", newlines)
const label = conditions.map((condition) => {
return `${condition.name}\n\n\n`
}).join("\n")
return label
},
color: "white",
"border-color": "#f85a3e",
"background-color": "#1f1f1f",
"font-size": "19px",
"text-margin-x": "-110px",
"text-wrap": "wrap",
shape: "roundrectangle",
width: "100",
height: "300",
},
},
];
//{
+3 -3
View File
@@ -6274,7 +6274,7 @@ If you're interested, please let me know a time that works for you, or set up a
style={{ minWidth: 50, maxWidth: 50 }}
/>
<ListItemText
primary="License"
primary="Scale"
style={{ minWidth: 85, maxWidth: 85 }}
/>
<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
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"
>
<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 ? (
<div>Not running</div>
) : (
environment.running_ip.split(":")[0]
environment.running_ip
)
) : (
"N/A"
File diff suppressed because it is too large Load Diff
+31 -9
View File
@@ -544,7 +544,7 @@ const AppCreator = (defaultprops) => {
};
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
useEffect(() => {
if (window.location.pathname.includes("apps/edit")) {
@@ -897,8 +897,31 @@ const AppCreator = (defaultprops) => {
};
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
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) {
@@ -3124,15 +3147,14 @@ const AppCreator = (defaultprops) => {
Scopes for Oauth2
</Typography>
<MuiChipsInput
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}}
required
required
InputProps={{
style: {
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"
color="primary"
fullWidth
@@ -3159,7 +3181,7 @@ const AppCreator = (defaultprops) => {
required
style={{ marginTop: 0, backgroundColor: inputColor }}
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"
id="standard-required"
margin="normal"
@@ -4441,7 +4463,7 @@ const AppCreator = (defaultprops) => {
setUpdate(Math.random())
}
}}
value={data.action_label}
value={data?.action_label?.replace(" ", "_").toLowerCase()}
style={{
border: data.action_label === undefined || data.action_label === "No Label" ? "" : `2px solid ${bgColor}`,
borderRadius: theme.shape.borderRadius,
@@ -4463,7 +4485,7 @@ const AppCreator = (defaultprops) => {
return (
<MenuItem
key={labelindex}
value={label}
value={label.replace(" ", "_").toLowerCase()}
style={{
}}
>
+1 -1
View File
@@ -3035,7 +3035,7 @@ const Apps = (props) => {
height: "50px",
}}
variant="contained"
disabled={openApi.length === 0 || appValidation.length > 0}
disabled={openApi.length === 0 || appValidation.length > 0 || validation}
color="primary"
onClick={() => {
setOpenApiError("");
+151 -42
View File
@@ -11,6 +11,7 @@ import { useNavigate, Link, useParams } from "react-router-dom";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import Draggable from "react-draggable";
import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
import {
Autocomplete,
@@ -18,6 +19,8 @@ import {
TextField,
IconButton,
Button,
Select,
MenuItem,
Typography,
Grid,
Paper,
@@ -146,28 +149,8 @@ const inputdata = [
const LineChartWrapper = ({keys, height, width}) => {
const [hovered, setHovered] = useState("");
//console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
console.log("Keys: ", keys)
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 (
<div style={{}}>
<Typography variant="h6" style={{marginBotton: 15}}>
@@ -211,7 +194,6 @@ const LineChartWrapper = ({keys, height, width}) => {
offset: '5px, 5px'
}}
content={(data, color) => {
console.log("DATA: ", data)
const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No"
return (
@@ -350,12 +332,35 @@ const Dashboard = (props) => {
const [frameworkData, setFrameworkData] = useState(undefined);
const [widgetData, setWidgetData] = useState([]);
const [newWidgetData, setNewWidgetData] = useState([]);
const [, setUpdate] = useState(0);
let navigate = useNavigate();
const isCloud =
window.location.host === "localhost:3002" ||
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(() => {
if (selectedUsecaseCategory.length === 0) {
@@ -368,6 +373,7 @@ const Dashboard = (props) => {
}
}, [selectedUsecaseCategory])
const checkSelectedParams = () => {
const urlSearchParams = new URLSearchParams(window.location.search)
const params = Object.fromEntries(urlSearchParams.entries())
@@ -409,23 +415,22 @@ const Dashboard = (props) => {
}, [usecases])
const getWidget = (dashboard, widget) => {
fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
console.log("Resp: ", responseJson)
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
//toast("Failed loading: " + responseJson.reason)
@@ -441,14 +446,12 @@ const Dashboard = (props) => {
}
const foundWidget = widgetData.findIndex(data => data.title === widget)
console.log("Found: ", foundWidget)
if (foundWidget !== undefined && foundWidget !== null && foundWidget >= 0) {
widgetData[foundWidget] = tmpdata
} else {
widgetData.push(tmpdata)
}
console.log("Data: ", widgetData)
setWidgetData(widgetData)
}
})
@@ -480,7 +483,7 @@ const Dashboard = (props) => {
useEffect(() => {
getWidget("main", "Overall")
getWidget("main", "Overall2")
}, []);
}, [])
const fetchdata = (stats_id) => {
fetch(globalUrl + "/api/v1/stats/" + stats_id, {
@@ -646,7 +649,6 @@ const Dashboard = (props) => {
stats["workflow_executions"].data !== undefined
) {
setStatsRan(true);
//console.log("NEW DATA?: ", stats)
console.log("SET WORKFLOW: ", stats["workflow_executions"]);
//var curday = startDate.getDate()
@@ -729,6 +731,100 @@ const Dashboard = (props) => {
</div>
) : 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 = (
<div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
<div style={{width: 500, margin: "auto"}}>
@@ -739,12 +835,25 @@ const Dashboard = (props) => {
: null}
</div>
{widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
{/*widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
<Draggable>
<Paper style={{height: 350, width: 500, padding: "15px 15px 15px 15px", }}>
<LineChartWrapper keys={widgetData[0]} height={280} width={470} />
</Paper>
</Draggable>
*/}
{newWidgetData === undefined || newWidgetData === null || newWidgetData === [] ? null :
newWidgetData.map((data, index) => {
return (
<WidgetController
key={index}
index={index}
data={data}
/>
)
})
}
</div>
);
+12 -2
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
import { toast } from 'react-toastify';
import Markdown from 'react-markdown'
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 { BrowserView, MobileView } from "react-device-detect";
import { useParams, useNavigate, Link } from "react-router-dom";
@@ -152,9 +152,19 @@ export const OuterLink = (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(
<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}
src={props.src}
/>
+367 -382
View File
@@ -7,15 +7,17 @@ import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx";
import { useNavigate, Link, useParams } from "react-router-dom";
import { validateJson, GetIconInfo } from "./Workflows.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 { useInterval } from "react-powerhooks";
import { isMobile } from "react-device-detect";
import Markdown from "react-markdown";
import theme from '../theme.jsx';
import rehypeRaw from "rehype-raw";
import {
Tooltip,
Tooltip,
Select,
IconButton,
CircularProgress,
TextField,
@@ -28,6 +30,7 @@ import {
Dialog,
DialogTitle,
DialogContent,
MenuItem,
} from '@mui/material';
import {
@@ -45,7 +48,7 @@ const bodyDivStyle = {
width: isMobile? "100%":"500px",
position: "relative",
marginTop: 25,
paddingBottom: 250,
}
@@ -71,7 +74,7 @@ const RunWorkflow = (defaultprops) => {
const boxStyle = {
color: "white",
padding: 50,
padding: "25px 50px 50px 50px",
backgroundColor: theme.palette.surfaceColor,
marginBottom: 150,
borderRadius: 25,
@@ -102,6 +105,17 @@ const RunWorkflow = (defaultprops) => {
const [executionInfo, setExecutionInfo] = useState("");
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
}
@@ -124,7 +138,10 @@ const RunWorkflow = (defaultprops) => {
return response.json();
})
.then((responseJson) => {
toast.success("Saved workflow")
if (responseJson.success === false) {
toast.error("Failed saving workflow. Please try again.")
}
//toast.success("Saved workflow")
})
.catch((error) => {
toast.error("Save workflow error: " + error)
@@ -197,178 +214,25 @@ const RunWorkflow = (defaultprops) => {
*/}
{executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ?
<Typography variant="h6">
{executionData.result}
</Typography>
<div style={{marginTop: 20, }}>
<Divider />
<Markdown
components={{
img: Img,
code: CodeHandler,
a: OuterLink,
}}
id="markdown_wrapper"
escapeHtml={false}
style={{
maxWidth: "100%", minWidth: "100%",
}}
>
{executionData.result}
</Markdown>
</div>
: null}
{/*executionData.results.map((data, index) => {
if (executionData.results.length !== 1 && (data.status === "SKIPPED")) {
return null;
}
// FIXME: The latter replace doens't really work if ' is used in a string
var showResult = data.result.trim();
const validate = validateJson(showResult);
const curapp = apps.find((a) => a.name === data.action.app_name && a.app_version === data.action.app_version);
const imgsize = 50;
const statusColor = data.status === "FINISHED" || data.status === "SUCCESS" ? green : data.status === "ABORTED" || data.status === "FAILURE" ? "red" : yellow;
var imgSrc = curapp === undefined ? "" : curapp.large_image;
if (
imgSrc.length === 0 &&
workflow.actions !== undefined &&
workflow.actions !== null
) {
// Look for the node in the workflow
const action = workflow.actions.find(
(action) => action.id === data.action.id
);
if (action !== undefined && action !== null) {
imgSrc = action.large_image;
}
}
var actionimg =
curapp === null ? null : (
<img
alt={data.action.app_name}
src={imgSrc}
style={{
marginRight: 20,
width: imgsize,
height: imgsize,
border: `2px solid ${statusColor}`,
borderRadius:
executionData.start === data.action.id ? 25 : 5,
}}
/>
);
if (data.action.app_name === "shuffle-subflow") {
//const parsedImage = triggers[2].large_image;
//actionimg = (
// <img
// alt={"Shuffle Subflow"}
// src={parsedImage}
// style={{
// marginRight: 20,
// width: imgsize,
// height: imgsize,
// border: `2px solid ${statusColor}`,
// borderRadius:
// executionData.start === data.action.id ? 25 : 5,
// }}
// />
//);
} else if (data.action.app_name === "User Input") {
//actionimg = (
// <img
// alt={"Shuffle Subflow"}
// src={triggers[3].large_image}
// style={{
// marginRight: 20,
// width: imgsize,
// height: imgsize,
// border: `2px solid ${statusColor}`,
// borderRadius:
// executionData.start === data.action.id ? 25 : 5,
// }}
// />
//);
}
if (validate.valid && typeof validate.result === "string") {
validate.result = JSON.parse(validate.result);
}
if (validate.valid && typeof validate.result === "object") {
if (
validate.result.result !== undefined &&
validate.result.result !== null
) {
try {
validate.result.result = JSON.parse(validate.result.result);
} catch (e) {
//console.log("ERROR PARSING: ", e)
}
}
}
var similarActionsView = null
if (data.similar_actions !== undefined && data.similar_actions !== null) {
var minimumMatch = 85
var matching_executions = []
if (data.similar_actions !== undefined && data.similar_actions !== null) {
for (let [k,kval] in Object.entries(data.similar_actions)){
if (data.similar_actions.hasOwnProperty(k)) {
if (data.similar_actions[k].similarity > minimumMatch) {
matching_executions.push(data.similar_actions[k].execution_id)
}
}
}
}
if (matching_executions.length !== 0) {
var parsed_url = matching_executions.join(",")
similarActionsView =
<Tooltip
color="primary"
title="See executions with similar results (not identical)"
placement="top"
style={{ zIndex: 50000, marginLeft: 50, }}
>
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
paddingLeft: 0,
width: 30,
}}
onClick={() => {
//navigate(`?execution_highlight=${parsed_url}`)
}}
>
<PreviewIcon style={{ color: "rgba(255,255,255,0.5)" }} />
</IconButton>
</Tooltip>
}
}
return (
<div
key={index}
style={{
marginBottom: 20,
border:
data.action.sub_action === true
? "1px solid rgba(255,255,255,0.3)"
: "1px solid rgba(255,255,255, 0.3)",
borderRadius: theme.palette.borderRadius,
backgroundColor: theme.palette.inputColor,
padding: "15px 10px 10px 10px",
overflow: "hidden",
}}
onMouseOver={() => {
}}
onMouseOut={() => {
}}
>
<div style={{ marginBottom: 5, display: "flex" }}>
<Typography variant="body1">
<b>Status&nbsp;</b>
</Typography>
<Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
{data.status}
</Typography>
</div>
</div>
)
})*/}
</div>
)
}
@@ -388,7 +252,7 @@ const RunWorkflow = (defaultprops) => {
var data = {
"execution_argument": executionArgument,
"execution_source": "questions",
"execution_source": "form",
}
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()
})
.then(responseJson => {
@@ -509,7 +377,9 @@ const RunWorkflow = (defaultprops) => {
}
const getWorkflow = (workflow_id, selectedNode) => {
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
const url = `${globalUrl}/api/v1/workflows/${workflow_id}`
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -522,6 +392,10 @@ const RunWorkflow = (defaultprops) => {
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();
})
.then((responseJson) => {
@@ -543,16 +417,24 @@ const RunWorkflow = (defaultprops) => {
}
if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0) {
var newexec = {}
for (let questionkey in responseJson.input_questions) {
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)
}
if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) {
var found = false
for (var actionkey in responseJson.actions) {
if (responseJson.actions[actionkey].id === selectedNode) {
@@ -567,6 +449,7 @@ const RunWorkflow = (defaultprops) => {
if (responseJson.triggers[triggerkey].id !== selectedNode) {
continue
}
setFoundSourcenode(responseJson.triggers[triggerkey])
@@ -864,215 +747,277 @@ const RunWorkflow = (defaultprops) => {
const basedata =
<div style={bodyDivStyle}>
<Paper style={boxStyle}>
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ?
<div style={{marginBottom: 20, }}>
<Markdown
components={{
img: Img,
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}
{workflow.id === undefined || workflow.id === null ?
<div style={{paddingTop: 150, marginTop: 150, width: 250, itemAlign: "center", textAlign: "center", margin: "auto", }}>
<CircularProgress />
<Typography variant="body1" style={{marginTop: 20, }}>
Loding Form Details...
</Typography>
<Divider style={{marginTop: 20, marginBottom: 20, }}/>
</div>
:
<div>
{disabledButtons && message.length > 0 ? null :
<Typography color="textSecondary" style={{textAlign: "center", }}>
{message}
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ?
<div style={{marginBottom: 20, }}>
<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>
}
<Divider style={{marginTop: 20, marginBottom: 20, }}/>
{answer !== undefined && answer !== null ? null :
<Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography>
}
{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}
{disabledButtons && message.length > 0 ? null :
<Typography color="textSecondary" style={{textAlign: "center", }}>
{message}
</Typography>
</div>
: null}
}
</div>
}
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
<div style={{marginBottom: 5, }}>
{workflow.input_questions.map((question, index) => {
return (
<div style={{marginBottom: 5}}>
{question.name}
<TextField
color="primary"
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
label={question.value}
required
{answer !== undefined && answer !== null ? null :
<Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography>
}
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>
{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>
</div>
: 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>
}
</div>
}
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
<div style={{marginBottom: 5, }}>
{workflow.input_questions.map((question, index) => {
{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)
// 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]
}
setButtonClicked("FINISHED")
setExecutionData({
status: "FINISHED",
})
}}>Continue</Button>
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
&nbsp;or&nbsp;
</Typography>
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
onSubmit(null, execution_id, authorization, false)
return (
<div style={{marginBottom: 10}} key={index}>
{question.name}
setButtonClicked("ABORTED")
setExecutionData({
status: "ABORTED",
})
}}>Stop</Button>
{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 ? 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>
:
<div style={{display: "flex", marginTop: "15px"}}>
<Button variant="contained" type="submit" color="primary" fullWidth disabled={!handleValidateForm(executionArgument) || executionLoading}>
{executionLoading ?
<CircularProgress color="secondary" style={{color: "white",}} /> : "Submit"}
</Button>
}
{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}
</span>
:
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ?
<span style={{marginTop: 20, }}>
{disabledButtons && message.length > 0 ?
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{message}. You may close this window.
</Typography>
:
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{disabledButtons ? "Answered. You may close this window." : ""}
</Typography>
}
{disabledButtons ? null :
<Typography variant="body2" color="textSecondary" style={{textAlign: "center", marginTop: 10, }}>
What do you want to do?
</Typography>
}
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}>
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{flex: 1,}} onClick={() => {
onSubmit(null, execution_id, authorization, true)
setButtonClicked("FINISHED")
setExecutionData({
status: "FINISHED",
})
}}>Continue</Button>
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
&nbsp;or&nbsp;
</Typography>
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
onSubmit(null, execution_id, authorization, false)
setButtonClicked("ABORTED")
setExecutionData({
status: "ABORTED",
})
}}>Stop</Button>
</div>
</span>
:
<div style={{display: "flex", marginTop: "15px"}}>
<Button
variant={executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ? "outlined" : "contained"}
type="submit"
color="primary"
fullWidth
disabled={!handleValidateForm(executionArgument) || executionLoading}
>
{executionLoading ?
<CircularProgress color="secondary" style={{color: "white",}} /> : "Submit"}
</Button>
</div>
}
{/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, marginLeft: 125, borderRadius: theme.palette.borderRadius, }}
onLoad={() => {
console.log("Img loaded.")
setTimeout(() => {
console.log("Img closing.")
setButtonClicked("finished")
}, 1250)
}}
/>
: null*/}
<div style={{marginTop: "10px"}}>
{executionInfo}
</div>
}
{/*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>
{answer !== undefined && answer !== null ? null :
<ShowExecutionResults executionData={executionData} />
}
</form>
{answer !== undefined && answer !== null ? null :
<ShowExecutionResults executionData={executionData} />
}
</form>
</div>
}
</Paper>
</div>
@@ -1092,6 +1037,7 @@ const RunWorkflow = (defaultprops) => {
usecases={undefined}
expanded={true}
scrollTo={"input_markdown"}
/>
: null}
@@ -1103,20 +1049,59 @@ const RunWorkflow = (defaultprops) => {
PaperProps={{
style: {
color: "white",
minWidth: isMobile ? "90%" : 400,
maxWidth: isMobile ? "90%" : 400,
minHeight: 350,
paddingTop: 25,
paddingLeft: 50,
minWidth: isMobile ? "90%" : 500,
maxWidth: isMobile ? "90%" : 500,
minHeight: 400,
maxHeight: 400,
padding: 25,
borderRadius: theme.palette.borderRadius,
//minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
//maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
},
}}
>
<DialogTitle style={{padding: 30, paddingBottom: 0, zIndex: 1000,}}>
Share Landingpage
<DialogTitle style={{padding: 30, paddingLeft: 20, paddingBottom: 0, zIndex: 1000,}}>
Form Sharing Options for '{workflow.name}'
</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>
</Dialog>
@@ -1135,19 +1120,19 @@ const RunWorkflow = (defaultprops) => {
<Button
variant={"outlined"}
color={"secondary"}
disabled
onClick={() => {
setSharingOpen(true)
}}
>
Manage Sharing
Share Form
</Button>
</div>
: null}
{basedata}
</div>
:
<div>
<div style={{width: 100, itemAlign: "center", textAlign: "center", margin: "auto", }}>
<CircularProgress />
</div>
+8 -14
View File
@@ -24,7 +24,7 @@ const SetAuthentication = (props) => {
const [loadFail, setLoadFail] = 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 parseIncomingOpenapiData = (data) => {
@@ -142,8 +142,8 @@ const SetAuthentication = (props) => {
// 3. Help them set info for the app
// Make sure to test both private and public apps
const appname = app.name !== undefined ? app.name : "";
const appLink = "/apps/" + app.id || "";
const appname = app.name !== undefined ? app.name : ""
const appLink = "/apps/" + app.id || ""
console.log("App: ", app)
@@ -154,21 +154,16 @@ const SetAuthentication = (props) => {
:
<><div>
<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>
{/* What does this mean box */}
<Typography variant="h6" style={{ marginBottom: 20, }}>
What does this mean?
</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.
</Typography>
<Typography variant="h6">
Authenticate Here:
</Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}>
{app.authentication === undefined || app.authentication === null || app.authentication.length === 0 ?
null
@@ -195,7 +190,7 @@ const SetAuthentication = (props) => {
appAuthentication={appAuthentication} />}
</Typography>
<Typography variant="h6" style={{ marginBottom: 20, }}>
<Typography variant="h6" style={{ marginTop: 50, marginBottom: 20, }}>
What can they do with this?
</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>.
</Typography>
<Typography variant="body1" style={{ marginBottom: 20, }}>
{/* Add a box below */}
<Typography variant="body1" style={{ marginBottom: 20, color: "rgba(255,255,255,0.4)",}}>
<div className="collapsible-container">
<div className="collapsible-list">
{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)}>
{item.label}
</div>
+1 -1
View File
@@ -47,7 +47,7 @@ const Welcome = (props) => {
}
}, [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([
"Help us get to know you",
"Find your Apps",
+1 -1
View File
@@ -3528,7 +3528,7 @@ const Workflows = (props) => {
var workflowDelay = -150
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 (
<div style={viewStyle}>
<div style={workflowViewStyle}>