diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index b43db79b..e400b547 100755 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -19,7 +19,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.4.19 + github.com/shuffle/shuffle-shared v0.4.35 golang.org/x/crypto v0.9.0 google.golang.org/api v0.125.0 google.golang.org/appengine v1.6.7 diff --git a/backend/go-app/main.go b/backend/go-app/main.go index c3363f00..67dcdc7a 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5841,6 +5841,8 @@ func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte("OK")) } + + func initHandlers() { var err error ctx := context.Background() @@ -5890,6 +5892,7 @@ func initHandlers() { r := mux.NewRouter() r.HandleFunc("/api/v1/_ah/health", shuffle.HealthCheckHandler) + r.HandleFunc("/api/v1/health", shuffle.RunOpsHealthCheck).Methods("GET", "OPTIONS") // Make user related locations // Fix user changes with org @@ -5998,7 +6001,7 @@ func initHandlers() { // New for recommendations in Shuffle r.HandleFunc("/api/v1/recommendations/get_actions", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/recommendations/modify", shuffle.HandleRecommendationAction).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/workflows/{key}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS") + // r.HandleFunc("/api/v1/workflows/{key}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS") // Triggers r.HandleFunc("/api/v1/hooks/new", shuffle.HandleNewHook).Methods("POST", "OPTIONS") @@ -6054,9 +6057,16 @@ func initHandlers() { r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/orgs/{orgId}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS") + + r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/datastore/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") + // Docker orborus specific - downloads an image r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") @@ -6098,6 +6108,7 @@ func initHandlers() { // Had to move away from mux, which means Method is fucked up right now. func main() { initHandlers() + go shuffle.InitOpsWorkflow() hostname, err := os.Hostname() if err != nil { hostname = "MISSING" diff --git a/docker-compose.yml b/docker-compose.yml index daf8f7a0..2eb61619 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: frontend: - image: ghcr.io/shuffle/shuffle-frontend:nightly + image: ghcr.io/shuffle/shuffle-frontend:latest container_name: shuffle-frontend hostname: shuffle-frontend ports: diff --git a/frontend/package.json b/frontend/package.json index 4a4e9808..56457972 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,8 +13,9 @@ "@mui/material": "^5.14.0", "@mui/styles": "^5.14.0", "@mui/x-data-grid": "^5.17.11", - "@uiw/codemirror-themes": "^4.21.7", - "@uiw/react-codemirror": "^4.21.7", + "@mui/x-date-pickers": "^6.11.1", + "@uiw/codemirror-themes": "^4.21.9", + "@uiw/react-codemirror": "^4.21.9", "@use-it/interval": "^1.0.0", "algoliasearch": "^4.13.1", "calculate-size": "^1.1.1", @@ -24,6 +25,7 @@ "cytoscape-edgehandles": "^3.6.0", "cytoscape-node-html-label": "^1.1.5", "d3": "^7.1.1", + "dayjs": "^1.11.9", "dotenv": "^6.1.0", "downshift": "^3.3.5", "github-markdown-css": "^3.0.1", @@ -36,11 +38,11 @@ "jss-nested": "^6.0.1", "jss-props-sort": "^6.0.0", "jss-vendor-prefixer": "^8.0.1", - "material-ui-chip-input": "^2.0.0-beta.2", "md5-file": "^4.0.0", "mdbreact": "^4.21.1", "mime": "^3.0.0", "moment": "^2.29.1", + "mui-chips-input": "^2.1.3", "mui-nested-menu": "^3.2.1", "process": "^0.11.10", "react": "^18.2.0", @@ -67,6 +69,7 @@ "react-router": "^6.14.1", "react-router-dom": "^6.14.1", "react-scripts": "^5.0.1", + "react-toastify": "^9.1.3", "reaviz": "^14.9.4", "remark-gfm": "^3.0.1", "search-insights": "^2.2.1", @@ -108,6 +111,6 @@ "react-16": "npm:react@16.13.1", "react-dom-16": "npm:react-dom@16.13.1", "react-error-overlay": "6.0.9", - "webpack": "^4.46.0" + "webpack": "^5.88.2" } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index abeb8ab3..28b665b0 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -167,7 +167,7 @@ const App = (message, props) => { const includedData =
`), @@ -520,7 +520,7 @@ const AppFramework = (props) => { const scale = size === undefined ? 1 : size > 5 ? 3 : size - const alert = useAlert() + //const alert = useAlert() const handleLoadNextSuggestion = (frameworkData) => { @@ -783,16 +783,16 @@ const AppFramework = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed updating: " + responseJson.reason) + toast("Failed updating: " + responseJson.reason) } else { - alert.error("Failed to update framework for your org.") + toast("Failed to update framework for your org.") } } else { - alert.info("Updated usecase.") + toast("Updated usecase.") } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); //setFrameworkLoaded(true) }) } @@ -815,13 +815,13 @@ const AppFramework = (props) => { }) .then((responseJson) => { if (responseJson.success === false) { - alert.error("Failed to activate the app") + toast("Failed to activate the app") } else { - //alert.success("App activated for your organization! Refresh the page to use the app.") + //toast("App activated for your organization! Refresh the page to use the app.") } }) .catch(error => { - //alert.error(error.toString()) + //toast(error.toString()) console.log("Activate app error: ", error.toString()) }); } @@ -851,9 +851,9 @@ const AppFramework = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed updating: " + responseJson.reason) + toast("Failed updating: " + responseJson.reason) } else { - alert.error("Failed to update framework for your org.") + toast("Failed to update framework for your org.") } } @@ -862,7 +862,7 @@ const AppFramework = (props) => { //setFrameworkData(responseJson) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); //setFrameworkLoaded(true) }) } @@ -1856,6 +1856,7 @@ const AppFramework = (props) => { //autounselectify={true} var usecasediff = -100 const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color + console.log("Background: ", bgColor) return (
@@ -1992,11 +1993,11 @@ const AppFramework = (props) => { { Object.getOwnPropertyNames(discoveryData).length > 0 ? - + {paperTitle.length > 0 ? - {paperTitle} + {paperTitle.replace("_", " ", -1)} @@ -2126,7 +2127,7 @@ const AppFramework = (props) => { ? - Click an app below to select it + Search to find your app : @@ -2161,7 +2162,7 @@ const AppFramework = (props) => { elements={elements} minZoom={0.35} maxZoom={2.00} - style={{width: 560*scale, height: 560*scale, backgroundColor: "transparent", margin: "auto",}} + style={{width: 560*scale, height: 560*scale, backgroundColor: theme.palette.backgroundColor, margin: "auto",}} stylesheet={frameworkStyle} boxSelectionEnabled={false} panningEnabled={false} diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 1e6ab296..57369deb 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -74,7 +74,7 @@ const AppGrid = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx index 5defd0f0..090dc620 100644 --- a/frontend/src/components/Appsearch.jsx +++ b/frontend/src/components/Appsearch.jsx @@ -1,23 +1,34 @@ import React, { useState, useEffect } from 'react'; -import theme from '../theme.jsx'; import ReactGA from 'react-ga4'; +import theme from '../theme.jsx'; import {Link} from 'react-router-dom'; -import { useAlert } from "react-alert"; import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material'; +import { toast } from 'react-toastify'; //import algoliasearch from 'algoliasearch/lite'; import algoliasearch from 'algoliasearch'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; -import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@mui/material'; +import { + Grid, + Paper, + TextField, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip +} from '@mui/material'; + import aa from 'search-insights' const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const Appsearch = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList} = props const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - const alert = useAlert(); + //const alert = useAlert(); const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs + //const theme = useTheme(); //const [apps, setApps] = React.useState([]); //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); @@ -57,55 +68,18 @@ const Appsearch = props => { if (response.status !== 200) { console.log("Status not 200 for set creator :O!"); } - alert.success("Sucessfully updated specialzed app.") + toast("Sucessfully updated specialzed app.") return response.json(); }) .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed updating user: " + responseJson.reason); + toast("Failed updating user: " + responseJson.reason); } }) .catch((error) => { console.log(error); }); }; - const submitContact = (email, message) => { - const data = { - "firstname": "", - "lastname": "", - "title": "", - "companyname": "", - "email": email, - "phone": "", - "message": message, - } - - const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." - - fetch(globalUrl+"/api/v1/contact", { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - .then(response => response.json()) - .then(response => { - if (response.success === true) { - setFormMessage(response.reason) - //alert.info("Thanks for submitting!") - } else { - setFormMessage(errorMessage) - } - - setFormMail("") - setMessage("") - }) - .catch(error => { - setFormMessage(errorMessage) - console.log(error) - }); - } // value={currentRefinement} const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { diff --git a/frontend/src/components/AuthenticationItem.jsx b/frontend/src/components/AuthenticationItem.jsx index 06329a35..593eacb4 100644 --- a/frontend/src/components/AuthenticationItem.jsx +++ b/frontend/src/components/AuthenticationItem.jsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from "react"; import theme from '../theme.jsx'; -import { useAlert } from "react-alert"; +import { toast } from 'react-toastify'; + import { Tooltip, IconButton, @@ -34,7 +35,7 @@ const AuthenticationItem = (props) => { const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false); const [authenticationFields, setAuthenticationFields] = React.useState([]); - const alert = useAlert(); + //const alert = useAlert(); var bgColor = "#27292d"; if (index % 2 === 0) { bgColor = "#1f2023"; @@ -63,7 +64,7 @@ const AuthenticationItem = (props) => { } const deleteAuthentication = (data) => { - alert.info("Deleting auth " + data.label); + toast("Deleting auth " + data.label); // Just use this one? const url = globalUrl + "/api/v1/apps/authentication/" + data.id; @@ -79,13 +80,13 @@ const AuthenticationItem = (props) => { response.json().then((responseJson) => { console.log("RESP: ", responseJson); if (responseJson["success"] === false) { - alert.error("Failed deleting auth"); + toast("Failed deleting auth"); } else { // Need to wait because query in ES is too fast setTimeout(() => { getAppAuthentication(); }, 1000); - //alert.success("Successfully deleted authentication!") + //toast("Successfully deleted authentication!") } }) ) @@ -115,9 +116,9 @@ const AuthenticationItem = (props) => { .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - alert.error("Failed overwriting appauth in workflows"); + toast("Failed overwriting appauth in workflows"); } else { - alert.success("Successfully updated auth everywhere!"); + toast("Successfully updated auth everywhere!"); //setSelectedUserModalOpen(false); setTimeout(() => { getAppAuthentication(); @@ -126,7 +127,7 @@ const AuthenticationItem = (props) => { }) ) .catch((error) => { - alert.error("Err: " + error.toString()); + toast("Err: " + error.toString()); }); }; diff --git a/frontend/src/components/AuthenticationNormal.jsx b/frontend/src/components/AuthenticationNormal.jsx index a0d6acab..1140de10 100644 --- a/frontend/src/components/AuthenticationNormal.jsx +++ b/frontend/src/components/AuthenticationNormal.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import theme from '../theme.jsx'; import { v4 as uuidv4 } from "uuid"; - +import { toast } from 'react-toastify'; import { Button, @@ -54,7 +54,7 @@ const AuthenticationData = (props) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to set app auth: " + responseJson.reason); + toast("Failed to set app auth: " + responseJson.reason); } else { if (getAppAuthentication !== undefined) { getAppAuthentication() @@ -65,11 +65,11 @@ const AuthenticationData = (props) => { } // Needs a refresh with the new authentication.. - //alert.success("Successfully saved new app auth") + //toast("Successfully saved new app auth") } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("New auth error: ", error.toString()); }); } @@ -146,7 +146,7 @@ const AuthenticationData = (props) => { selectedApp.authentication.parameters[key].name ] = "false"; } else { - alert.info( + toast( "Field " + selectedApp.authentication.parameters[key].name + " can't be empty" diff --git a/frontend/src/components/AuthenticationWindow.jsx b/frontend/src/components/AuthenticationWindow.jsx index 47dd9b4e..d7bcc55a 100755 --- a/frontend/src/components/AuthenticationWindow.jsx +++ b/frontend/src/components/AuthenticationWindow.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import theme from '../theme.jsx'; import { v4 as uuidv4 } from "uuid"; -import { useAlert } from "react-alert"; +import { toast } from 'react-toastify'; import { Divider, @@ -44,7 +44,7 @@ const AuthenticationData = (props) => { authFieldsOnly, } = props - const alert = useAlert() + //const alert = useAlert() let navigate = useNavigate(); const [submitSuccessful, setSubmitSuccessful] = useState(false) const [authenticationOption, setAuthenticationOptions] = React.useState({ @@ -99,9 +99,9 @@ const AuthenticationData = (props) => { .then((responseJson) => { if (!responseJson.success) { if (responseJson.reason === undefined) { - alert.error("Failed to set app auth. Are you logged in?") + toast("Failed to set app auth. Are you logged in?") } else { - alert.error("Failed to set app auth: " + responseJson.reason); + toast("Failed to set app auth: " + responseJson.reason); } } else { setSubmitSuccessful(true) @@ -115,7 +115,7 @@ const AuthenticationData = (props) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("New auth error: ", error.toString()); }); }; @@ -175,7 +175,7 @@ const AuthenticationData = (props) => { selectedApp.authentication.parameters[paramkey].name ] = "false"; } else { - alert.info( + toast( "Field " + selectedApp.authentication.parameters[paramkey].name + " can't be empty" diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index 4b44f5c1..e565a013 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -11,11 +11,11 @@ import { Card, } from "@mui/material"; -import { useAlert } from "react-alert"; +//import { useAlert const Branding = (props) => { const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; - const alert = useAlert(); + //const alert = useAlert(); const [publishingInfo, setPublishingInfo] = useState(""); // Should enable / disable org branding diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx index 62394423..64f071f7 100644 --- a/frontend/src/components/CacheView.jsx +++ b/frontend/src/components/CacheView.jsx @@ -1,5 +1,7 @@ import React, { useState, useEffect } from "react"; import theme from "../theme.jsx"; +import { toast } from 'react-toastify'; + import { Tooltip, Divider, @@ -16,7 +18,7 @@ import { DialogTitle, DialogActions, } from "@mui/material"; -import { useAlert } from "react-alert"; +//import { useAlert import { Edit as EditIcon, @@ -72,7 +74,7 @@ const CacheView = (props) => { const [dataValue, setDataValue] = React.useState({}); const [editCache, setEditCache] = React.useState(false); const [show, setShow] = useState({}); - const alert = useAlert(); + //const alert = useAlert(); useEffect(() => { listOrgCache(orgId); console.log("orgid", orgId); @@ -105,7 +107,7 @@ const CacheView = (props) => { } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -148,7 +150,7 @@ const CacheView = (props) => { const deleteCache = (orgId, key) => { - alert.info("Attempting to delete Cache"); + toast("Attempting to delete Cache"); fetch(globalUrl + `/api/v1/orgs/${orgId}/cache/${key}`, { method: "DELETE", headers: { @@ -158,16 +160,16 @@ const CacheView = (props) => { }) .then((response) => { if (response.status === 200) { - alert.success("Successfully deleted Cache"); + toast("Successfully deleted Cache"); setTimeout(() => { listOrgCache(orgId); }, 1000); } else { - alert.error("Failed deleting Cache. Does it still exist?"); + toast("Failed deleting Cache. Does it still exist?"); } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -197,12 +199,12 @@ const CacheView = (props) => { }) .then((responseJson) => { setAddCache(responseJson); - alert.success("Cache Edited Successfully!"); + toast("Cache Edited Successfully!"); listOrgCache(orgId); setModalOpen(false); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -231,12 +233,12 @@ const CacheView = (props) => { }) .then((responseJson) => { setAddCache(responseJson); - alert.success("New Cache Added Successfully!"); + toast("New Cache Added Successfully!"); listOrgCache(orgId); setModalOpen(false); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index c24703fe..0e80540c 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import { useInterval } from "react-powerhooks"; +import { toast } from 'react-toastify'; import { InputAdornment, @@ -112,9 +113,9 @@ const ConfigureWorkflow = (props) => { }) .then((response) => { if (response.status === 200) { - //alert.success("Successfully GOT app "+appId) + //toast("Successfully GOT app "+appId) } else { - alert.error("Failed getting app"); + toast("Failed getting app"); } return response.json(); @@ -128,7 +129,7 @@ const ConfigureWorkflow = (props) => { } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -566,7 +567,7 @@ const ConfigureWorkflow = (props) => { .then((response) => { if (response.status !== 200) { //window.location.pathname = "/search" - //alert.error("Failed to find this app. Is it public?") + //toast("Failed to find this app. Is it public?") } return response.json(); @@ -574,16 +575,16 @@ const ConfigureWorkflow = (props) => { .then((responseJson) => { if (responseJson.success === false) { if (responseJson.reason !== undefined) { - alert.error("Failed to activate the app: "+responseJson.reason); + toast("Failed to activate the app: "+responseJson.reason); } else { - alert.error("Failed to activate the app"); + toast("Failed to activate the app"); } } else { - alert.success("App activated for your organization!"); + toast("App activated for your organization!"); } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -693,7 +694,7 @@ const ConfigureWorkflow = (props) => { if (workflow.actions !== null) { //console.log(workflow.actions) - alert.info("Setting action to version "+action.update_version) + toast("Setting action to version "+action.update_version) for (let [key,keyval] in Object.entries(workflow.actions)) { if (workflow.actions[key].app_name === action.app_name && workflow.actions[key].app_version === action.app_version) { workflow.actions[key].app_version = action.update_version @@ -851,7 +852,7 @@ const ConfigureWorkflow = (props) => { console.log("NAVIGATOR: ", navigator); const clipboard = navigator.clipboard; if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)"); + toast("Can only copy over HTTPS (port 3443)"); return; } @@ -864,7 +865,7 @@ const ConfigureWorkflow = (props) => { /* Copy the text inside the text field */ document.execCommand("copy"); - alert.success("Copied Webhook URL"); + toast("Copied Webhook URL"); } }}> {webhook.description} diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx index 9d23b439..712a2197 100644 --- a/frontend/src/components/CreatorGrid.jsx +++ b/frontend/src/components/CreatorGrid.jsx @@ -81,7 +81,7 @@ const CreatorGrid = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx index fa194a25..947a2d76 100644 --- a/frontend/src/components/DocsGrid.jsx +++ b/frontend/src/components/DocsGrid.jsx @@ -69,7 +69,7 @@ const DocsGrid = props => { .then(response => { if (response.success === true) { setFormMessage(response.reason) - //alert.info("Thanks for submitting!") + //toast("Thanks for submitting!") } else { setFormMessage(errorMessage) } diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 5e8b0493..9971ff3d 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -1,8 +1,10 @@ import React, { useEffect, useContext } from "react"; import theme from '../theme.jsx'; import { isMobile } from "react-device-detect" -import ChipInput from "material-ui-chip-input"; +import { MuiChipsInput } from "mui-chips-input"; import UsecaseSearch from "../components/UsecaseSearch.jsx" +import WorkflowGrid from "../components/WorkflowGrid.jsx" +import dayjs from 'dayjs'; import { Badge, @@ -25,6 +27,7 @@ import { Typography, Zoom, CircularProgress, + Drawer, Dialog, DialogTitle, DialogActions, @@ -39,6 +42,13 @@ import { } from "@mui/material"; +import { + DatePicker, + LocalizationProvider, +} from '@mui/x-date-pickers' + +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' + import { ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, @@ -49,16 +59,19 @@ import { const EditWorkflow = (props) => { const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props + const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove + const [submitLoading, setSubmitLoading] = React.useState(false); const [showMoreClicked, setShowMoreClicked] = React.useState(false); - const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) - const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove + const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) + const [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : []) + const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "") + const [selectedUsecases, setSelectedUsecases] = React.useState(workflow.usecase_ids !== undefined && workflow.usecase_ids !== null ? JSON.parse(JSON.stringify(workflow.usecase_ids)) : []); const [foundWorkflowId, setFoundWorkflowId] = React.useState("") const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "") - const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "") - + const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day')) // Gets the generated workflow const getGeneratedWorkflow = (workflow_id) => { @@ -114,7 +127,7 @@ const EditWorkflow = (props) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Get workflow error: ", error.toString()); }) } @@ -136,67 +149,67 @@ const EditWorkflow = (props) => { var total_count = 0 return ( - { setModalOpen(false); }} PaperProps={{ style: { - backgroundColor: theme.palette.surfaceColor, color: "white", - minWidth: isMobile ? "90%" : 550, - maxWidth: isMobile ? "90%" : 550, - minHeight: 400, + minWidth: isMobile ? "90%" : 650, + maxWidth: isMobile ? "90%" : 650, + minHeight: 400, + paddingTop: 25, //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, }, }} > -
+
-
- - {newWorkflow ? "New" : "Editing"} workflow - - {newWorkflow === true ? null : -
- - - - - -
- } +
+ + {newWorkflow ? "New" : "Editing"} workflow + + {newWorkflow === true ? null : +
+ + + + +
- - 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 ? -
- - - -
- : null} + } +
+ + 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 ? +
+ + + +
+ : null}
{/*newWorkflow === true ?
@@ -211,12 +224,12 @@ const EditWorkflow = (props) => {
- -
+ +
{ - setName(event.target.value) - }} + onChange={(event) => { + setName(event.target.value) + }} InputProps={{ style: { color: "white", @@ -231,27 +244,29 @@ const EditWorkflow = (props) => { autoFocus fullWidth /> - { - setDescription(event.target.value) - }} - InputProps={{ - style: { - color: "white", - }, - }} - maxRows={4} - color="primary" - defaultValue={innerWorkflow.description} - placeholder="Description" - multiline - label="Description" - margin="dense" - fullWidth - /> +
+ { + setDescription(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + maxRows={4} + color="primary" + defaultValue={innerWorkflow.description} + placeholder="Description" + multiline + label="Description" + margin="dense" + fullWidth + /> +
- { color="primary" fullWidth value={newWorkflowTags} + onChange={(chip) => { + console.log("Chip: ", chip) + //newWorkflowTags.push(chip); + setNewWorkflowTags(chip); + }} onAdd={(chip) => { newWorkflowTags.push(chip); setNewWorkflowTags(newWorkflowTags); }} onDelete={(chip, index) => { + console.log("Deleting: ", chip, index) newWorkflowTags.splice(index, 1); setNewWorkflowTags(newWorkflowTags); + setUpdate(Math.random()); }} /> {usecases !== null && usecases !== undefined && usecases.length > 0 ? @@ -328,26 +350,41 @@ const EditWorkflow = (props) => { {showMoreClicked === true ? +
+ + Status + { + console.log("Data: ", e.target.value) + + innerWorkflow.workflow_type = e.target.value + setInnerWorkflow(innerWorkflow) + }} + > + } label="Test" /> + } label="Production" /> - - Status - { - console.log("Data: ", e.target.value) - - innerWorkflow.workflow_type = e.target.value - setInnerWorkflow(innerWorkflow) + + + + - } label="Test" /> - } label="Production" /> - - - + value={dueDate} + label="Due Date" + format="YYYY-MM-DD" + onChange={(newValue) => { + setDueDate(newValue) + }} + /> + +
@@ -430,38 +467,28 @@ const EditWorkflow = (props) => { : null} - { - setShowMoreClicked(!showMoreClicked); - }} - > - {showMoreClicked ? : } - - -
- {/*newWorkflow === true ? -
- -
- : null*/} + { + setShowMoreClicked(!showMoreClicked); + }} + > + {showMoreClicked ? : } + + +
+ - + + + + {newWorkflow === true && name.length > 5 ? +
+ +
+ : null} -
+ ) } diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index cd7a004b..75553621 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import { toast } from 'react-toastify'; import { IconButton, @@ -29,7 +30,7 @@ import { Add as AddIcon, } from "@mui/icons-material"; -import { useAlert } from "react-alert"; +//import { useAlert import Dropzone from "../components/Dropzone.jsx"; import CodeEditor from "../components/ShuffleCodeEditor.jsx"; import theme from "../theme.jsx"; @@ -45,7 +46,7 @@ const Files = (props) => { const [openEditor, setOpenEditor] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false); - const alert = useAlert(); + //const alert = useAlert(); const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log"] var upload = ""; @@ -113,7 +114,7 @@ const Files = (props) => { } }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -139,12 +140,12 @@ const Files = (props) => { }) .then((responseJson) => { if (responseJson.success) { - alert.info("Successfully deleted file " + file.name); + toast("Successfully deleted file " + file.name); } else if ( responseJson.reason !== undefined && responseJson.reason !== null ) { - alert.error("Failed to delete file: " + responseJson.reason); + toast("Failed to delete file: " + responseJson.reason); } setTimeout(() => { getFiles(); @@ -153,7 +154,7 @@ const Files = (props) => { console.log(responseJson); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -178,7 +179,7 @@ const Files = (props) => { // console.log("respdata type ->", typeof(respdata)); if (respdata.length === 0) { - alert.error("Failed getting file. Is it deleted?"); + toast("Failed getting file. Is it deleted?"); return; } return respdata @@ -189,7 +190,7 @@ const Files = (props) => { //console.log("filecontent state ",fileContent); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -210,7 +211,7 @@ const Files = (props) => { }) .then((respdata) => { if (respdata.length === 0) { - alert.error("Failed getting file. Is it deleted?"); + toast("Failed getting file. Is it deleted?"); return; } @@ -249,7 +250,7 @@ const Files = (props) => { //setSchedules(responseJson) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -291,11 +292,11 @@ const Files = (props) => { if (responseJson.success === true) { handleFileUpload(responseJson.id, file); } else { - alert.error("Failed to upload file ", filename); + toast("Failed to upload file ", filename); } }) .catch((error) => { - alert.error("Failed to upload file ", filename); + toast("Failed to upload file ", filename); console.log(error.toString()); }); }; @@ -312,7 +313,7 @@ const Files = (props) => { .then((response) => { if (response.status !== 200 && response.status !== 201) { console.log("Status not 200 for apps :O!"); - alert.error("File was created, but failed to upload."); + toast("File was created, but failed to upload."); return; } @@ -323,7 +324,7 @@ const Files = (props) => { //setFiles(responseJson) }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -364,7 +365,7 @@ const Files = (props) => { const files = isDropzone ? e.dataTransfer.files : e.target.files; //const reader = new FileReader(); - //alert.info("Starting fileupload") + //toast("Starting fileupload") uploadFiles(files); }; @@ -681,7 +682,7 @@ const Files = (props) => { @@ -742,7 +743,7 @@ const Files = (props) => { ) { const clipboard = navigator.clipboard; if (clipboard === undefined) { - alert.error( + toast( "Can only copy over HTTPS (port 3443)" ); return; @@ -758,7 +759,7 @@ const Files = (props) => { /* Copy the text inside the text field */ document.execCommand("copy"); - alert.info(file.id + " copied to clipboard"); + toast(file.id + " copied to clipboard"); } }} > diff --git a/frontend/src/components/FooterNew.js b/frontend/src/components/FooterNew.js deleted file mode 100755 index 482c45b7..00000000 --- a/frontend/src/components/FooterNew.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from "react"; - -//import List from '@material-ui/core/List'; -//import ListItem from '@material-ui/core/ListItem'; - -//borderTop: "1px solid #385F71" -const FooterStyle = { - right: "0", - left: "0", - bottom: "0", - height: "130px", - backgroundColor: "rgba(15, 14, 31, 1)", -}; - -const FooterInfo = { - maxWidth: "1150px", - minWidth: "768px", - textAlign: "center", - margin: "auto", -}; - -const hrefStyle = { - color: "#bdbdbd", - textDecoration: "none", -}; - -const Footer = (props) => { - return ( -
-
- -
-
- ); -}; - -const Box = (props) => { - return ( - - ); -}; - -export default Footer; diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 6a1f2436..6a680b5e 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -1,8 +1,9 @@ import React, {useState} from 'react'; -import {BrowserView, MobileView} from "react-device-detect"; +import { toast } from 'react-toastify'; import theme from '../theme.jsx'; +import {BrowserView, MobileView} from "react-device-detect"; -import {Link} from 'react-router-dom'; +import { useNavigate, Link } from "react-router-dom"; import ReactGA from 'react-ga4'; import { @@ -21,7 +22,7 @@ import { IconButton, Divider, LinearProgress, -} from '@mui/material'; +} from '@mui/material' import { MeetingRoom as MeetingRoomIcon, @@ -29,16 +30,20 @@ import { Settings as SettingsIcon, Notifications as NotificationsIcon, Home as HomeIcon, - Polymer as PolymerIcon, Apps as AppsIcon, Description as DescriptionIcon, EmojiObjects as EmojiObjectsIcon, Business as BusinessIcon, - Analytics as AnalyticsIcon, - Lightbulb as LightbulbIcon, + + Polyline as PolylineIcon, } from '@mui/icons-material'; -import { useAlert } from "react-alert"; +import { + Analytics as AnalyticsIcon, + Lightbulb as LightbulbIcon, +} from "@mui/icons-material"; + +//import { useAlert import SearchField from '../components/Searchfield.jsx' const hoverColor = "#f85a3e" @@ -46,7 +51,8 @@ const hoverOutColor = "#e8eaf6" const Header = props => { const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, userdata, serverside, } = props; - const alert = useAlert() + //const theme = useTheme(); + //const alert = useAlert() const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); @@ -57,6 +63,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho const [anchorEl, setAnchorEl] = React.useState(null); const [anchorElAvatar, setAnchorElAvatar] = React.useState(null); const [subAnchorEl, setSubAnchorEl] = React.useState(null); + let navigate = useNavigate(); const handleClick = (event) => { @@ -95,7 +102,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho setNotifications([]) handleClose() } else { - alert.error("Failed dismissing notifications. Please try again later.") + toast("Failed dismissing notifications. Please try again later.") } }) .catch(error => { @@ -125,7 +132,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho console.log("NEW NOTIFICATIONS: ", newNotifications) setNotifications(newNotifications) } else { - alert.error("Failed dismissing notification. Please try again later.") + toast("Failed dismissing notification. Please try again later.") } }) .catch(error => { @@ -400,9 +407,9 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho setTimeout(() => { window.location.reload() }, 2000) - alert.success("Successfully changed active organization - refreshing!") + toast("Successfully changed active organization - refreshing!") } else { - alert.error("Failed changing org: ", responseJson.reason) + toast("Failed changing org: ", responseJson.reason) } }) .catch(error => { @@ -514,6 +521,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho } // Handle top bar or something + const defaultTop = 7 const loginTextBrowser = !isLoggedIn ?
@@ -664,7 +672,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
: -
+
@@ -686,9 +694,9 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
{/* - + */} - Workflows + Workflows
@@ -698,17 +706,24 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho {/* */} - Apps + Apps
+ {/* + + +
Dashboard
+ +
+ */}
{/* */} - Docs + Docs
@@ -782,14 +797,13 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho : null*/} - - {userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null : { */}
{datafield} - {shufflecode} + {/*shufflecode*/} {showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && @@ -2587,7 +2572,6 @@ const ParsedAction = (props) => { labelId="action-autocompleter" SelectDisplayProps={{ style: { - marginLeft: 10, }, }} onClose={() => { @@ -2680,6 +2664,62 @@ const ParsedAction = (props) => { return null; }; + + const ActionSelectOption = (actionprops) => { + const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; + const [hover, setHover] = React.useState(false); + + console.log("Extra desc: ", extraDescription) + + return ( + +
setHover(true)} onMouseLeave={() => setHover(false)} + onClick={() => { + //setSelectedAction(actionprops) + //setShowActionList(false) + //setUpdate(Math.random()) + // + if (data !== undefined && data !== null) { + setNewSelectedAction({ + target: { + value: data.name + } + }); + } + }} + > +
+ + {useIcon} + + {newActionname} +
+ {extraDescription.length > 0 ? + + {extraDescription} + + : null} +
+
+ ) + } + //const CustomPopper = function (props) { // const classes = useStyles() // return @@ -2906,7 +2946,6 @@ const ParsedAction = (props) => { }} SelectDisplayProps={{ style: { - marginLeft: 10, }, }} > @@ -2941,6 +2980,7 @@ const ParsedAction = (props) => {
Name { } SelectDisplayProps={{ style: { - marginLeft: 10, - maxWidth: 250, + maxWidth: 250, }, }} fullWidth @@ -3329,7 +3368,6 @@ const ParsedAction = (props) => { } SelectDisplayProps={{ style: { - marginLeft: 10, }, }} fullWidth @@ -3375,9 +3413,9 @@ const ParsedAction = (props) => {
Execution variable (optional) -
{ @@ -2246,7 +2425,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user if (copyText !== null && copyText !== undefined) { const clipboard = navigator.clipboard; if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)"); + toast("Can only copy over HTTPS (port 3443)"); return; } @@ -2260,13 +2439,12 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user /* Copy the text inside the text field */ document.execCommand("copy"); - alert.info(org_id + " copied to clipboard"); + toast(org_id + " copied to clipboard"); } }} > -
{selectedOrganization.defaults !== undefined && selectedOrganization.defaults.documentation_reference !== undefined && selectedOrganization.defaults.documentation_reference !== null && selectedOrganization.defaults.documentation_reference.includes("http") ? -
-
: null} {selectedOrganization.name.length > 0 ? ( @@ -2341,7 +2517,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user - Billing + Billing (Beta) /> /> - - Usage - - /> - {/* - - Notifications - - /> - */} , + icon: , }; return ( - -
- -
+ + ); })} @@ -2614,6 +2774,8 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user adminTab={adminTab} globalUrl={globalUrl} checkLogin={checkLogin} + setAdminTab={setAdminTab} + setCurTab={setCurTab} /> : adminTab === 3 ? @@ -2635,6 +2798,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user globalUrl={globalUrl} handleGetOrg={handleGetOrg} selectedOrganization={selectedOrganization} + selectedOrganization={selectedOrganization} setSelectedOrganization={setSelectedOrganization} /> : null @@ -2940,7 +3104,6 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user style={{}} aria-label={"Copy APIkey"} > -
{ @@ -2953,7 +3116,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user ) { const clipboard = navigator.clipboard; if (clipboard === undefined) { - alert.error( + toast( "Can only copy over HTTPS (port 3443)" ); return; @@ -2969,7 +3132,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user /* Copy the text inside the text field */ document.execCommand("copy"); - alert.info("Apikey copied to clipboard"); + toast("Apikey copied to clipboard"); } }} > @@ -2977,7 +3140,6 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user style={{ color: "rgba(255,255,255,0.8)" }} /> -
) } @@ -3550,19 +3712,17 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user title="Set in EVERY workflow" placement="top" > -
- { - editAuthenticationConfig(data.id); - }} - > - - -
+ { + editAuthenticationConfig(data.id); + }} + > + + ) : ( -
{}} @@ -3579,7 +3738,6 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user color={data.defined ? "primary" : "secondary"} /> -
)} { - alert.info("Failed dismissing alert. Please contact support@shuffler.io if this persists."); + toast("Failed dismissing alert. Please contact support@shuffler.io if this persists."); }); } @@ -3791,13 +3949,12 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user style={{}} aria-label={"Copy orborus command"} > -
{ if (environment.Type === "cloud") { - alert.info("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.") + toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.") return } @@ -3808,7 +3965,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user if (copyText !== null && copyText !== undefined) { const clipboard = navigator.clipboard; if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)"); + toast("Can only copy over HTTPS (port 3443)"); return; } @@ -3822,13 +3979,12 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user /* Copy the text inside the text field */ document.execCommand("copy"); - alert.info("Orborus command copied to clipboard"); + toast("Orborus command copied to clipboard"); } }} > -
} /> @@ -3920,7 +4076,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user {showCPUAlert === false ? null : -
+
90% CPU the server(s) hosting the Shuffle App Runner (Orborus) was found. @@ -4202,7 +4358,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user - + Environments /> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 024799fb..bc2191aa 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1,31 +1,31 @@ /* eslint-disable react/no-multi-comp */ import React, { useState, useEffect, useLayoutEffect } from "react"; import ReactDOM from "react-dom" -import theme from '../theme.jsx'; +import theme from "../theme.jsx"; import { useInterval } from "react-powerhooks"; -//import { makeStyles, } from "@mui/styles"; +import { makeStyles, } from "@mui/styles"; import { v4 as uuidv4 } from "uuid"; import { useNavigate, Link, useParams } from "react-router-dom"; import { useBeforeunload } from "react-beforeunload"; import ReactJson from "react-json-view"; -import { NestedMenuItem } from "mui-nested-menu" +import { NestedMenuItem } from 'mui-nested-menu'; import ReactMarkdown from "react-markdown"; -import { useAlert } from "react-alert"; +//import { useAlert +import { ToastContainer, toast } from "react-toastify" import { isMobile } from "react-device-detect" import aa from 'search-insights' import Drift from "react-driftjs"; - -//import { Cron } from 'react-js-cron'; -//import 'react-js-cron/dist/styles.css' - +import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx"; import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; import algoliasearch from 'algoliasearch/lite'; import { Zoom, + Fade, + Avatar, Popover, TextField, @@ -50,7 +50,6 @@ import { IconButton, Menu, Input, - Collapse, FormGroup, FormControlLabel, Typography, @@ -66,8 +65,8 @@ import { ListItemText, ListItemAvatar, Badge, - Autocomplete, AvatarGroup, + Autocomplete, } from "@mui/material"; import { @@ -84,7 +83,6 @@ import { ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, - Code as CodeIcon, FormatListNumbered as FormatListNumberedIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, @@ -104,12 +102,15 @@ import { VpnKey as VpnKeyIcon, AddComment as AddCommentIcon, Edit as EditIcon, - Send as SendIcon, + Send as SendIcon, + Restore as RestoreIcon, Preview as PreviewIcon, ContentCopy as ContentCopyIcon, Circle as CircleIcon, SquareFoot as SquareFootIcon, AutoFixHigh as AutoFixHighIcon, + + Polyline as PolylineIcon, } from "@mui/icons-material"; @@ -223,9 +224,6 @@ export const triggers = [ ]; // is_valid: cloudSyncEnabled || isCloud ? true : false, -const surfaceColor = "#27292D"; -const inputColor = "#383B40"; - // http://apps.cytoscape.org/apps/yfileslayoutalgorithms cytoscape.use(edgehandles); //cytoscape.use(clipboard); @@ -353,6 +351,31 @@ export function removeParam(key, sourceURL) { return rtn; } +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important", + }, + root: { + "& .MuiAutocomplete-listbox": { + border: "2px solid #f85a3e", + color: "white", + fontSize: 18, + "& li:nth-child(even)": { + backgroundColor: "#CCC", + }, + "& li:nth-child(odd)": { + backgroundColor: "#FFF", + }, + }, + }, + inputRoot: { + color: "white", + "&:hover .MuiOutlinedInput-notchedOutline": { + borderColor: "#f86a3e", + }, + }, +}); + const splitter = "|~|"; const svgSize = 24; //const referenceUrl = "https://shuffler.io/functions/webhooks/" @@ -362,14 +385,13 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52 const AngularWorkflow = (defaultprops) => { const { globalUrl, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; const referenceUrl = globalUrl + "/api/v1/hooks/"; - const alert = useAlert() + //const alert = useAlert() let navigate = useNavigate(); const params = useParams(); var props = JSON.parse(JSON.stringify(defaultprops)) props.match = {} props.match.params = params - var to_be_copied = ""; const [firstrequest, setFirstrequest] = React.useState(true); const [cystyle] = useState(cytoscapestyle); @@ -385,6 +407,7 @@ const AngularWorkflow = (defaultprops) => { const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false); const [workflow, setWorkflow] = React.useState({}); + const [originalWorkflow, setOriginalWorkflow] = React.useState({}); const [userSettings, setUserSettings] = React.useState({}); const [subworkflow, setSubworkflow] = React.useState({}); const [subworkflowStartnode, setSubworkflowStartnode] = React.useState(""); @@ -448,6 +471,8 @@ const AngularWorkflow = (defaultprops) => { const [destinationValue, setDestinationValue] = React.useState({}); const [conditionValue, setConditionValue] = React.useState({}); const [dragging, setDragging] = React.useState(false); + const [showWorkflowRevisions, setShowWorkflowRevisions] = React.useState(false); + const [selectedRevision, setSelectedRevision] = useState({}) const [dragPosition, setDragPosition] = React.useState({ x: 0, y: 0, @@ -461,6 +486,7 @@ const AngularWorkflow = (defaultprops) => { const [selectedEdgeIndex, setSelectedEdgeIndex] = React.useState({}); const [visited, setVisited] = React.useState([]); + const [allRevisions, setAllRevisions] = useState([]) const [apps, setApps] = React.useState([]); const [filteredApps, setFilteredApps] = React.useState([]); @@ -491,7 +517,7 @@ const AngularWorkflow = (defaultprops) => { const [selectionOpen, setSelectionOpen] = React.useState(false); // eslint-disable-next-line no-unused-vars - const [_, setUpdate] = useState(""); // Used for rendring, don't remove + const [_, setUpdate] = useState(""); // Used to force rendring, don't remove const [workflowExecutions, setWorkflowExecutions] = React.useState([]); const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0); @@ -507,7 +533,16 @@ const AngularWorkflow = (defaultprops) => { "attachedTo": "", }); - + // For code editor + const [codeEditorModalOpen, setCodeEditorModalOpen] = React.useState(false); + const [codedata, setcodedata] = React.useState(""); + const [editorData, setEditorData] = React.useState({ + "name": "", + "value": "", + "field_number": -1, + "actionlist": [], + "field_id": "", + }) // This should all be set once, not on every iteration // Use states and don't update lol @@ -524,6 +559,7 @@ const AngularWorkflow = (defaultprops) => { const appBarSize = isCloud ? 75 : 72; const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"]; const unloadText = "Are you sure you want to leave without saving (CTRL+S)?"; + const classes = useStyles(); const [bodyWidth, bodyHeight] = useWindowSize() //console.log("Mobile: ", isMobile, bodyWidth, bodyHeight) @@ -549,9 +585,9 @@ const AngularWorkflow = (defaultprops) => { }) .then((response) => { if (response.status === 200) { - //alert.success("Successfully GOT app "+appId) + //toast("Successfully GOT app "+appId) } else { - //alert.error("Failed getting app"); + //toast("Failed getting app"); } return response.json(); @@ -569,7 +605,7 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -609,7 +645,7 @@ const AngularWorkflow = (defaultprops) => { setListCache(responseJson); }) .catch((error) => { - alert.error(error.toString()); + toast(error.toString()); }); }; @@ -711,7 +747,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Workflow error: ", error.toString()) }); }; @@ -858,17 +894,17 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to set app auth: " + responseJson.reason); + toast("Failed to set app auth: " + responseJson.reason); } else { getAppAuthentication(true, false); setAuthenticationModalOpen(false); // Needs a refresh with the new authentication.. - //alert.success("Successfully saved new app auth") + //toast("Successfully saved new app auth") } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("New auth error: ", error.toString()); }); }; @@ -957,7 +993,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Get execution error: ", error.toString()); }); }; @@ -1011,7 +1047,7 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Abort error: ", error.toString()); }); }; @@ -1124,7 +1160,7 @@ const AngularWorkflow = (defaultprops) => { if (!visited.includes(label)) { //if (item.action.result !== undefined && item.action.result !== null && !item.action.result.includes("failed condition")) { - // alert.error("Error for " + item.action.label + " with result " + item.result); + // toast("Error for " + item.action.label + " with result " + item.result); //} visited.push(label); setVisited(visited); @@ -1252,7 +1288,7 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { console.log("Stream send error: ", error.toString()) - //alert.error(error.toString()); + //toast(error.toString()); }) } @@ -1423,7 +1459,7 @@ const AngularWorkflow = (defaultprops) => { newComments.push(curworkflowComment); } else { - alert.info("No handler for type: " + type); + toast("No handler for type: " + type); } } } @@ -1484,9 +1520,9 @@ const AngularWorkflow = (defaultprops) => { if (!responseJson.success) { console.log(responseJson); if (responseJson.reason !== undefined && responseJson.reason !== null) { - alert.error("Failed to save: " + responseJson.reason); + toast("Failed to save: " + responseJson.reason); } else { - alert.error("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") + toast("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") } } else { @@ -1535,10 +1571,11 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setSavingState(0); - //alert.error(error.toString()); + //toast(error.toString()); console.log("Save workflow error: ", error.toString()); }); + setOriginalWorkflow(useworkflow) return success; }; @@ -1579,12 +1616,12 @@ const AngularWorkflow = (defaultprops) => { } if (workflow.public) { - alert.info("Save it to get a new version"); + toast("Save it to get a new version"); } var returncheck = monitorUpdates(); if (!returncheck) { - alert.error("No startnode set."); + toast("No startnode set."); return; } @@ -1620,7 +1657,7 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to start: " + responseJson.reason); + toast("Failed to start: " + responseJson.reason); setExecutionRunning(false); setExecutionRequestStarted(false); stop(); @@ -1640,7 +1677,7 @@ const AngularWorkflow = (defaultprops) => { responseJson.authorization === "" || responseJson.authorization === undefined ) { - alert.error("Something went wrong during execution startup"); + toast("Something went wrong during execution startup"); console.log("BAD RESPONSE FOR EXECUTION: ", responseJson); setExecutionRunning(false); setExecutionRequestStarted(false); @@ -1662,7 +1699,7 @@ const AngularWorkflow = (defaultprops) => { start(); }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); setExecutionRequestStarted(false) console.log("Execute workflow err: ", error.toString()); }); @@ -1773,16 +1810,16 @@ const AngularWorkflow = (defaultprops) => { setSelectedAction(selectedAction); setWorkflow(workflow); saveWorkflow(workflow); - alert.info("Added and updated authentication!"); + toast("Added and updated authentication!"); shouldClose = true } else { console.log("Closing auth modal? FAIL") - alert.error("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); + toast("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); shouldClose = false } } else { - alert.info("No authentication to update"); + toast("No authentication to update"); } } else { shouldClose = true @@ -1799,7 +1836,7 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setAppAuthentication([]); - //alert.error("Auth loading error: " + error.toString()); + //toast("Auth loading error: " + error.toString()); console.log("AppAuth error: " + error.toString()); }); }; @@ -1831,7 +1868,7 @@ const AngularWorkflow = (defaultprops) => { setFilteredApps(pretend_apps) setPrioritizedApps(pretend_apps); - alert.error("Something went wrong while loading apps. Please refresh the window to try again.") + toast("Something went wrong while loading apps. Please refresh the window to try again.") return } @@ -1894,7 +1931,7 @@ const AngularWorkflow = (defaultprops) => { .catch((error) => { console.log("App loading error: " + error.toString()); setAppsLoaded(true) - //alert.error("App loading error: "+error.toString()); + //toast("App loading error: "+error.toString()); }); }; @@ -1972,7 +2009,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Error loading files: ", error) }); }; @@ -1991,9 +2028,9 @@ const AngularWorkflow = (defaultprops) => { console.log("Status not 200 for workflows :O!"); if (response.status >= 500) { - alert.info("Something went wrong while loading the workflow. Please reload.") + toast("Something went wrong while loading the workflow. Please reload.") } else { - alert.info("You don't access to this workflow or loading failed.") + toast("You don't access to this workflow or loading failed.") window.location.pathname = "/workflows"; } } @@ -2028,7 +2065,7 @@ const AngularWorkflow = (defaultprops) => { fetchRecommendations(responseJson) if (responseJson.public) { - //alert.info("This workflow is public. Save the workflow to use it in your organization."); + //toast("This workflow is public. Save the workflow to use it in your organization."); setAppAuthentication([]) console.log("RESP: ", responseJson) @@ -2076,13 +2113,13 @@ const AngularWorkflow = (defaultprops) => { var nodefound = false; var target = sourcenode.parameters.find((item) => item.name === "startnode"); - console.log("Got rightclick target: ", target) - if (target === undefined || target === null) { - target = { - "name": "startnode", - "value": responseJson.start - } - } + console.log("Got rightclick target: ", target) + if (target === undefined || target === null) { + target = { + "name": "startnode", + "value": responseJson.start + } + } console.log(sourcenode.parameters); console.log(target); @@ -2223,6 +2260,7 @@ const AngularWorkflow = (defaultprops) => { cy.on("add", "node", (e) => onNodeAdded(e)); cy.on("add", "edge", (e) => onEdgeAdded(e)); } else { + setOriginalWorkflow(responseJson); setWorkflow(responseJson); setWorkflowDone(true); @@ -2236,14 +2274,14 @@ const AngularWorkflow = (defaultprops) => { responseJson.errors !== // what responseJson.errors.length > 0 ) { - console.log("Setting configure Modal to open") + console.log("Setting configure Modal to open") setConfigureWorkflowModalOpen(true); } } } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Get workflows error: ", error.toString()); }); }; @@ -2374,7 +2412,7 @@ const AngularWorkflow = (defaultprops) => { event.target.data("type") !== "COMMENT" && event.target.data().decorator ) { - alert.info("This edge can't be edited."); + toast("This edge can't be edited."); } else { //console.log("DATA: ", event.target.data()) const destinationId = event.target.data("target"); @@ -2386,7 +2424,7 @@ const AngularWorkflow = (defaultprops) => { curaction.app_name === "Shuffle Tools" && curaction.name === "router" ) { - alert.info("Router action can't have incoming conditions"); + toast("Router action can't have incoming conditions"); event.target.unselect(); return; } @@ -2418,7 +2456,7 @@ const AngularWorkflow = (defaultprops) => { if (nodedata.type === "TRIGGER" && (nodedata.app_name === "Shuffle Workflow" || nodedata.app_name === "User Input")) { if (nodedata.parameters === null) { - alert.error("Set a workflow first"); + toast("Set a workflow first"); return; } @@ -3008,7 +3046,7 @@ const AngularWorkflow = (defaultprops) => { curaction = data } else { if (workflow.public !== true) { - alert.error("Action not found. Please remake it."); + toast("Action not found. Please remake it."); } event.target.remove(); @@ -3066,7 +3104,7 @@ const AngularWorkflow = (defaultprops) => { if (!curapp || curapp === undefined) { console.log("APPS - couldn't find it: ", newapps) - //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`); + //toast(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`); const tmpapp = { name: curaction.app_name, @@ -3258,7 +3296,7 @@ const AngularWorkflow = (defaultprops) => { // workflow.actions.push(data) // curaction = data //} else { - // alert.error("Action not found. Please remake it."); + // toast("Action not found. Please remake it."); // event.target.remove(); // return; //} @@ -3292,7 +3330,7 @@ const AngularWorkflow = (defaultprops) => { } else if (data.type === "COMMENT") { setSelectedComment(data); } else { - alert.error("Can't handle node type " + data.type); + toast("Can't handle node type " + data.type); return; } @@ -3346,16 +3384,16 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (responseJson.success === false) { - alert.error("Failed to auto-activate the app. Go to /apps and activate it.") + toast("Failed to auto-activate the app. Go to /apps and activate it.") } else { if (refresh === true) { - //alert.success("App activated for your organization! Refresh the page to use the app.") + //toast("App activated for your organization! Refresh the page to use the app.") getApps() } } }) .catch(error => { - //alert.error(error.toString()) + //toast(error.toString()) console.log("Activate app error: ", error.toString()) }); } @@ -3670,7 +3708,7 @@ const AngularWorkflow = (defaultprops) => { event.target.remove() //console.log("Found branch already!") - alert.info("Triggers can have exactly one target node") + toast("Triggers can have exactly one target node") return @@ -3703,7 +3741,7 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers[targetnode].app_name === "Shuffle Workflow" ) { } else { - alert.error("Can't have triggers as target of branch"); + toast("Can't have triggers as target of branch"); event.target.remove(); } } @@ -3756,7 +3794,7 @@ const AngularWorkflow = (defaultprops) => { workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target ) { - alert.error("A branch in the opposite direction already exists"); + toast("A branch in the opposite direction already exists"); event.target.remove(); found = true; break; @@ -3765,8 +3803,10 @@ const AngularWorkflow = (defaultprops) => { workflow.branches[branchkey].source_id === edge.source ) { console.log(edge.source); - alert.error("That branch already exists"); + + //toast("That branch already exists"); event.target.remove(); + found = true; break; } else if (edge.target === workflow.start) { @@ -3775,7 +3815,7 @@ const AngularWorkflow = (defaultprops) => { ); if (targetnode === -1) { if (targetnode.type !== "TRIGGER") { - alert.error("Can't make arrow to starting node"); + toast("Can't make arrow to starting node"); event.target.remove(); break; } @@ -3790,7 +3830,7 @@ const AngularWorkflow = (defaultprops) => { // console.log("Destination: ", edge.target) // console.log("CHECK SOURCE IF ITS A TRIGGER: ", targetnode) // if (targetnode !== -1) { - // alert.error("Triggers can only target one target (startnode)") + // toast("Triggers can only target one target (startnode)") // event.target.remove() // found = true // break @@ -3805,7 +3845,7 @@ const AngularWorkflow = (defaultprops) => { console.log("TARGETNODE: ", targetnode) if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow") { } else { - alert.error("Can't have triggers as target of branch") + toast("Can't have triggers as target of branch") event.target.remove() found = true break @@ -3898,7 +3938,7 @@ const AngularWorkflow = (defaultprops) => { /* var curaction = workflow.actions.find((a) => a.id === nodedata.id); if (curaction === null || curaction === undefined) { - alert.error("Node not found. Please remake it.") + toast("Node not found. Please remake it.") event.target.remove(); } */ @@ -3977,7 +4017,7 @@ const AngularWorkflow = (defaultprops) => { setWorkflow(workflow); } else if (nodedata.type === "TRIGGER") { if (nodedata.is_valid === false) { - alert.info("This trigger is not available to you"); + toast("This trigger is not available to you"); node.remove(); return; } @@ -4005,18 +4045,14 @@ const AngularWorkflow = (defaultprops) => { data: newcybranch, }; - if ( - nodedata.name !== "User Input" && - nodedata.name !== "Shuffle Workflow" - ) { - if ( - workflow.actions !== undefined && - workflow.actions !== null && - workflow.actions.length > 0 - ) { - cy.add(edgeToBeAdded); - } - } + if (edgeToBeAdded.data.source !== edgeToBeAdded.data.target && edgeToBeAdded.data.source !== undefined && edgeToBeAdded.data.target !== undefined) { + if (nodedata.name !== "User Input" && nodedata.name !== "Shuffle Workflow") { + if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { + console.log("Edge handle: ", edgeToBeAdded) + cy.add(edgeToBeAdded); + } + } + } setWorkflow(workflow); } @@ -4041,7 +4077,7 @@ const AngularWorkflow = (defaultprops) => { } // Check if the source is trigger and can start - console.log("Removed: ", edge.data()) + //console.log("Removed: ", edge.data()) const allNodes = cy.nodes().jsons() for (let nodekey in allNodes) { const curnode = allNodes[nodekey] @@ -4049,33 +4085,33 @@ const AngularWorkflow = (defaultprops) => { continue } - if (curnode.data.id === edge.data("source")) { - console.log("Found matching trigger source: ", curnode) - if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") { - // If it's started, READD the edge - if (curnode.data.status === "running") { - console.log("Edge is running - readd it: ", edge.data()) + if (curnode.data.id === edge.data("source")) { + console.log("Found matching trigger source: ", curnode) + if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") { + // If it's started, READD the edge + if (curnode.data.status === "running") { + console.log("Edge is running - readd it: ", edge.data()) - // Just making sure it's not running infinitely - var newdata = edge.data() - newdata.readded = true + // Just making sure it's not running infinitely + var newdata = edge.data() + newdata.readded = true - try { - cy.add({ - group: "edges", - data: newdata, - }) + try { + cy.add({ + group: "edges", + data: newdata, + }) - alert.error("You must STOP the trigger before deleting its branches") - } catch (e) { - console.log("Failed re-adding edge: ", e) - } - } + toast("You must STOP the trigger before deleting its branches") + } catch (e) { + console.log("Failed re-adding edge: ", e) + } + } - //status: "uninitialized", - } - } - } + //status: "uninitialized", + } + } + } workflow.branches = workflow.branches.filter( (a) => a.id !== edge.data().id @@ -4223,7 +4259,7 @@ const AngularWorkflow = (defaultprops) => { if (copyText !== null && copyText !== undefined) { const clipboard = navigator.clipboard; if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)"); + toast("Can only copy over HTTPS (port 3443)"); return; } @@ -4231,7 +4267,7 @@ const AngularWorkflow = (defaultprops) => { copyText.select(); copyText.setSelectionRange(0, 99999); /* For mobile devices */ document.execCommand("copy"); - alert.success(`Copied ${cydata.length} element(s)`); + toast(`Copied ${cydata.length} element(s)`); } } } @@ -4244,7 +4280,7 @@ const AngularWorkflow = (defaultprops) => { /* const clipboard = navigator.clipboard if (clipboard === undefined || window === undefined || window === null) { - alert.error("Can only use cliboard over HTTPS (port 3443)") + toast("Can only use cliboard over HTTPS (port 3443)") return } @@ -4352,7 +4388,7 @@ const AngularWorkflow = (defaultprops) => { } } catch (e) { console.log("Error pasting: ", e); - //alert.info("Failed parsing clipboard: ", e) + //toast("Failed parsing clipboard: ", e) } }; @@ -4423,7 +4459,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Get environments error: ", error.toString()); }); }; @@ -5117,8 +5153,36 @@ const AngularWorkflow = (defaultprops) => { } } - const setupGraph = () => { - const actions = workflow.actions.map((action) => { + const setupGraph = (inputworkflow) => { + // Reset cytoscape nodes and branches + if (cy !== undefined && cy !== null) { + if (inputworkflow.actions !== undefined && inputworkflow.actions !== null && inputworkflow.actions.length > 0) { + cy.remove('*') + } + console.log("INPUT: ", inputworkflow) + } + + if (inputworkflow.actions === undefined || inputworkflow.actions === null) { + inputworkflow.actions = [] + } + + if (inputworkflow.branches === undefined || inputworkflow.branches === null) { + inputworkflow.branches = [] + } + + if (inputworkflow.triggers === undefined || inputworkflow.triggers === null) { + inputworkflow.triggers = [] + } + + if (inputworkflow.comments === undefined || inputworkflow.comments === null) { + inputworkflow.comments = [] + } + + if (inputworkflow.visual_branches === undefined || inputworkflow.visual_branches === null) { + inputworkflow.visual_branches = [] + } + + const actions = inputworkflow.actions.map((action) => { const node = {}; if (!action.isStartNode && action.app_name === "Shuffle Tools") { @@ -5145,9 +5209,9 @@ const AngularWorkflow = (defaultprops) => { node.data.id = action["id"]; node.data._id = action["id"]; node.data.type = "ACTION"; - node.isStartNode = action["id"] === workflow.start; + node.isStartNode = action["id"] === inputworkflow.start; - if (workflow.public === true) { + if (inputworkflow.public === true) { node.data.is_valid = true node.is_valid = true } @@ -5166,7 +5230,7 @@ const AngularWorkflow = (defaultprops) => { return node; }); - const decoratorNodes = workflow.actions.map((action) => { + const decoratorNodes = inputworkflow.actions.map((action) => { if (!action.isStartNode) { if (action.app_name === "Testing") { return null; @@ -5200,7 +5264,7 @@ const AngularWorkflow = (defaultprops) => { }); - const foundtriggers = workflow.triggers.map((trigger) => { + const foundtriggers = inputworkflow.triggers.map((trigger) => { const node = {}; node.position = trigger.position; node.data = trigger; @@ -5214,11 +5278,11 @@ const AngularWorkflow = (defaultprops) => { var comments = []; if ( - workflow.comments !== undefined && - workflow.comments !== null && - workflow.comments.length > 0 + inputworkflow.comments !== undefined && + inputworkflow.comments !== null && + inputworkflow.comments.length > 0 ) { - comments = workflow.comments.map((comment) => { + comments = inputworkflow.comments.map((comment) => { const node = {}; node.position = comment.position; node.data = comment; @@ -5230,13 +5294,12 @@ const AngularWorkflow = (defaultprops) => { }); } - // FIXME - tmp branch update var insertedNodes = [].concat(actions, foundtriggers, decoratorNodes, comments); insertedNodes = insertedNodes.filter((node) => node !== null); - var edges = workflow.branches.map((branch, index) => { + var edges = inputworkflow.branches.map((branch, index) => { const edge = {}; - var conditions = workflow.branches[index].conditions; + var conditions = inputworkflow.branches[index].conditions; if (conditions === undefined || conditions === null) { conditions = []; } @@ -5248,6 +5311,26 @@ const AngularWorkflow = (defaultprops) => { label = conditions.length + " conditions"; } + // Verify if branch.source_id and branch.destination_id exists in triggers or actions + /* + var sourceExists = false; + var destinationExists = false; + for (var i = 0; i < insertedNodes.length; i++) { + console.log("Insertednode: ", insertedNodes[i].data); + if (insertedNodes[i].data._id === branch.source_id) { + sourceExists = true; + } + if (insertedNodes[i].data._id === branch.destination_id) { + destinationExists = true; + } + } + + if (sourceExists === false || destinationExists === false) { + console.log("Couldn't find source node for branch " + branch.id); + return null; + } + */ + edge.data = { id: branch.id, _id: branch.id, @@ -5287,18 +5370,18 @@ const AngularWorkflow = (defaultprops) => { }); if ( - workflow.visual_branches !== undefined && - workflow.visual_branches !== null && - workflow.visual_branches.length > 0 + inputworkflow.visual_branches !== undefined && + inputworkflow.visual_branches !== null && + inputworkflow.visual_branches.length > 0 ) { - const visualedges = workflow.visual_branches.map((branch, index) => { + const visualedges = inputworkflow.visual_branches.map((branch, index) => { const edge = {}; - if (workflow.branches[index] === undefined) { + if (inputworkflow.branches[index] === undefined) { return {}; } - var conditions = workflow.branches[index].conditions; + var conditions = inputworkflow.branches[index].conditions; if (conditions === undefined || conditions === null) { conditions = []; } @@ -5319,7 +5402,6 @@ const AngularWorkflow = (defaultprops) => { edges = edges.concat(visualedges); } - setWorkflow(workflow); // Verifies if a branch is valid and skips others var newedges = []; @@ -5343,7 +5425,18 @@ const AngularWorkflow = (defaultprops) => { } insertedNodes = insertedNodes.concat(newedges); - setElements(insertedNodes); + + console.log("NODES: ", insertedNodes) + setWorkflow(inputworkflow); + + // Reset view for cytoscape + if (cy !== undefined && cy !== null) { + console.log("In cy add!") + cy.add(insertedNodes); + cy.fit(null, 200); + } else { + setElements(insertedNodes); + } }; const removeNode = (nodeId) => { @@ -5386,7 +5479,7 @@ const AngularWorkflow = (defaultprops) => { //} const parsedSelection = cy.$(":selected"); if (selectedNode.data().decorator === true && selectedNode.data("type") !== "COMMENT") { - alert.info("This node can't be deleted."); + toast("This node can't be deleted."); } else { console.log("Deleted.") selectedNode.remove(); @@ -5453,7 +5546,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //alert.error("ERROR: " + error.toString()); + //toast("ERROR: " + error.toString()); console.log("ERROR getting usecases: " + error.toString()); }) } @@ -5482,16 +5575,52 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //alert.error("ERROR: " + error.toString()); + //toast("ERROR: " + error.toString()); console.log("ERROR getting usecases: " + error.toString()); }) } + const getRevisionHistory = (workflow_id) => { + console.log("Loading revisions for workflow ID ", workflow_id) + + fetch(`${globalUrl}/api/v1/workflows/${workflow_id}/revisions`, { + 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!"); + } + + // Read text from stream + //return response.text(); + return response.json(); + }) + .then((responseJson) => { + console.log("Got workflow revisions: ", responseJson) + + if (responseJson.success === false) { + console.log("Error getting workflow revisions: ", responseJson) + return + } + + setAllRevisions(responseJson) + }) + .catch((error) => { + console.log("Error getting workflow revisions: ", error) + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps //useEffect(() => { if (firstrequest) { setFirstrequest(false); getWorkflow(props.match.params.key, {}); + getRevisionHistory(props.match.params.key) getApps(); fetchUsecases() @@ -5532,7 +5661,7 @@ const AngularWorkflow = (defaultprops) => { // App length necessary cus of cy initialization if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) { setGraphSetup(true); - setupGraph(); + setupGraph(workflow); console.log("In graph setup") // 2nd load - configures cytoscape @@ -5621,7 +5750,7 @@ const AngularWorkflow = (defaultprops) => { console.log("END: ", cy) var cydata = cy.$(":selected").jsons(); if (cydata !== undefined && cydata !== null && cydata.length > 0) { - alert.success(`Selected ${cydata.length} element(s). CTRL+C to copy them.`); + toast(`Selected ${cydata.length} element(s). CTRL+C to copy them.`); } }); @@ -5676,10 +5805,10 @@ const AngularWorkflow = (defaultprops) => { // No matter what, it's being stopped. if (!responseJson.success) { if (responseJson.reason !== undefined) { - alert.error("Failed to stop schedule: " + responseJson.reason); + toast("Failed to stop schedule: " + responseJson.reason); } } else { - alert.success("Successfully stopped schedule"); + toast("Successfully stopped schedule"); } workflow.triggers[triggerindex].status = "stopped"; @@ -5689,14 +5818,14 @@ const AngularWorkflow = (defaultprops) => { saveWorkflow(workflow); }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Stop schedule error: ", error.toString()); }); }; const submitSchedule = (trigger, triggerindex) => { if (trigger.name.length <= 0) { - alert.error("Error: name can't be empty"); + toast("Error: name can't be empty"); return; } @@ -5713,7 +5842,7 @@ const AngularWorkflow = (defaultprops) => { } } - alert.info("Creating schedule") + toast("Creating schedule") const data = { name: trigger.name, frequency: workflow.triggers[triggerindex].parameters[0].value, @@ -5744,9 +5873,9 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to set schedule: " + responseJson.reason); + toast("Failed to set schedule: " + responseJson.reason); } else { - alert.success("Successfully created schedule"); + toast("Successfully created schedule"); workflow.triggers[triggerindex].status = "running"; trigger.status = "running"; setSelectedTrigger(trigger); @@ -5756,7 +5885,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //alert.error(error.toString()); + //toast(error.toString()); console.log("Get schedule error: ", error.toString()); }); }; @@ -5778,7 +5907,7 @@ const AngularWorkflow = (defaultprops) => { maxWidth: isMobile ? 50 : "100%", marginTop: "5px", color: "white", - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, cursor: "pointer", display: "flex", }; @@ -5791,7 +5920,7 @@ const AngularWorkflow = (defaultprops) => { maxWidth: "100%", marginTop: "5px", color: "white", - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, cursor: "pointer", display: "flex", }; @@ -5893,7 +6022,7 @@ const AngularWorkflow = (defaultprops) => { open={open} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, }, }} onClose={() => { @@ -5903,7 +6032,7 @@ const AngularWorkflow = (defaultprops) => { > { @@ -5929,7 +6058,7 @@ const AngularWorkflow = (defaultprops) => { { @@ -6138,7 +6267,7 @@ const AngularWorkflow = (defaultprops) => { - {isMobile ? null : Variables} + {isMobile ? null : Vars} } style={tabStyle} @@ -6269,7 +6398,7 @@ const AngularWorkflow = (defaultprops) => { currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop); } else { if (workflow.start === "" || workflow.start === undefined) { - alert.error("Define a starting action first."); + toast("Define a starting action first."); return; } @@ -6474,7 +6603,7 @@ const AngularWorkflow = (defaultprops) => { app.actions === null || app.actions.length === 0 ) { - alert.error( + toast( "App " + app.name + " currently has no actions to perform. Please go to https://shuffler.io/apps to edit it." @@ -6871,10 +7000,6 @@ const AngularWorkflow = (defaultprops) => { style={{ backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, maxWidth: leftBarSize - 20, }} InputProps={{ style: { - color: "white", - fontSize: "1em", - height: 50, - margin: 0, }, startAdornment: ( @@ -7007,13 +7132,13 @@ const AngularWorkflow = (defaultprops) => { return (
{ //if (!isCloud) { - // alert.info("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.") + // toast("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.") // setTimeout(() => { // event.preventDefault() // window.open(parsedUrl, '_blank') // }, 2000) //} else { - alert.info(`Activating ${name}`) + toast(`Activating ${name}`) //} console.log("CLICK: ", hit) @@ -7080,13 +7205,14 @@ const AngularWorkflow = (defaultprops) => {
@@ -7127,11 +7253,13 @@ const AngularWorkflow = (defaultprops) => { delay += 75 return ( runDelay ? - + + {/**/}
-
+ {/*
*/} + :
{extraMessage} @@ -7187,16 +7315,11 @@ const AngularWorkflow = (defaultprops) => { if (allitems !== undefined && allitems !== null) { for (let itemkey in allitems) { const item = allitems[itemkey]; - if ( - item.app_name === appName && - item.label !== undefined && - item.label !== null - ) { + console.log("Appname: ", appName, "Item: ", item); + + if (item.app_name === appName && item.label !== undefined && item.label !== null) { var number = item.label.split("_"); - if ( - isNaN(number[-1]) && - parseInt(number[number.length - 1]) > highest - ) { + if (isNaN(number[-1]) && parseInt(number[number.length - 1]) > highest) { highest = number[number.length - 1]; } } @@ -7224,7 +7347,7 @@ const AngularWorkflow = (defaultprops) => { ); if (newaction === undefined || newaction === null) { - alert.error("Failed to find the action"); + toast("Failed to find the action"); return; } @@ -7534,6 +7657,9 @@ const AngularWorkflow = (defaultprops) => { zIndex: 1000, resize: "vertical", overflow: "auto", + paddingBottom: 100, + + overflowAnchor: "none", }; var rightsidebarStyle = { @@ -7550,6 +7676,8 @@ const AngularWorkflow = (defaultprops) => { borderRadius: theme.palette.borderRadius, resize: "both", overflow: "auto", + + overflowAnchor: "none", }; const setTriggerFolderWrapperMulti = (event) => { @@ -7569,7 +7697,7 @@ const AngularWorkflow = (defaultprops) => { // Max 1 folder for office for some reason. MailFolders('MAILBOX_ID') in resource // Can't parse URL with multiple folders. if (selectedTrigger.name === "Office365" & value !== undefined && value !== null && value.length > 1) { - alert.info("Max 1 folder at a time allowed for Office365") + toast("Max 1 folder at a time allowed for Office365") console.log("VALUE: ", value) value = [value[0]] } @@ -7780,9 +7908,13 @@ const AngularWorkflow = (defaultprops) => { var datafield = ( { labelId="action-autocompleter" SelectDisplayProps={{ style: { - marginLeft: 10, }, }} onClose={() => { @@ -7937,7 +8068,7 @@ const AngularWorkflow = (defaultprops) => { return ( { if (data.type === "Execution Argument") { @@ -7975,7 +8106,7 @@ const AngularWorkflow = (defaultprops) => { const menuItemStyle = { color: "white", - backgroundColor: inputColor, + backgroundColor: theme.palette.inputColor, }; const conditionsModal = ( @@ -7991,7 +8122,6 @@ const AngularWorkflow = (defaultprops) => { PaperProps={{ style: { pointerEvents: "auto", - backgroundColor: surfaceColor, color: "white", minWidth: isMobile ? "90%" : 800, border: theme.palette.defaultBorder, @@ -8096,7 +8226,7 @@ const AngularWorkflow = (defaultprops) => { anchorEl={variableAnchorEl} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, }, }} onClose={() => { @@ -8329,7 +8459,7 @@ const AngularWorkflow = (defaultprops) => { maxWidth: "100%", marginTop: "5px", color: "white", - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, cursor: "pointer", display: "flex", }; @@ -8440,7 +8570,7 @@ const AngularWorkflow = (defaultprops) => { open={open} PaperProps={{ style: { - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, }, }} onClose={() => { @@ -8449,7 +8579,7 @@ const AngularWorkflow = (defaultprops) => { }} > { duplicateCondition(index); }} @@ -8458,7 +8588,7 @@ const AngularWorkflow = (defaultprops) => { {"Duplicate"} { setOpen(false); deleteCondition(index); @@ -8488,7 +8618,6 @@ const AngularWorkflow = (defaultprops) => { const conditionId = uuidv4(); return (
-

Branch: Conditions - {selectedEdgeIndex} @@ -8502,7 +8631,6 @@ const AngularWorkflow = (defaultprops) => { What are conditions?

-
{ (branch) => branch.source_id === selectedTrigger.id ); if (branch === undefined || branch === null) { - alert.error( + toast( "No startnode connected to node. Connect it to an action." ); return; @@ -8869,7 +8997,7 @@ const AngularWorkflow = (defaultprops) => { (branch) => branch.source_id === selectedTrigger.id ); if (branch === undefined || branch === null) { - alert.error( + toast( "No startnode connected to node. Connect it to an action." ); return; @@ -9012,7 +9140,6 @@ const AngularWorkflow = (defaultprops) => { disabled={selectedTrigger.status === "running"} SelectDisplayProps={{ style: { - marginLeft: 10, }, }} onChange={(e) => { @@ -9027,7 +9154,7 @@ const AngularWorkflow = (defaultprops) => {