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">