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
+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}>