diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx
index 11e25f0b..f15456c6 100644
--- a/frontend/src/components/AppGrid.jsx
+++ b/frontend/src/components/AppGrid.jsx
@@ -79,6 +79,7 @@ const AppGrid = (props) => {
const [formMail, setFormMail] = React.useState("");
const [message, setMessage] = React.useState("");
const [formMessage, setFormMessage] = React.useState("");
+ const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]);
const buttonStyle = {
borderRadius: 30,
@@ -352,7 +353,7 @@ const AppGrid = (props) => {
if (responseJson.success === false) {
toast.error(responseJson.reason);
} else {
- toast.success(`App ${type}d Successfully!`);
+ //toast.success(`App ${type}d Successfully!`);
if (type === 'activate') {
setAllActivatedAppIds(prev => [...prev, data.objectID]);
setIsAnyAppActivated(true);
@@ -395,7 +396,7 @@ const AppGrid = (props) => {
{!isLoading ? (
{hits.length === 0 && searchQuery.length >= 0 && showNoAppFound ? (
-
No App Found
+
No Apps Found
) : (
{
Filter By
+
+
+
+
)}
@@ -1722,6 +1727,10 @@ const AppGrid = (props) => {
? `/apps/${data.id}`
: `https://shuffler.io/apps/${data.id}`;
+ if (data.name === "" && data.id === "") {
+ return null
+ }
+
return (
{
width: 230,
textAlign: 'start',
marginLeft: 8,
- color: "rgba(158, 158, 158, 1)"
+ color: "rgba(158, 158, 158, 1)",
+ display: "flex",
}}
>
- {data.tags &&
- data.tags.map((tag, tagIndex) => (
-
- {normalizedString(tag)}
- {tagIndex < data.tags.length - 1 ? ", " : ""}
-
- ))}
-
+
+ {data.generated !== true ?
+
+ {data.tags &&
+ data.tags.slice(0,2).map((tag, tagIndex) => (
+
+ {normalizedString(tag)}
+ {tagIndex < data.tags.length - 1 ? ", " : ""}
+
+ ))
+ }
+
+ : null}
+
+ {currTab === 1 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true ?
+ {
+ //deactivatedIndexes.push(index)
+ //setDeactivatedIndexes(deactivatedIndexes)
+
+ event.preventDefault();
+ event.stopPropagation();
+ //handleActivateButton(event, data, "deactivate");
+ // FIXME: Put this in a function lol
+ const url = `${globalUrl}/api/v1/apps/${data.id}/deactivate`;
+
+ fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ credentials: "include",
+ })
+ .then((response) => response.json())
+ .then((responseJson) => {
+ if (responseJson.success === false) {
+ toast.error(responseJson.reason);
+ } else {
+ toast.success("App Deactivated Successfully. Reload UI to see updated changes.")
+ //const updatedIds = allActivatedAppIds.filter(id => id !== data.objectID);
+ //setAllActivatedAppIds(updatedIds);
+ }
+ })
+ .catch(error => {
+ console.log("app error: ", error.toString());
+ })
+ }}>
+ Deactivate
+
+ : null}
+
+
{/* )} */}
@@ -1946,6 +2009,7 @@ const AppGrid = (props) => {
selectedOptionOfCreatedWith={selectedOptionOfCreatedWith}
/>
)}
+
{
setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps}
setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith}
/>
+
diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx
index d5b33aca..f7598852 100644
--- a/frontend/src/components/Billing.jsx
+++ b/frontend/src/components/Billing.jsx
@@ -1623,12 +1623,12 @@ const Billing = (props) => {
{userdata.support === true ?
:
diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx
index e2443664..46be4138 100644
--- a/frontend/src/components/EditWorkflow.jsx
+++ b/frontend/src/components/EditWorkflow.jsx
@@ -6,6 +6,7 @@ import UsecaseSearch from "../components/UsecaseSearch.jsx"
import WorkflowGrid from "../components/WorkflowGrid.jsx"
import dayjs from 'dayjs';
import WorkflowTemplatePopup from "./WorkflowTemplatePopup.jsx";
+import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
import {
Badge,
@@ -206,6 +207,16 @@ const EditWorkflow = (props) => {
Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more
+
+
+
+
+
{showUpload === true ?
@@ -957,7 +968,7 @@ const EditWorkflow = (props) => {
}}
>
{showMoreClicked ? : }
- {showMoreClicked ? "Collapse": "Expand"}
+ {showMoreClicked ? "Less Options": "More Options"}
diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx
index 1c12d7fd..014fa46f 100644
--- a/frontend/src/components/LicencePopup.jsx
+++ b/frontend/src/components/LicencePopup.jsx
@@ -515,16 +515,16 @@ const LicencePopup = (props) => {
}
useEffect(() => {
- console.log("New variant: ", shuffleVariant)
+ console.log("New variant: ", shuffleVariant)
- if (shuffleVariant === 1) {
- setCalculatedCost("$600")
- setSelectedValue(8)
- } else {
- setCalculatedCost("$540")
- setSelectedValue(300)
- }
- }, [shuffleVariant])
+ if (shuffleVariant === 1) {
+ setCalculatedCost("$960")
+ setSelectedValue(8)
+ } else {
+ setCalculatedCost("$960")
+ setSelectedValue(300)
+ }
+ }, [shuffleVariant])
if (typeof window === 'undefined' || window.location === undefined) {
return null
@@ -680,7 +680,7 @@ const LicencePopup = (props) => {
color: "white",
}
-
+ console.log("Priceitem: ", shuffleVariant)
const isLoggedInHandler = () => {
if (calculatedCost === payasyougo) {
handlePayasyougo(props.userdata)
@@ -690,7 +690,7 @@ const LicencePopup = (props) => {
const priceItem = window.location.origin === "https://shuffler.io" ?
shuffleVariant === 0 ? "app_executions" : "cores"
:
- shuffleVariant === 0 ? "price_1MROFrDzMUgUjxHShcSxgHO1" : "price_1NXjQqDzMUgUjxHSg690R4FP"
+ shuffleVariant === 0 ? "price_1PWI5zDzMUgUjxHSKkz0fGdN" : "price_1NXjQqDzMUgUjxHSg690R4FP"
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx
index 12bc756a..fa626601 100644
--- a/frontend/src/components/NewHeader.jsx
+++ b/frontend/src/components/NewHeader.jsx
@@ -517,12 +517,11 @@ const Header = (props) => {
window.location.href = responseJson["url"]
return
}, 2000)
- }
-
+ } else {
setTimeout(() => {
window.location.reload()
}, 2000);
-
+ }
toast("Successfully changed active organization - refreshing!");
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
@@ -1547,7 +1546,7 @@ const Header = (props) => {
{/* Shuffle 1.4.0 is out! Read more about */}
- Shuffle now offers
+ Early Success! More
{/*
{
ReactGA.event({
@@ -1596,10 +1595,11 @@ const Header = (props) => {
navigate("/training")
- }} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}>
- Public Training!
+ }} style={{ cursor: "pointer", textDecoration: "none", fontWeight:600, color: "rgba(255,255,255,0.8)" }}>
+ Public Trainings
+ Ahead!
{
setShowTopbar(false)
diff --git a/frontend/src/components/OrgHeaderexpanded.jsx b/frontend/src/components/OrgHeaderexpanded.jsx
index 4cfbd690..58d1a244 100644
--- a/frontend/src/components/OrgHeaderexpanded.jsx
+++ b/frontend/src/components/OrgHeaderexpanded.jsx
@@ -1,516 +1,517 @@
-import React, { useEffect } from "react";
-
-import { makeStyles } from "@mui/styles";
-import theme from '../theme.jsx';
-import { toast } from "react-toastify"
-import Chip from '@mui/material/Chip';
-import Stack from '@mui/material/Stack';
-import SubflowSuggestions from "../components/SubflowSuggestions.jsx";
-
-import {
- FormControl,
- InputLabel,
- Paper,
- OutlinedInput,
- Checkbox,
- Card,
- Tooltip,
- FormControlLabel,
- Typography,
- Switch,
- Select,
- MenuItem,
- Divider,
- TextField,
- Button,
- Tabs,
- Tab,
- Grid,
- IconButton,
- Autocomplete,
- Dialog,
- DialogTitle,
- DialogActions,
- DialogContent,
- Box
-} from "@mui/material";
-
-import {
- ExpandLess as ExpandLessIcon,
- ExpandMore as ExpandMoreIcon,
- Save as SaveIcon,
-} from "@mui/icons-material";
-
-const useStyles = makeStyles({
- notchedOutline: {
- borderColor: "#f85a3e !important",
- },
-})
-
-const OrgHeaderexpanded = (props) => {
- const {
- userdata,
- selectedOrganization,
- setSelectedOrganization,
- globalUrl,
- isCloud,
- adminTab,
+import React, { useEffect } from "react";
+
+import { makeStyles } from "@mui/styles";
+import theme from '../theme.jsx';
+import { toast } from "react-toastify"
+import Chip from '@mui/material/Chip';
+import Stack from '@mui/material/Stack';
+import SubflowSuggestions from "../components/SubflowSuggestions.jsx";
+
+import {
+ FormControl,
+ InputLabel,
+ Paper,
+ OutlinedInput,
+ Checkbox,
+ Card,
+ Tooltip,
+ FormControlLabel,
+ Typography,
+ Switch,
+ Select,
+ MenuItem,
+ Divider,
+ TextField,
+ Button,
+ Tabs,
+ Tab,
+ Grid,
+ IconButton,
+ Autocomplete,
+ Dialog,
+ DialogTitle,
+ DialogActions,
+ DialogContent,
+ Box
+} from "@mui/material";
+
+import {
+ ExpandLess as ExpandLessIcon,
+ ExpandMore as ExpandMoreIcon,
+ Save as SaveIcon,
+} from "@mui/icons-material";
+
+const useStyles = makeStyles({
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+})
+
+const OrgHeaderexpanded = (props) => {
+ const {
+ userdata,
+ selectedOrganization,
+ setSelectedOrganization,
+ globalUrl,
+ isCloud,
+ adminTab,
selectedStatus,
setSelectedStatus,
isEditOrgTab
- } = props;
-
- const classes = useStyles();
- const defaultBranch = "master";
-
- const [orgName, setOrgName] = React.useState(selectedOrganization.name);
- const [orgDescription, setOrgDescription] = React.useState(
- selectedOrganization.description
- );
-
- const [appDownloadUrl, setAppDownloadUrl] = React.useState(
- selectedOrganization.defaults === undefined
- ? "https://github.com/frikky/shuffle-apps"
- : selectedOrganization.defaults.app_download_repo === undefined ||
- selectedOrganization.defaults.app_download_repo.length === 0
- ? "https://github.com/frikky/shuffle-apps"
- : selectedOrganization.defaults.app_download_repo
- );
- const [appDownloadBranch, setAppDownloadBranch] = React.useState(
- selectedOrganization.defaults === undefined
- ? defaultBranch
- : selectedOrganization.defaults.app_download_branch === undefined ||
- selectedOrganization.defaults.app_download_branch.length === 0
- ? defaultBranch
- : selectedOrganization.defaults.app_download_branch
- );
- const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState(
- selectedOrganization.defaults === undefined
- ? "https://github.com/frikky/shuffle-apps"
- : selectedOrganization.defaults.workflow_download_repo === undefined ||
- selectedOrganization.defaults.workflow_download_repo.length === 0
- ? "https://github.com/frikky/shuffle-workflows"
- : selectedOrganization.defaults.workflow_download_repo
- );
- const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState(
- selectedOrganization.defaults === undefined
- ? defaultBranch
- : selectedOrganization.defaults.workflow_download_branch === undefined ||
- selectedOrganization.defaults.workflow_download_branch.length === 0
- ? defaultBranch
- : selectedOrganization.defaults.workflow_download_branch
- );
- const [ssoEntrypoint, setSsoEntrypoint] = React.useState(
- selectedOrganization.sso_config === undefined
- ? ""
- : selectedOrganization.sso_config.sso_entrypoint === undefined ||
- selectedOrganization.sso_config.sso_entrypoint.length === 0
- ? ""
- : selectedOrganization.sso_config.sso_entrypoint
- );
- const [ssoCertificate, setSsoCertificate] = React.useState(
- selectedOrganization.sso_config === undefined
- ? ""
- : selectedOrganization.sso_config.sso_certificate === undefined ||
- selectedOrganization.sso_config.sso_certificate.length === 0
- ? ""
- : selectedOrganization.sso_config.sso_certificate
- );
- const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined
- ? false
- : selectedOrganization.sso_config.SSORequired === undefined
- ? false
- : selectedOrganization.sso_config.SSORequired);
-
- const [notificationWorkflow, setNotificationWorkflow] = React.useState(
- selectedOrganization.defaults === undefined
- ? ""
- : selectedOrganization.defaults.notification_workflow === undefined ||
- selectedOrganization.defaults.notification_workflow.length === 0
- ? ""
- : selectedOrganization.defaults.notification_workflow
- );
-
- const [documentationReference, setDocumentationReference] = React.useState(
- selectedOrganization.defaults === undefined
- ? ""
- : selectedOrganization.defaults.documentation_reference === undefined ||
- selectedOrganization.defaults.documentation_reference.length === 0
- ? ""
- : selectedOrganization.defaults.documentation_reference
- );
- const [openidClientId, setOpenidClientId] = React.useState(
- selectedOrganization.sso_config === undefined
- ? ""
- : selectedOrganization.sso_config.client_id === undefined ||
- selectedOrganization.sso_config.client_id.length === 0
- ? ""
- : selectedOrganization.sso_config.client_id
- );
- const [openidClientSecret, setOpenidClientSecret] = React.useState(
- selectedOrganization.sso_config === undefined
- ? ""
- : selectedOrganization.sso_config.client_secret === undefined ||
- selectedOrganization.sso_config.client_secret.length === 0
- ? ""
- : selectedOrganization.sso_config.client_secret
- );
- const [openidAuthorization, setOpenidAuthorization] = React.useState(
- selectedOrganization.sso_config === undefined
- ? ""
- : selectedOrganization.sso_config.openid_authorization === undefined ||
- selectedOrganization.sso_config.openid_authorization.length === 0
- ? ""
- : selectedOrganization.sso_config.openid_authorization
- );
- const [openidToken, setOpenidToken] = React.useState(
- selectedOrganization.sso_config === undefined
- ? ""
- : selectedOrganization.sso_config.openid_token === undefined ||
- selectedOrganization.sso_config.openid_token.length === 0
- ? ""
- : selectedOrganization.sso_config.openid_token
- )
+ } = props;
+
+ const classes = useStyles();
+ const defaultBranch = "master";
+
+ const [orgName, setOrgName] = React.useState(selectedOrganization.name);
+ const [orgDescription, setOrgDescription] = React.useState(
+ selectedOrganization.description
+ );
+
+ const [appDownloadUrl, setAppDownloadUrl] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? "https://github.com/frikky/shuffle-apps"
+ : selectedOrganization.defaults.app_download_repo === undefined ||
+ selectedOrganization.defaults.app_download_repo.length === 0
+ ? "https://github.com/frikky/shuffle-apps"
+ : selectedOrganization.defaults.app_download_repo
+ );
+ const [appDownloadBranch, setAppDownloadBranch] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? defaultBranch
+ : selectedOrganization.defaults.app_download_branch === undefined ||
+ selectedOrganization.defaults.app_download_branch.length === 0
+ ? defaultBranch
+ : selectedOrganization.defaults.app_download_branch
+ );
+ const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? "https://github.com/frikky/shuffle-apps"
+ : selectedOrganization.defaults.workflow_download_repo === undefined ||
+ selectedOrganization.defaults.workflow_download_repo.length === 0
+ ? "https://github.com/frikky/shuffle-workflows"
+ : selectedOrganization.defaults.workflow_download_repo
+ );
+ const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? defaultBranch
+ : selectedOrganization.defaults.workflow_download_branch === undefined ||
+ selectedOrganization.defaults.workflow_download_branch.length === 0
+ ? defaultBranch
+ : selectedOrganization.defaults.workflow_download_branch
+ );
+ const [ssoEntrypoint, setSsoEntrypoint] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.sso_entrypoint === undefined ||
+ selectedOrganization.sso_config.sso_entrypoint.length === 0
+ ? ""
+ : selectedOrganization.sso_config.sso_entrypoint
+ );
+ const [ssoCertificate, setSsoCertificate] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.sso_certificate === undefined ||
+ selectedOrganization.sso_config.sso_certificate.length === 0
+ ? ""
+ : selectedOrganization.sso_config.sso_certificate
+ );
+ const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined
+ ? false
+ : selectedOrganization.sso_config.SSORequired === undefined
+ ? false
+ : selectedOrganization.sso_config.SSORequired);
+
+ const [notificationWorkflow, setNotificationWorkflow] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? ""
+ : selectedOrganization.defaults.notification_workflow === undefined ||
+ selectedOrganization.defaults.notification_workflow.length === 0
+ ? ""
+ : selectedOrganization.defaults.notification_workflow
+ );
+
+ const [documentationReference, setDocumentationReference] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? ""
+ : selectedOrganization.defaults.documentation_reference === undefined ||
+ selectedOrganization.defaults.documentation_reference.length === 0
+ ? ""
+ : selectedOrganization.defaults.documentation_reference
+ );
+ const [openidClientId, setOpenidClientId] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.client_id === undefined ||
+ selectedOrganization.sso_config.client_id.length === 0
+ ? ""
+ : selectedOrganization.sso_config.client_id
+ );
+ const [openidClientSecret, setOpenidClientSecret] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.client_secret === undefined ||
+ selectedOrganization.sso_config.client_secret.length === 0
+ ? ""
+ : selectedOrganization.sso_config.client_secret
+ );
+ const [openidAuthorization, setOpenidAuthorization] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.openid_authorization === undefined ||
+ selectedOrganization.sso_config.openid_authorization.length === 0
+ ? ""
+ : selectedOrganization.sso_config.openid_authorization
+ );
+ const [openidToken, setOpenidToken] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.openid_token === undefined ||
+ selectedOrganization.sso_config.openid_token.length === 0
+ ? ""
+ : selectedOrganization.sso_config.openid_token
+ )
const [uploadRepo, setUploadRepo] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_repo === undefined || selectedOrganization.defaults.workflow_upload_repo.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_repo)
const [uploadBranch, setUploadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch === undefined || selectedOrganization.defaults.workflow_upload_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch)
const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username)
const [uploadToken, setUploadToken] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_token === undefined || selectedOrganization.defaults.workflow_upload_token.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_token)
-
- const [workflows, setWorkflows] = React.useState([])
- const [workflow, setWorkflow] = React.useState({})
-
- const getAvailableWorkflows = (trigger_index) => {
- fetch(globalUrl + "/api/v1/workflows", {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Accept: "application/json",
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for workflows :O!");
- return;
- }
- return response.json();
- })
- .then((responseJson) => {
- if (responseJson !== undefined) {
- setWorkflows(responseJson)
-
- if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) {
-
- const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow)
- if (workflow !== undefined && workflow !== null) {
- setWorkflow(workflow)
- }
- }
- }
- })
- .catch((error) => {
- console.log("Error getting workflows: " + error);
- })
- }
-
- useEffect(() => {
- getAvailableWorkflows()
- }, [])
-
- const handleEditOrg = (
- name,
- description,
- orgId,
- image,
- defaults,
- sso_config
- ) => {
-
- const data = {
- name: name,
- description: description,
- org_id: orgId,
- image: image,
- defaults: defaults,
- sso_config: sso_config,
- };
-
- const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
- fetch(url, {
- mode: "cors",
- method: "POST",
- body: JSON.stringify(data),
- credentials: "include",
- crossDomain: true,
- withCredentials: true,
- headers: {
- "Content-Type": "application/json; charset=utf-8",
- },
- })
- .then((response) =>
- response.json().then((responseJson) => {
- if (responseJson["success"] === false) {
- toast("Failed updating org: ", responseJson.reason);
- } else {
- toast("Successfully edited org!");
- }
- })
- )
- .catch((error) => {
- toast("Err: " + error.toString());
- });
- };
-
-
- const handleWorkflowSelectionUpdate = (e, isUserinput) => {
- if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
- console.log("Returning as there's no id")
- return null
- }
-
- setWorkflow(e.target.value)
- setNotificationWorkflow(e.target.value.id)
- toast("Updated notification workflow. Don't forget to save!")
- }
-
- const orgSaveButton = (
-
-
-
- handleEditOrg(
- orgName,
- orgDescription,
- selectedOrganization.id,
- selectedOrganization.image,
- {
- app_download_repo: appDownloadUrl,
- app_download_branch: appDownloadBranch,
- workflow_download_repo: workflowDownloadUrl,
- workflow_download_branch: workflowDownloadBranch,
- notification_workflow: notificationWorkflow,
- documentation_reference: documentationReference,
- },
- {
- sso_entrypoint: ssoEntrypoint,
- sso_certificate: ssoCertificate,
- client_id: openidClientId,
- client_secret: openidClientSecret,
- openid_authorization: openidAuthorization,
- openid_token: openidToken,
- }
- )
- }
- >
-
-
-
-
- );
-
- const toggleBetweenRequiredOrOptional = (event) => {
- setSSORequired(event.target.checked);
- };
-
- return (
-
-
-
-
- Notification Workflow
-
- {/*
-
- */}
-
-
-
- {workflows !== undefined && workflows !== null && workflows.length > 0 ?
-
{
- if (
- option === undefined ||
- option === null ||
- option.name === undefined ||
- option.name === null
- ) {
- return "No Workflow Selected";
- }
-
- const newname = (
- option.name.charAt(0).toUpperCase() + option.name.substring(1)
- ).replaceAll("_", " ");
- return newname;
- }}
- options={workflows}
- fullWidth
- style={{
- backgroundColor: theme.palette.inputColor,
- height: 50,
- borderRadius: theme.palette.borderRadius,
- }}
- onChange={(event, newValue) => {
- console.log("Found value: ", newValue)
-
- var parsedinput = { target: { value: newValue } }
-
- // For variables
- if (typeof newValue === 'string' && newValue.startsWith("$")) {
- parsedinput = {
- target: {
- value: {
- "name": newValue,
- "id": newValue,
- "actions": [],
- "triggers": [],
- }
- }
- }
- }
-
- handleWorkflowSelectionUpdate(parsedinput)
- }}
- renderOption={(props, data, state) => {
- if (data.id === workflow.id) {
- data = workflow;
- }
-
- return (
-
- {data.image !== undefined && data.image !== null && data.image.length > 0 ?
-
- : null}
-
- Choose {data.name}
-
-
- } placement="bottom">
- {
- var parsedinput = { target: { value: data } }
- handleWorkflowSelectionUpdate(parsedinput)
- }}
- >
- {data.name}
-
-
- )
- }}
- renderInput={(params) => {
- return (
-
- );
- }}
- />
- :
- {
- setNotificationWorkflow(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
- }
-
- {orgSaveButton}
-
-
-
-
-
-
- Org Documentation reference
- {
- setDocumentationReference(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
+
+ const [workflows, setWorkflows] = React.useState([])
+ const [workflow, setWorkflow] = React.useState({})
+
+ const getAvailableWorkflows = (trigger_index) => {
+ fetch(globalUrl + "/api/v1/workflows", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for workflows :O!");
+ return;
+ }
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson !== undefined) {
+ setWorkflows(responseJson)
+
+ if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) {
+
+ const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow)
+ if (workflow !== undefined && workflow !== null) {
+ setWorkflow(workflow)
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ console.log("Error getting workflows: " + error);
+ })
+ }
+
+ useEffect(() => {
+ getAvailableWorkflows()
+ }, [])
+
+ const handleEditOrg = (
+ name,
+ description,
+ orgId,
+ image,
+ defaults,
+ sso_config
+ ) => {
+
+ const data = {
+ name: name,
+ description: description,
+ org_id: orgId,
+ image: image,
+ defaults: defaults,
+ sso_config: sso_config,
+ };
+
+ const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
+ fetch(url, {
+ mode: "cors",
+ method: "POST",
+ body: JSON.stringify(data),
+ credentials: "include",
+ crossDomain: true,
+ withCredentials: true,
+ headers: {
+ "Content-Type": "application/json; charset=utf-8",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ toast("Failed updating org: ", responseJson.reason);
+ } else {
+ toast("Successfully edited org!");
+ }
+ })
+ )
+ .catch((error) => {
+ toast("Err: " + error.toString());
+ });
+ };
+
+
+ const handleWorkflowSelectionUpdate = (e, isUserinput) => {
+ if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
+ console.log("Returning as there's no id")
+ return null
+ }
+
+ setWorkflow(e.target.value)
+ setNotificationWorkflow(e.target.value.id)
+ toast("Updated notification workflow. Don't forget to save!")
+ }
+
+ const orgSaveButton = (
+
+
+
+ handleEditOrg(
+ orgName,
+ orgDescription,
+ selectedOrganization.id,
+ selectedOrganization.image,
+ {
+ app_download_repo: appDownloadUrl,
+ app_download_branch: appDownloadBranch,
+ workflow_download_repo: workflowDownloadUrl,
+ workflow_download_branch: workflowDownloadBranch,
+ notification_workflow: notificationWorkflow,
+ documentation_reference: documentationReference,
+ },
+ {
+ sso_entrypoint: ssoEntrypoint,
+ sso_certificate: ssoCertificate,
+ client_id: openidClientId,
+ client_secret: openidClientSecret,
+ openid_authorization: openidAuthorization,
+ openid_token: openidToken,
+ SSORequired: SSORequired,
+ }
+ )
+ }
+ >
+
+
+
+
+ );
+
+ const toggleBetweenRequiredOrOptional = (event) => {
+ setSSORequired(event.target.checked);
+ };
+
+ return (
+
+
+
+
+ Notification Workflow
+
+ {/*
+
+ */}
+
+
+
+ {workflows !== undefined && workflows !== null && workflows.length > 0 ?
+
{
+ if (
+ option === undefined ||
+ option === null ||
+ option.name === undefined ||
+ option.name === null
+ ) {
+ return "No Workflow Selected";
+ }
+
+ const newname = (
+ option.name.charAt(0).toUpperCase() + option.name.substring(1)
+ ).replaceAll("_", " ");
+ return newname;
+ }}
+ options={workflows}
+ fullWidth
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ height: 50,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(event, newValue) => {
+ console.log("Found value: ", newValue)
+
+ var parsedinput = { target: { value: newValue } }
+
+ // For variables
+ if (typeof newValue === 'string' && newValue.startsWith("$")) {
+ parsedinput = {
+ target: {
+ value: {
+ "name": newValue,
+ "id": newValue,
+ "actions": [],
+ "triggers": [],
+ }
+ }
+ }
+ }
+
+ handleWorkflowSelectionUpdate(parsedinput)
+ }}
+ renderOption={(props, data, state) => {
+ if (data.id === workflow.id) {
+ data = workflow;
+ }
+
+ return (
+
+ {data.image !== undefined && data.image !== null && data.image.length > 0 ?
+
+ : null}
+
+ Choose {data.name}
+
+
+ } placement="bottom">
+ {
+ var parsedinput = { target: { value: data } }
+ handleWorkflowSelectionUpdate(parsedinput)
+ }}
+ >
+ {data.name}
+
+
+ )
+ }}
+ renderInput={(params) => {
+ return (
+
+ );
+ }}
+ />
+ :
+ {
+ setNotificationWorkflow(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+ }
+
+ {orgSaveButton}
+
+
+
+
+
+
+ Org Documentation reference
+ {
+ setDocumentationReference(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
Workflow Backup Repository
@@ -663,409 +664,409 @@ const OrgHeaderexpanded = (props) => {
SSO Configuration
-
-
Make SAML SSO or OpenID Authentication Required or Optional for Your Organization.
-
-
- {SSORequired ? 'Required' : 'Optional'}
-
-
-
- OpenID connect
-
-
-
- Client ID
- 0
- }
- id="outlined-with-placeholder"
- margin="normal"
- variant="outlined"
- placeholder="The OpenID client ID from the identity provider"
- value={openidClientId}
- onChange={(e) => {
- setOpenidClientId(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
-
-
- Client Secret (optional)
- 0
- }
- id="outlined-with-placeholder"
- margin="normal"
- variant="outlined"
- placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE"
- value={openidClientSecret}
- onChange={(e) => {
- setOpenidClientSecret(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
-
-
-
-
- Authorization URL
- {
- setOpenidAuthorization(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
-
-
- Token URL
- {
- setOpenidToken(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
-
-
- {/* } */}
- {/*isCloud ? null : */}
-
- SAML SSO (v1.1)
-
-
-
- SSO Entrypoint (IdP)
- 0
- }
- id="outlined-with-placeholder"
- margin="normal"
- variant="outlined"
- placeholder="The entrypoint URL from your provider"
- value={ssoEntrypoint}
- onChange={(e) => {
- setSsoEntrypoint(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
-
-
- SSO Certificate (X509)
- {
- setSsoCertificate(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
-
- {isCloud ?
-
- IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso
-
- : null}
-
- {isCloud ? null : (
-
-
- App Download URL
- {
- setAppDownloadUrl(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
- )}
- {isCloud ? null : (
-
-
- App Download Branch
- {
- setAppDownloadBranch(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
- )}
- {isCloud ? null : (
-
-
- Workflow Download URL
- {
- setWorkflowDownloadUrl(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
- )}
- {isCloud ? null : (
-
-
- Workflow Download Branch
- {
- setWorkflowDownloadBranch(e.target.value);
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style: {
- color: "white",
- },
- }}
- />
-
-
- )}
-
-
- {orgSaveButton}
-
- {/*
-
- {expanded ?
-
- :
-
- }
-
- */}
-
-
- )
-}
-
+
+
Make SAML SSO or OpenID Authentication Required or Optional for Your Organization.
+
+
+ {SSORequired ? 'Required' : 'Optional'}
+
+
+
+ OpenID connect
+
+
+
+ Client ID
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The OpenID client ID from the identity provider"
+ value={openidClientId}
+ onChange={(e) => {
+ setOpenidClientId(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ Client Secret (optional)
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE"
+ value={openidClientSecret}
+ onChange={(e) => {
+ setOpenidClientSecret(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+
+
+ Authorization URL
+ {
+ setOpenidAuthorization(e.target.value)
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ Token URL
+ {
+ setOpenidToken(e.target.value)
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ {/* } */}
+ {/*isCloud ? null : */}
+
+ SAML SSO (v1.1)
+
+
+
+ SSO Entrypoint (IdP)
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The entrypoint URL from your provider"
+ value={ssoEntrypoint}
+ onChange={(e) => {
+ setSsoEntrypoint(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ SSO Certificate (X509)
+ {
+ setSsoCertificate(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+ {isCloud ?
+
+ IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso
+
+ : null}
+
+ {isCloud ? null : (
+
+
+ App Download URL
+ {
+ setAppDownloadUrl(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+ {isCloud ? null : (
+
+
+ App Download Branch
+ {
+ setAppDownloadBranch(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+ {isCloud ? null : (
+
+
+ Workflow Download URL
+ {
+ setWorkflowDownloadUrl(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+ {isCloud ? null : (
+
+
+ Workflow Download Branch
+ {
+ setWorkflowDownloadBranch(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+
+
+ {orgSaveButton}
+
+ {/*
+
+ {expanded ?
+
+ :
+
+ }
+
+ */}
+
+
+ )
+}
+
export default OrgHeaderexpanded;
diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx
index 9249361f..8a0ef74a 100755
--- a/frontend/src/components/ParsedAction.jsx
+++ b/frontend/src/components/ParsedAction.jsx
@@ -307,7 +307,6 @@ const ParsedAction = (props) => {
(action) =>
action.name.toLowerCase() === selectedAction.name.toLowerCase()
);
- console.log("FOUNDACTION: ", foundAction);
if (foundAction !== null && foundAction !== undefined) {
var foundparams = [];
for (let [paramkey,paramkeyval] in Object.entries(foundAction.parameters)) {
@@ -549,6 +548,11 @@ const ParsedAction = (props) => {
}
}
}
+
+ if (parentNode.label === undefined) {
+ parentNode.label = ""
+ }
+
newActionList.push({
type: "action",
id: parentNode.id,
diff --git a/frontend/src/components/WorkflowValidationTimeline.jsx b/frontend/src/components/WorkflowValidationTimeline.jsx
new file mode 100644
index 00000000..cbbb7bd6
--- /dev/null
+++ b/frontend/src/components/WorkflowValidationTimeline.jsx
@@ -0,0 +1,443 @@
+import React, { useState, } from "react";
+import { makeStyles, createStyles } from "@mui/styles";
+import { toast } from "react-toastify"
+
+import {
+ Tooltip,
+ Typography,
+
+ Avatar,
+ AvatarGroup,
+} from "@mui/material"
+
+import {
+ green,
+ yellow,
+ red,
+} from "../views/AngularWorkflow.jsx"
+
+import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
+import theme from "../theme.jsx";
+const itemHeight = 40
+
+export const getParentNodes = (workflow, action) => {
+ if (action === undefined || action === null) {
+ return []
+ }
+
+ if (workflow.actions === undefined || workflow.actions === null) {
+ workflow.actions = []
+ }
+
+ if (workflow.triggers === undefined || workflow.triggers === null) {
+ workflow.triggers = []
+ }
+
+ if (workflow.branches === undefined || workflow.branches === null) {
+ workflow.branches = []
+ }
+
+ var allkeys = [action.id];
+ var handled = [];
+ var results = [];
+
+ // maxiter = max amount of parent nodes to loop
+ // also handles breaks if there are issues
+ var iterations = 0;
+ var maxiter = 10;
+ while (true) {
+ for (let parentkey in allkeys) {
+ if (allkeys[parentkey] === undefined) {
+ continue
+ }
+
+ var currentnode = workflow.actions.find((element) => element.id === allkeys[parentkey])
+ if (currentnode === undefined) {
+ currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey])
+
+ if (currentnode === undefined) {
+ console.log("Could not find parent node for: ", allkeys[parentkey])
+ continue
+ }
+ }
+
+ if (handled.includes(currentnode.id)) {
+ continue
+ } else {
+ handled.push(currentnode.id);
+ results.push(currentnode);
+ }
+
+ // Get the name / label here too?
+ if (currentnode.length === 0) {
+ continue;
+ }
+
+ // FIXME: This part is only handling first level,
+ // but needs to recurse
+ var incomingEdges = []
+ for (var branchkey in workflow.branches) {
+ const branch = workflow.branches[branchkey]
+ if (branch.destination_id !== currentnode.id) {
+ continue
+ }
+
+ // Go up in the levels
+ const parents = getParentNodes(workflow, {
+ id: branch.source_id,
+ })
+ if (parents.length > 0) {
+ incomingEdges = incomingEdges.concat(parents)
+ }
+
+ incomingEdges.push(branch)
+ }
+
+ for (let i = 0; i < incomingEdges.length; i++) {
+ var tmp = incomingEdges[i];
+ if (tmp.decorator === true) {
+ continue
+ }
+
+ if (!allkeys.includes(tmp.source_id)) {
+ allkeys.push(tmp.source_id)
+ }
+ }
+ }
+
+ if (results.length === allkeys.length || iterations === maxiter) {
+ break
+ }
+
+ iterations += 1
+ }
+
+ // Remove on the end as we don't want to remove everything
+ results = results.filter((data) => data.id !== action.id)
+ results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input" || data.app_name === "shuffle-subflow")
+ results.push({ label: "Execution Argument", type: "INTERNAL" })
+
+ return results
+}
+
+const WorkflowValidationTimeline = (props) => {
+ const { workflow, originalWorkflow, apps, getParents, execution} = props
+
+
+ if (workflow === undefined || workflow === null) {
+ return null
+ }
+
+ if (workflow.validation === undefined || workflow.validation === null) {
+ return null
+ }
+
+ if (workflow.actions === undefined || workflow.actions === null) {
+ workflow.actions = []
+ }
+
+ if (workflow.triggers === undefined || workflow.triggers === null) {
+ workflow.triggers = []
+
+ }
+
+ if (workflow.branches === undefined || workflow.branches === null) {
+ workflow.branches = []
+
+ }
+
+ var results = []
+ if (execution !== undefined) {
+ results = execution.results
+ }
+
+ // 1. Find startnode
+ // 2. Map childnodes from it
+ const startnodeId = workflow.start
+
+ // Find parent of startnodeId and if it's a webhook
+ var relevantactions = []
+ for (var key in workflow.branches) {
+ const branch = workflow.branches[key]
+ if (branch.destination_id !== startnodeId) {
+ continue
+ }
+
+ for (var triggerkey in workflow.triggers) {
+ const trigger = workflow.triggers[triggerkey]
+ if (trigger.trigger_type !== "WEBHOOK") {
+ continue
+ }
+
+ if (trigger.id === branch.source_id) {
+ trigger.order = -1
+ relevantactions.push(trigger)
+ break
+ }
+ }
+ }
+
+
+ if (getParents !== undefined) {
+ for (var key in workflow.actions) {
+ const action = workflow.actions[key]
+ if (action.id === startnodeId) {
+ action.order = 0
+ relevantactions.push(action)
+ continue
+ }
+
+ const parents = getParents(action)
+ //const parents = getParentNodes(workflow, action)
+ //console.log("PARENTS", key, parents)
+ if (parents !== undefined && parents !== null) {
+ const parentfound = parents.find((element) => element.id === startnodeId)
+ if (parentfound !== undefined) {
+
+ // FIXME: add order here based on how many steps away from the startnode
+ // This just has the parent count
+ action.order = parents.length
+
+ relevantactions.push(action)
+ }
+ }
+ }
+ } else {
+ for (var key in workflow.triggers) {
+ const trigger = workflow.triggers[key]
+ if (trigger.trigger_type !== "SUBFLOW" && trigger.trigger_type !== "USERINPUT") {
+ continue
+ }
+
+ if (workflow.actions.find((element) => element.id === trigger.id) === undefined) {
+ workflow.actions.push(trigger)
+ }
+ }
+
+ relevantactions = workflow.actions
+ }
+
+ // Sort according to how many parents a node has. MAY be wrong~
+ relevantactions.sort((a, b) => {
+ if (a.order === undefined) {
+ return 1
+ }
+
+ if (b.order === undefined) {
+ return -1
+ }
+
+ return a.order - b.order
+ })
+
+ // FIXME: Add other relevant items as well from subflows (?)
+ var nodecolor = "grey"
+ var branchcolor = "grey"
+ var skipped = false
+
+ var previousTools = false
+
+ return (
+
+
+ {relevantactions.map((action, index) => {
+ action.result = {}
+ if (results !== undefined) {
+ const foundResult = results.find((element) => element.action.id === action.id)
+ if (foundResult !== undefined) {
+ action.result = foundResult
+
+ action.status = foundResult.status
+ }
+ }
+
+ const lastitem = index === relevantactions.length - 1
+ if (!lastitem) {
+ if (action.app_name === "Shuffle Tools") {
+ if (action.status === "SUCCESS") {
+ branchcolor = red
+
+ // Check action.result for the actual status
+ const validate = validateJson(action.result.result)
+ if (validate.valid) {
+ if (validate.result.success === true) {
+ branchcolor = green
+ } else {
+ branchcolor = "grey"
+ }
+ }
+
+
+ } else if (action.status === "SKIPPED") {
+ branchcolor = "grey"
+ } else {
+ if (action.status === undefined) {
+ branchcolor = green
+ } else {
+ branchcolor = red
+ }
+ }
+
+ previousTools = true
+ return null
+ } else {
+ if (action.status === "SUCCESS") {
+ nodecolor = green
+ } else if (action.status === "SKIPPED") {
+ nodecolor = "grey"
+ } else {
+ if (action.status === undefined) {
+ nodecolor = green
+ } else {
+ nodecolor = red
+ }
+ }
+ }
+ } else {
+ nodecolor = "grey"
+ }
+
+ if (action.status === "SKIPPED") {
+ skipped = true
+ }
+
+ var image = ""
+ if (action.large_image !== undefined && action.large_image !== null && action.large_image !== "") {
+ image = action.large_image
+ } else {
+ if (originalWorkflow !== undefined) {
+ for (var key in originalWorkflow.actions) {
+ if (originalWorkflow.actions[key].id === action.id) {
+ image = originalWorkflow.actions[key].large_image
+ break
+ }
+ }
+
+ if (image === "") {
+ for (var key in originalWorkflow.triggers) {
+ if (originalWorkflow.triggers[key].id === action.id) {
+ image = originalWorkflow.triggers[key].large_image
+ break
+ }
+ }
+
+ }
+ }
+ }
+
+ var founderror = ""
+ if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null) {
+ const foundError = workflow.validation.errors.find((element) => element.action_id === action.id)
+ if (foundError !== undefined) {
+ founderror = foundError.error
+ nodecolor = yellow
+ branchcolor = yellow
+ }
+ }
+
+ if (skipped && !lastitem) {
+ nodecolor = "grey"
+ branchcolor = "grey"
+ }
+
+ if (previousTools) {
+ previousTools = false
+ } else {
+ 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
+ const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : ""
+
+ return (
+
+ {lastitem ?
+
+
+
+ : null}
+
+ {appgroup.length > 0 ?
+
+ {appgroup.map((subflowApp, subflowIndex) => {
+ var appimage = ""
+ if (apps !== undefined && apps !== null && apps.length > 0) {
+ for (var key in apps) {
+ const app = apps[key]
+ if (app.name === subflowApp.app_name) {
+ appimage = apps[key].large_image
+ break
+ }
+ }
+ }
+
+ return (
+
+
+
+ )
+ })}
+
+ :
+
+ {founderror.length > 0 ? founderror : ``}
+
+ } placement="top">
+
+
+ {image !== "" ?
+
+ : null}
+
+
+ }
+
+ {lastitem ? null :
+
+
+
+ }
+
+ )
+ })}
+
+
+
+ )
+}
+
+export default WorkflowValidationTimeline
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx
index 15382695..d7ee12b6 100755
--- a/frontend/src/views/Admin.jsx
+++ b/frontend/src/views/Admin.jsx
@@ -5385,13 +5385,17 @@ If you're interested, please let me know a time that works for you, or set up a
+
{validIcon}
style={{ minWidth: 65, maxWidth: 65, }}
onClick={() => {
+ if (data.validation === null || data.validation === undefined) {
+ return
+ }
+
if (data.validation.workflow_id === undefined || data.validation.workflow_id === null || data.validation.workflow_id.length === 0) {
toast.warn("No workflow runs found for this auth yet. Check back later.")
return
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index 48fd2ff2..6ead5cf0 100755
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -139,6 +139,7 @@ import Draggable from "react-draggable";
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
+import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import { GetParsedPaths, internalIds, } from "../views/Apps.jsx";
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
@@ -319,6 +320,7 @@ export function SetJsonDotnotation(jsonInput, inputKey) {
export const green = "#86c142";
export const yellow = "#FECC00";
+export const red = "red";
export function removeParam(key, sourceURL) {
if (sourceURL === undefined) {
@@ -1641,7 +1643,6 @@ const releaseToConnectLabel = "Release to Connect"
setExecutionData(responseJson)
} else {
if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING" || responseJson.status === "FINISHED") {
- console.log("DONE!")
stop()
}
@@ -6732,7 +6733,6 @@ const releaseToConnectLabel = "Release to Connect"
const parentlabel = parentNode.data("label").toLowerCase().replace(" ", "_")
const parentname = parentNode.data("app_name").toLowerCase().replace(" ", "_")
if (!parentlabel.startsWith(parentname)+"_") {
- console.log("Return 1")
return
}
@@ -7135,7 +7135,6 @@ const releaseToConnectLabel = "Release to Connect"
// Find how many executions it has
var executions = 0
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
- console.log("Matches: ", matchingExecutions.length)
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
const decoratorNode = {
position: {
@@ -10204,6 +10203,7 @@ const releaseToConnectLabel = "Release to Connect"
// Starts on current node and climbs UP the tree to the root object.
// Sends back everything in it's path
+ // FIXME: Use the GetParentNodes in WorkflowValidationTimeline.jsx instead
const getParents = (action) => {
if (action === undefined || action === null) {
return []
@@ -18720,6 +18720,7 @@ const releaseToConnectLabel = "Release to Connect"
: null}
+
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
@@ -18831,12 +18832,31 @@ const releaseToConnectLabel = "Release to Connect"
{new Date(executionData.completed_at * 1000).toLocaleString("en-GB")}
+
) : null}
+
+ {executionData.workflow !== undefined && executionData.workflow !== null && executionData.status !== "EXECUTING" ?
+
+
+
+ : null}
+
- {executionData.execution_argument !== undefined &&
- executionData.execution_argument.length > 0
+
+ {executionData.execution_argument !== undefined && executionData.execution_argument !== null &&
+ executionData.execution_argument.length > 1
? parsedExecutionArgument()
- : null}
+ :
+ null}
+
+
{executionData.results !== undefined &&
executionData.results !== null &&
executionData.results.length > 1 &&
@@ -19538,7 +19559,7 @@ const releaseToConnectLabel = "Release to Connect"
if (stringjson.includes("\n") && !stringjson.includes("\n")) {
return "Looks like you have a newline problem. Consider using the | replace: '\n', '\\n' }} filter in Liquid."
} else {
- return "The result looks like it should be JSON, but is invalid. Look for potential"
+ return "The result looks like it should be JSON, but is invalid. Look for potential single quotes instead of double quotes, missing commas or newlines"
}
}
}
diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go
index 9f27aec8..d1a09ff7 100755
--- a/functions/onprem/orborus/orborus.go
+++ b/functions/onprem/orborus/orborus.go
@@ -178,6 +178,51 @@ func getThisContainerId() {
}
func cleanupExistingNodes(ctx context.Context) error {
+
+ if isKubernetes == "true" {
+ if kubernetesNamespace == "" {
+ kubernetesNamespace = "default"
+ }
+
+ clientset, _, err := shuffle.GetKubernetesClient()
+ if err != nil {
+ log.Printf("[ERROR] Error getting kubernetes client:", err)
+ return err
+ }
+
+ // Delete all pods
+ pods, err := clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
+ if err != nil {
+ log.Printf("[ERROR] Failed listing pods: %s", err)
+ return err
+ }
+
+ for _, pod := range pods.Items {
+ err := clientset.CoreV1().Pods(kubernetesNamespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{})
+ if err != nil {
+ log.Printf("[ERROR] Failed deleting pod %s: %s", pod.Name, err)
+ }
+ }
+
+ // Delete all services
+ services, err := clientset.CoreV1().Services(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
+ if err != nil {
+ log.Printf("[ERROR] Failed listing services: %s", err)
+ return err
+ }
+
+ for _, service := range services.Items {
+ err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{})
+ if err != nil {
+ log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err)
+ }
+ }
+
+ log.Printf("[INFO] Cleaned up all pods and services in namespace %s", kubernetesNamespace)
+ return nil
+ }
+
+
serviceListOptions := types.ServiceListOptions{}
services, err := dockercli.ServiceList(
context.Background(),
@@ -1662,11 +1707,12 @@ func main() {
if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" {
if isKubernetes != "true" {
checkSwarmService(ctx)
- log.Printf("[DEBUG] Cleaning up containers from previous run")
- cleanupExistingNodes(ctx)
- time.Sleep(time.Duration(5) * time.Second)
}
+ log.Printf("[DEBUG] Cleaning up containers from previous run")
+ cleanupExistingNodes(ctx)
+ time.Sleep(time.Duration(5) * time.Second)
+
log.Printf("[DEBUG] Deploying worker image %s to swarm", workerImage)
runString := "Run: \"docker service ls\" for more info"