diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx
index b063a204..0573883d 100644
--- a/frontend/src/components/Appsearch.jsx
+++ b/frontend/src/components/Appsearch.jsx
@@ -10,10 +10,11 @@ import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon }
import algoliasearch from 'algoliasearch';
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core';
+import aa from 'search-insights'
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
-const WorkflowSearch = props => {
- const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits } = props
+const Appsearch = props => {
+ const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, } = props
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
@@ -140,19 +141,19 @@ const WorkflowSearch = props => {
}
counted += 1
- var parsedname = ""
- for (var key = 0; key < data.name.length; key++) {
- var character = data.name.charAt(key)
- if (character === character.toUpperCase()) {
- //console.log(data.name[key], data.name[key+1])
- if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) {
- } else {
- parsedname += " "
- }
- }
+ var parsedname = data.name.valueOf()
+ //for (var key = 0; key < data.name.length; key++) {
+ // var character = data.name.charAt(key)
+ // if (character === character.toUpperCase()) {
+ // //console.log(data.name[key], data.name[key+1])
+ // if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) {
+ // } else {
+ // parsedname += " "
+ // }
+ // }
- parsedname += character
- }
+ // parsedname += character
+ //}
parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ")
@@ -180,6 +181,32 @@ const WorkflowSearch = props => {
label: "",
})
}
+
+ const queryID = ""
+
+ if (queryID !== undefined && queryID !== null) {
+ try {
+ aa('init', {
+ appId: searchClient.appId,
+ apiKey: searchClient.transporter.headers["x-algolia-api-key"]
+ })
+
+ const timestamp = new Date().getTime()
+ aa('sendEvents', [
+ {
+ eventType: 'conversion',
+ eventName: 'App Framework Activation',
+ index: 'appsearch',
+ objectIDs: [data.objectID],
+ timestamp: timestamp,
+ queryID: queryID,
+ userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
+ }
+ ])
+ } catch (e) {
+ console.log("Failed algolia search update: ", e)
+ }
+ }
}}>
@@ -215,4 +242,4 @@ const WorkflowSearch = props => {
)
}
-export default WorkflowSearch;
+export default Appsearch;
diff --git a/frontend/src/components/AppsearchPopout.jsx b/frontend/src/components/AppsearchPopout.jsx
new file mode 100644
index 00000000..7e15dc67
--- /dev/null
+++ b/frontend/src/components/AppsearchPopout.jsx
@@ -0,0 +1,189 @@
+import React, { useState, useEffect } from 'react';
+
+import theme from '../theme';
+import AppSearch from './Appsearch.jsx';
+
+import {
+ Paper,
+ Typography,
+ Divider,
+ IconButton,
+ Badge,
+ CircularProgress,
+ Tooltip,
+ Button,
+} from "@material-ui/core";
+
+import {
+ Close as CloseIcon,
+ Delete as DeleteIcon,
+} from "@material-ui/icons";
+
+const AppSearchPopout = (props) => {
+ const {
+ cy,
+ paperTitle,
+ setPaperTitle,
+ newSelectedApp,
+ setNewSelectedApp,
+ selectionOpen,
+ setSelectionOpen,
+ discoveryData,
+ setDiscoveryData,
+ userdata,
+ } = props;
+
+ const [defaultSearch, setDefaultSearch] = React.useState(paperTitle !== undefined ? paperTitle : "")
+
+ if (selectionOpen !== true) {
+ return null
+ }
+
+ return (
+
+ {paperTitle !== undefined && paperTitle.length > 0 ?
+
+
+ {paperTitle}
+
+
+
+ : null}
+
+ {
+ //cy.elements().unselectify();
+ if (cy !== undefined) {
+ cy.elements().unselect()
+ }
+
+ e.preventDefault();
+ setSelectionOpen(false)
+ }}
+ >
+
+
+
+ {/* {/*Causes errors in Cytoscape. Removing for now.}
+
+ {
+ e.preventDefault();
+ setDiscoveryData({
+ "id": discoveryData.id,
+ "label": discoveryData.label,
+ "name": ""
+ })
+ setNewSelectedApp({
+ "image_url": "",
+ "name": "",
+ "id": "",
+ "objectID": "remove",
+ })
+ setSelectionOpen(true)
+ setDefaultSearch("")
+
+ const foundelement = cy.getElementById(discoveryData.id)
+ if (foundelement !== undefined && foundelement !== null) {
+ console.log("element: ", foundelement)
+ foundelement.data("large_image", discoveryData.large_image)
+ foundelement.data("text_margin_y", "14px")
+ foundelement.data("margin_x", "32px")
+ foundelement.data("margin_y", "19x")
+ foundelement.data("width", "45px")
+ foundelement.data("height", "45px")
+ }
+
+ setTimeout(() => {
+ setDiscoveryData({})
+ setNewSelectedApp({})
+ }, 1000)
+ }}
+ >
+
+
+
+ */}
+
+ {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
+
+
+
0 ? newSelectedApp.image_url : discoveryData.large_image} style={{height: 40, width: 40, margin: "auto",}}/>
+
+ :
+
+ }
+
+ {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
+ discoveryData.name
+ :
+ newSelectedApp.name !== undefined && newSelectedApp.name !== null && newSelectedApp.name.length > 0 ?
+ newSelectedApp.name
+ :
+ `No ${discoveryData.label} app chosen`
+ }
+
+
+
+ {discoveryData !== undefined && discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
+
+
+ {discoveryData.description}
+
+ {/*isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ?
+ {<
+ newSelectedApp={newSelectedApp}
+ setNewSelectedApp={setNewSelectedApp}
+ defaultSearch={defaultSearch}
+ />}
+ :
+ null
+ */}
+
+ :
+ selectionOpen
+ ?
+
+
+ Click an app below to select it
+
+
+ :
+ {
+ setSelectionOpen(true)
+ setDefaultSearch(discoveryData.label)
+ }}
+ >
+ Choose {discoveryData.label} app
+
+ }
+
+
+ {selectionOpen ?
+
+ : null}
+
+
+ )
+}
+
+export default AppSearchPopout;
diff --git a/frontend/src/components/AuthenticationItem.jsx b/frontend/src/components/AuthenticationItem.jsx
new file mode 100644
index 00000000..45879519
--- /dev/null
+++ b/frontend/src/components/AuthenticationItem.jsx
@@ -0,0 +1,278 @@
+import React, { useState, useEffect } from "react";
+
+import theme from '../theme';
+import { useAlert } from "react-alert";
+import {
+ Tooltip,
+ IconButton,
+ ListItem,
+ ListItemText,
+ FormGroup,
+ FormControl,
+ InputLabel,
+ FormLabel,
+ FormControlLabel,
+ Select,
+ MenuItem,
+ Grid,
+ Paper,
+ Typography,
+ TextField,
+ Zoom,
+} from "@material-ui/core";
+
+import {
+ Edit as EditIcon,
+ Delete as DeleteIcon,
+ SelectAll as SelectAllIcon,
+} from "@material-ui/icons";
+
+const AuthenticationItem = (props) => {
+ const { data, index, globalUrl, getAppAuthentication } = props
+
+ const [selectedAuthentication, setSelectedAuthentication] = React.useState({})
+ const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false);
+ const [authenticationFields, setAuthenticationFields] = React.useState([]);
+
+ const alert = useAlert();
+ var bgColor = "#27292d";
+ if (index % 2 === 0) {
+ bgColor = "#1f2023";
+ }
+
+ //console.log("Auth data: ", data)
+ if (data.type === "oauth2") {
+ data.fields = [
+ {
+ key: "url",
+ value: "Secret. Replaced during app execution!",
+ },
+ {
+ key: "client_id",
+ value: "Secret. Replaced during app execution!",
+ },
+ {
+ key: "client_secret",
+ value: "Secret. Replaced during app execution!",
+ },
+ {
+ key: "scope",
+ value: "Secret. Replaced during app execution!",
+ },
+ ];
+ }
+
+ const deleteAuthentication = (data) => {
+ alert.info("Deleting auth " + data.label);
+
+ // Just use this one?
+ const url = globalUrl + "/api/v1/apps/authentication/" + data.id;
+ console.log("URL: ", url);
+ fetch(url, {
+ method: "DELETE",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ console.log("RESP: ", responseJson);
+ if (responseJson["success"] === false) {
+ alert.error("Failed deleting auth");
+ } else {
+ // Need to wait because query in ES is too fast
+ setTimeout(() => {
+ getAppAuthentication();
+ }, 1000);
+ //alert.success("Successfully deleted authentication!")
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error in userdata: ", error);
+ });
+ }
+
+ const editAuthenticationConfig = (id) => {
+ const data = {
+ id: id,
+ action: "assign_everywhere",
+ };
+ const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config";
+
+ 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) {
+ alert.error("Failed overwriting appauth in workflows");
+ } else {
+ alert.success("Successfully updated auth everywhere!");
+ //setSelectedUserModalOpen(false);
+ setTimeout(() => {
+ getAppAuthentication();
+ }, 1000);
+ }
+ })
+ )
+ .catch((error) => {
+ alert.error("Err: " + error.toString());
+ });
+ };
+
+ const updateAppAuthentication = (field) => {
+ setSelectedAuthenticationModalOpen(true);
+ setSelectedAuthentication(field);
+ //{selectedAuthentication.fields.map((data, index) => {
+ var newfields = [];
+ for (var key in field.fields) {
+ newfields.push({
+ key: field.fields[key].key,
+ value: "",
+ });
+ }
+ setAuthenticationFields(newfields);
+ }
+
+ return (
+
+
+ style={{ minWidth: 75, maxWidth: 75 }}
+ />
+
+
+ {/*
+
+ */}
+
+ {/*
+
+ */}
+ {
+ return data.key;
+ })
+ .join(", ")
+ }
+ style={{
+ minWidth: 125,
+ maxWidth: 125,
+ overflow: "hidden",
+ }}
+ />
+
+
+ {
+ updateAppAuthentication(data);
+ }}
+ >
+
+
+ {data.defined ? (
+
+ {
+ editAuthenticationConfig(data.id);
+ }}
+ >
+
+
+
+ ) : (
+
+ {}}
+ >
+
+
+
+ )}
+ {
+ deleteAuthentication(data);
+ }}
+ >
+
+
+
+
+ )
+ }
+
+export default AuthenticationItem
diff --git a/frontend/src/components/AuthenticationNormal.jsx b/frontend/src/components/AuthenticationNormal.jsx
new file mode 100644
index 00000000..aae3a128
--- /dev/null
+++ b/frontend/src/components/AuthenticationNormal.jsx
@@ -0,0 +1,366 @@
+import React, { useState, useEffect } from "react";
+import theme from '../theme';
+import { v4 as uuidv4 } from "uuid";
+
+
+import {
+ Button,
+ Divider,
+ Select,
+ MenuItem,
+ TextField,
+ DialogActions,
+ DialogTitle,
+ DialogContent,
+ Typography,
+} from "@material-ui/core";
+
+import {
+ LockOpen as LockOpenIcon,
+} from "@material-ui/icons";
+
+const AuthenticationData = (props) => {
+ const {
+ globalUrl,
+ saveWorkflow,
+ selectedApp,
+ workflow,
+ selectedAction,
+ authenticationType,
+ getAppAuthentication,
+ appAuthentication,
+ setSelectedAction,
+ setAuthenticationModalOpen,
+ isCloud,
+ } = props;
+
+ const setNewAppAuth = (appAuthData) => {
+ console.log("DAta: ", appAuthData);
+ fetch(globalUrl + "/api/v1/apps/authentication", {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(appAuthData),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for setting app auth :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to set app auth: " + responseJson.reason);
+ } else {
+ if (getAppAuthentication !== undefined) {
+ getAppAuthentication()
+ }
+
+ if (setAuthenticationModalOpen !== undefined) {
+ setAuthenticationModalOpen(false)
+ }
+
+ // Needs a refresh with the new authentication..
+ //alert.success("Successfully saved new app auth")
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("New auth error: ", error.toString());
+ });
+ }
+
+ const [authenticationOption, setAuthenticationOptions] = React.useState({
+ app: JSON.parse(JSON.stringify(selectedApp)),
+ fields: {},
+ label: "",
+ usage: [
+ {
+ workflow_id: workflow.id,
+ },
+ ],
+ id: uuidv4(),
+ active: true,
+ });
+
+ if (
+ selectedApp.authentication === undefined ||
+ selectedApp.authentication.parameters === null ||
+ selectedApp.authentication.parameters === undefined ||
+ selectedApp.authentication.parameters.length === 0
+ ) {
+ return (
+
+
+ {selectedApp.name} does not require authentication
+
+
+ );
+ }
+
+ authenticationOption.app.actions = [];
+
+ for (var key in selectedApp.authentication.parameters) {
+ if (
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] === undefined
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] = "";
+ }
+ }
+
+ const handleSubmitCheck = () => {
+ console.log("NEW AUTH: ", authenticationOption);
+ if (authenticationOption.label.length === 0) {
+ authenticationOption.label = `Auth for ${selectedApp.name}`;
+ }
+
+ // Automatically mapping fields that already exist (predefined).
+ // Warning if fields are NOT filled
+ for (var key in selectedApp.authentication.parameters) {
+ if (
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ].length === 0
+ ) {
+ if (
+ selectedApp.authentication.parameters[key].value !== undefined &&
+ selectedApp.authentication.parameters[key].value !== null &&
+ selectedApp.authentication.parameters[key].value.length > 0
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] = selectedApp.authentication.parameters[key].value;
+ } else {
+ if (
+ selectedApp.authentication.parameters[key].schema.type === "bool"
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] = "false";
+ } else {
+ alert.info(
+ "Field " +
+ selectedApp.authentication.parameters[key].name +
+ " can't be empty"
+ );
+ return;
+ }
+ }
+ }
+ }
+
+ console.log("Action: ", selectedAction);
+ selectedAction.authentication_id = authenticationOption.id;
+ selectedAction.selectedAuthentication = authenticationOption;
+ if (
+ selectedAction.authentication === undefined ||
+ selectedAction.authentication === null
+ ) {
+ selectedAction.authentication = [authenticationOption];
+ } else {
+ selectedAction.authentication.push(authenticationOption);
+ }
+
+ setSelectedAction(selectedAction);
+
+ var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
+ var newFields = [];
+ for (const key in newAuthOption.fields) {
+ const value = newAuthOption.fields[key];
+ newFields.push({
+ key: key,
+ value: value,
+ });
+ }
+
+ console.log("FIELDS: ", newFields);
+ newAuthOption.fields = newFields;
+ setNewAppAuth(newAuthOption);
+
+ //if (configureWorkflowModalOpen) {
+ // setSelectedAction({});
+ //}
+
+ //setUpdate(authenticationOption.id);
+ };
+
+ if (
+ authenticationOption.label === null ||
+ authenticationOption.label === undefined
+ ) {
+ authenticationOption.label = selectedApp.name + " authentication";
+ }
+
+ return (
+
+
+
+ Authentication for {selectedApp.name}
+
+
+
+
+ What is app authentication?
+
+
+ These are required fields for authenticating with {selectedApp.name}
+
+ Name - what is this used for?
+ {
+ authenticationOption.label = event.target.value;
+ }}
+ />
+
+
+ {selectedApp.authentication.parameters.map((data, index) => {
+ return (
+
+
+ {data.name}
+
+ {data.schema !== undefined &&
+ data.schema !== null &&
+ data.schema.type === "bool" ? (
+ {
+ console.log("Value: ", e.target.value);
+ authenticationOption.fields[data.name] = e.target.value;
+ }}
+ style={{
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ height: 50,
+ }}
+ >
+
+ false
+
+
+ true
+
+
+ ) : (
+ {
+ authenticationOption.fields[data.name] =
+ event.target.value;
+ }}
+ />
+ )}
+
+ );
+ })}
+
+
+ {
+ setAuthenticationModalOpen(false);
+ }}
+ color="primary"
+ >
+ Cancel
+
+ {
+ setAuthenticationOptions(authenticationOption);
+ handleSubmitCheck();
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+ );
+};
+
+export default AuthenticationData
diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx
index fbd7eb79..66c48ab2 100644
--- a/frontend/src/components/ConfigureWorkflow.jsx
+++ b/frontend/src/components/ConfigureWorkflow.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import {
InputAdornment,
@@ -13,6 +13,7 @@ import {
List,
ListItem,
ListItemText,
+ Fade,
} from "@material-ui/core";
import { FavoriteBorder as FavoriteBorderIcon } from "@material-ui/icons";
import { FixName } from "../views/Apps.jsx";
@@ -43,14 +44,15 @@ const ConfigureWorkflow = (props) => {
isCloud,
setAuthenticationType,
alert,
+ showTriggers,
} = props;
const [requiredActions, setRequiredActions] = React.useState([]);
const [requiredVariables, setRequiredVariables] = React.useState([]);
const [requiredTriggers, setRequiredTriggers] = React.useState([]);
const [previousAuth, setPreviousAuth] = React.useState(appAuthentication);
- const [firstLoad, setFirstLoad] = React.useState("");
const [itemChanged, setItemChanged] = React.useState(false);
- var finished = false;
+ const [firstLoad, setFirstLoad] = React.useState("");
+ const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false);
if (workflow === undefined || workflow === null) {
return null;
@@ -94,18 +96,13 @@ const ConfigureWorkflow = (props) => {
};
if (firstLoad.length === 0 || firstLoad !== workflow.id) {
- if (finished) {
- setConfigureWorkflowModalOpen(false);
- return null;
- }
-
if (apps === undefined || apps === null || apps.length === 0) {
console.log("No apps loaded: ", apps);
setConfigureWorkflowModalOpen(false);
return null;
}
- setFirstLoad(workflow.id);
+ setFirstLoad(workflow.id)
const newactions = [];
for (var key in workflow.actions) {
const action = workflow.actions[key];
@@ -272,17 +269,19 @@ const ConfigureWorkflow = (props) => {
setRequiredTriggers(requiredTriggers);
setRequiredVariables(requiredVariables);
setRequiredActions(newactions);
- }
+ }
if (appAuthentication.length !== previousAuth.length) {
- var newactions = [];
+ var newactions = []
for (var actionkey in requiredActions) {
var newaction = requiredActions[actionkey];
const app = newaction.app;
for (var key in appAuthentication) {
const auth = appAuthentication[key];
- if (auth.app.name === app.name && auth.active) {
+
+ // Does this account for all the different ones of the same?
+ if (auth.app.name === app.name && auth.active === true) {
newaction.auth_done = true;
break;
}
@@ -502,6 +501,7 @@ const ConfigureWorkflow = (props) => {
return (
+ {/*
{
secondary={action.app_version}
style={{}}
/>
- {action.must_authenticate ? (
- action.auth_done ? (
- {}}>
- Authenticated
-
- ) : selectedAction.app_name === action.app_name ? (
-
- ) : (
- {
- setAuthenticationType(
+ */}
+ {action.must_authenticate ?
+ {
+ setAuthenticationType(
action.app.authentication.type === "oauth2" &&
action.app.authentication.redirect_uri !== undefined &&
action.app.authentication.redirect_uri !== null
@@ -541,37 +550,86 @@ const ConfigureWorkflow = (props) => {
: {
type: "",
}
- );
+ )
setItemChanged(true);
- setSelectedAction(action.action);
- setSelectedApp(action.app);
+
+ if (setSelectedAction !== undefined) {
+ setSelectedAction(action.action);
+ }
+
+ if (setSelectedApp !== undefined) {
+ setSelectedApp(action.app);
+ }
+
setAuthenticationModalOpen(true);
- }}
+ }}
>
- Authenticate
-
- )
- ) : null}
+
+
+ {action.auth_done ? "Authenticated" : `Authenticate ${action.app_name}`}
+
+
+ : null}
{action.must_activate ? (
- {
- console.log("ACTION: ", action)
- activateApp(action.action.app_id, action.app_name, action.app_version);
- setItemChanged(true);
- }}
+ {
+ console.log("ACTION: ", action)
+ activateApp(action.action.app_id, action.app_name, action.app_version);
+ setItemChanged(true);
+ }}
>
- Activate
+
+
+ Activate
+
) : null}
{action.update_version !== action.app_version ? (
{
+ fullWidth
+ variant="contained"
+ disabled={action.auth_done}
+ style={{
+ flex: 1,
+ textTransform: "none",
+ textAlign: "left",
+ justifyContent: "flex-start",
+ backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
+ color: action.auth_done ? "#686a6c" : "#2f2f2f",
+ borderRadius: theme.palette.borderRadius,
+ minWidth: 350,
+ maxHeight: 50,
+ overflow: "hidden",
+ border: `1px solid ${theme.palette.inputColor}`,
+ }}
+ color="primary"
+ onClick={() => {
console.log("Set version to: ", action.update_version)
if (workflow.actions !== null) {
@@ -592,84 +650,118 @@ const ConfigureWorkflow = (props) => {
}
}}
>
- {action.update_version}
+
+
+ {action.update_version}
+
) : null}
);
- };
+ }
+
+ // Based on the color here. Default: #f86a3e
+ //backgroundColor: selectedUsecaseCategory === usecase.name ? usecase.color : theme.palette.surfaceColor,
+ const topColor = "#f86a3e, #fc3922"
return (
-
{workflow.name}
-
- The following configuration makes the workflow ready immediately.
-
- {requiredActions.length > 0 ? (
-
-
- Actions
-
-
- {requiredActions.map((data, index) => {
- return ;
- })}
-
-
- ) : null}
+
+
+
+
{workflow.name}
+
+ The following configuration makes the workflow ready immediately.
+
+ {requiredActions.length > 0 ? (
+
+
+ Required Actions
+
+
+ {requiredActions.map((data, index) => {
+ return (
+
+ )
+ })}
+
+
+ ) : null}
- {requiredVariables.length > 0 ? (
-
-
- Variables
-
-
- {requiredVariables.map((data, index) => {
- return ;
- })}
-
-
- ) : null}
+ {requiredVariables.length > 0 ? (
+
+
+ Variables
+
+
+ {requiredVariables.map((data, index) => {
+ return ;
+ })}
+
+
+ ) : null}
- {requiredTriggers.length > 0 ? (
-
-
- Triggers
-
-
- {requiredTriggers.map((data, index) => {
- return ;
- })}
-
-
- ) : null}
-
-
- {/*
- {
- setConfigureWorkflowModalOpen(false)
- }}>
- Skip
-
- */}
- {
- if (itemChanged) {
- saveWorkflow(workflow);
- window.location.reload();
- } else {
- setConfigureWorkflowModalOpen(false);
- }
- }}
- >
- Close window
-
-
-
+ {requiredTriggers.length > 0 && showTriggers !== false ? (
+
+
+ Triggers
+
+
+ {requiredTriggers.map((data, index) => {
+ return ;
+ })}
+
+
+ ) : null}
+
+
+ {showFinalizeAnimation ?
+
{
+ console.log("Img loaded.")
+ setTimeout(() => {
+ console.log("Img closing.")
+ setConfigureWorkflowModalOpen(false);
+ }, 1250)
+
+ }}/>
+ :
+
+ {/*
+ {
+ setConfigureWorkflowModalOpen(false)
+ }}>
+ Skip
+
+ */}
+ {
+ setShowFinalizeAnimation(true)
+ setTimeout(() => {
+ if (itemChanged) {
+ if (saveWorkflow !== undefined) {
+ saveWorkflow(workflow);
+ window.location.reload();
+ }
+
+ } else {
+ }
+ }, 1000)
+ }}
+ >
+ Finalize
+
+
+ }
+
+
);
};
diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx
index b7fefd3a..57852c5d 100644
--- a/frontend/src/components/EditWorkflow.jsx
+++ b/frontend/src/components/EditWorkflow.jsx
@@ -2,6 +2,7 @@ import React, { useEffect, useContext } from "react";
import theme from '../theme';
import { isMobile } from "react-device-detect"
import ChipInput from "material-ui-chip-input";
+import UsecaseSearch from "../components/UsecaseSearch.jsx"
import {
Badge,
@@ -45,19 +46,91 @@ import {
} from "@material-ui/icons";
const EditWorkflow = (props) => {
- const { workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, } = props
+ const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props
const [submitLoading, setSubmitLoading] = React.useState(false);
- const [selectedUsecases, setSelectedUsecases] = React.useState([]);
const [showMoreClicked, setShowMoreClicked] = React.useState(false);
const [innerWorkflow, setInnerWorkflow] = React.useState(workflow)
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : [])
+ 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 : "")
- if (workflow === undefined || workflow.id === undefined || setWorkflow === undefined || modalOpen !== true) {
+
+ // Gets the generated workflow
+ const getGeneratedWorkflow = (workflow_id) => {
+ fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 when getting workflow");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.id === workflow_id) {
+ console.log("GOT WORKFLOW: ", responseJson)
+ if (name === "") {
+ innerWorkflow.name = responseJson.name
+ setName(responseJson.name)
+ }
+
+ if (description === "") {
+ innerWorkflow.description = responseJson.description
+ setDescription(description)
+ }
+
+ if (newWorkflowTags === []) {
+ innerWorkflow.tags = responseJson.tags
+ setNewWorkflowTags(responseJson.tags)
+ }
+
+ if (selectedUsecases === []) {
+ selectedUsecases = responseJson.usecase_ids
+ }
+
+ innerWorkflow.id = responseJson.id
+ innerWorkflow.blogpost = responseJson.blogpost
+ innerWorkflow.actions = responseJson.actions
+ innerWorkflow.triggers = responseJson.triggers
+ innerWorkflow.branches = responseJson.branches
+ innerWorkflow.comments = responseJson.comments
+ innerWorkflow.workflow_variables = responseJson.workflow_variables
+ innerWorkflow.execution_variables = responseJson.execution_variables
+
+
+ setInnerWorkflow(innerWorkflow)
+ setUpdate(Math.random())
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Get workflow error: ", error.toString());
+ })
+ }
+
+ if (foundWorkflowId.length > 0) {
+ getGeneratedWorkflow(foundWorkflowId)
+
+ setFoundWorkflowId("")
+ } else {
+ }
+
+ if (modalOpen !== true) {
return null
}
+ const newWorkflow = isEditing === true ? false : true
+
var upload = "";
var total_count = 0
@@ -71,264 +144,299 @@ const EditWorkflow = (props) => {
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
- minWidth: isMobile ? "90%" : "800px",
- maxWidth: isMobile ? "90%" : "800px",
+ minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
+ maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
+ minHeight: 400,
},
}}
>
-
-
- {workflow.id !== undefined ? "Editing" : "New"} workflow
- {showUpload === true ?
-
-
- upload.click()}
- >
-
-
-
-
+
+
+
+
+ {newWorkflow ? "New" : "Editing"} workflow
+
+
+ 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 ?
+
+
+ upload.click()}
+ >
+
+
+
+
+ : null}
+
+ {newWorkflow === true ?
+
+
+ Use a Template
+
+
+ Start your workflow from our templating system. This uses publied workflows from our Creators to generate full Usecases or parts of your Workflow.
+
+
: null}
-
+
-
- {
- innerWorkflow.name = event.target.value
- }}
- InputProps={{
- style: {
- color: "white",
- },
- }}
- color="primary"
- placeholder="Name"
- required
- margin="dense"
- defaultValue={innerWorkflow.name}
- label="Name"
- autoFocus
- fullWidth
- />
- {
- innerWorkflow.description = event.target.value
- setInnerWorkflow(innerWorkflow)
- }}
- InputProps={{
- style: {
- color: "white",
- },
- }}
- color="primary"
- defaultValue={innerWorkflow.description}
- placeholder="Description"
- multiline
- label="Description"
- margin="dense"
- fullWidth
- />
-
-
+
+
{
+ setName(event.target.value)
}}
- placeholder="Tags"
- color="primary"
- fullWidth
- value={newWorkflowTags}
- onAdd={(chip) => {
- newWorkflowTags.push(chip);
- setNewWorkflowTags(newWorkflowTags);
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ color="primary"
+ placeholder="Name"
+ required
+ margin="dense"
+ defaultValue={innerWorkflow.name}
+ label="Name"
+ autoFocus
+ fullWidth
+ />
+ {
+ setDescription(event.target.value)
}}
- onDelete={(chip, index) => {
- newWorkflowTags.splice(index, 1);
- setNewWorkflowTags(newWorkflowTags);
- }}
- />
- {usecases !== null && usecases !== undefined && usecases.length > 0 ?
-
- Usecases
- selected.join(', ')}
- onChange={(event) => {
- console.log("Changed: ", event)
- }}
- >
-
- None
-
- {usecases.map((usecase, index) => {
- //console.log(usecase)
- return (
-
-
- {usecase.name}
-
- {usecase.list.map((subcase, subindex) => {
- //console.log(subcase)
- total_count += 1
- return (
- {
- if (selectedUsecases.includes(subcase.name)) {
- const itemIndex = selectedUsecases.indexOf(subcase.name)
- if (itemIndex > -1) {
- selectedUsecases.splice(itemIndex, 1)
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ maxRows={4}
+ color="primary"
+ defaultValue={innerWorkflow.description}
+ placeholder="Description"
+ multiline
+ label="Description"
+ margin="dense"
+ fullWidth
+ />
+
+ {
+ newWorkflowTags.push(chip);
+ setNewWorkflowTags(newWorkflowTags);
+ }}
+ onDelete={(chip, index) => {
+ newWorkflowTags.splice(index, 1);
+ setNewWorkflowTags(newWorkflowTags);
+ }}
+ />
+ {usecases !== null && usecases !== undefined && usecases.length > 0 ?
+
+ Usecases
+ selected.join(', ')}
+ onChange={(event) => {
+ console.log("Changed: ", event)
+ }}
+ >
+
+ None
+
+ {usecases.map((usecase, index) => {
+ //console.log(usecase)
+ return (
+
+
+ {usecase.name}
+
+ {usecase.list.map((subcase, subindex) => {
+ //console.log(subcase)
+ total_count += 1
+ return (
+ {
+ if (selectedUsecases.includes(subcase.name)) {
+ const itemIndex = selectedUsecases.indexOf(subcase.name)
+ if (itemIndex > -1) {
+ selectedUsecases.splice(itemIndex, 1)
+ }
+ } else {
+ selectedUsecases.push(subcase.name)
}
- } else {
- selectedUsecases.push(subcase.name)
- }
- setUpdate(Math.random());
- setSelectedUsecases(selectedUsecases)
- }}>
-
-
-
- )
- })}
-
- )
- })}
-
-
+ setUpdate(Math.random());
+ setSelectedUsecases(selectedUsecases)
+ }}>
+
+
+
+ )
+ })}
+
+ )
+ })}
+
+
+ : null}
+
+
+ {showMoreClicked === true ?
+
+
+
+ Status
+ {
+ console.log("Data: ", e.target.value)
+
+ innerWorkflow.workflow_type = e.target.value
+ setInnerWorkflow(innerWorkflow)
+ }}
+ >
+ } label="Test" />
+ } label="Production" />
+
+
+
+
+
+
+ Type
+ {
+ console.log("Data: ", e.target.value)
+
+ innerWorkflow.workflow_type = e.target.value
+ setInnerWorkflow(innerWorkflow)
+ }}
+ >
+ } label="Trigger" />
+ } label="Subflow" />
+ } label="Standalone" />
+
+
+
+
+
+ {
+ innerWorkflow.blogpost = event.target.value
+ setInnerWorkflow(innerWorkflow)
+ }}
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ color="primary"
+ defaultValue={innerWorkflow.blogpost}
+ placeholder="A blogpost or other reference for how this work workflow was built, and what it's for."
+ rows="1"
+ label="blogpost"
+ margin="dense"
+ fullWidth
+ />
+ {
+ innerWorkflow.video = event.target.value
+ setInnerWorkflow(innerWorkflow)
+ }}
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ color="primary"
+ defaultValue={innerWorkflow.video}
+ placeholder="A youtube or loom link to the video"
+ rows="1"
+ label="Video"
+ margin="dense"
+ fullWidth
+ />
+ {
+ innerWorkflow.default_return_value = event.target.value
+ setInnerWorkflow(innerWorkflow)
+ }}
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ color="primary"
+ defaultValue={innerWorkflow.default_return_value}
+ placeholder="Default return value (used for Subflows if the subflow fails)"
+ rows="3"
+ multiline
+ label="Default return value"
+ margin="dense"
+ fullWidth
+ />
+
: null}
+
+ {
+ setShowMoreClicked(!showMoreClicked);
+ }}
+ >
+ {showMoreClicked ? : }
+
+
-
- {showMoreClicked === true ?
-
-
-
- Status
- {
- console.log("Data: ", e.target.value)
-
- innerWorkflow.workflow_type = e.target.value
- setInnerWorkflow(innerWorkflow)
- }}
- >
- } label="Test" />
- } label="Production" />
-
-
-
-
-
-
- Type
- {
- console.log("Data: ", e.target.value)
-
- innerWorkflow.workflow_type = e.target.value
- setInnerWorkflow(innerWorkflow)
- }}
- >
- } label="Trigger" />
- } label="Subflow" />
- } label="Standalone" />
-
-
-
-
-
- {
- innerWorkflow.blogpost = event.target.value
- setInnerWorkflow(innerWorkflow)
- }}
- InputProps={{
- style: {
- color: "white",
- },
- }}
- color="primary"
- defaultValue={innerWorkflow.blogpost}
- placeholder="A blogpost or other reference for how this work workflow was built, and what it's for."
- rows="1"
- label="blogpost"
- margin="dense"
- fullWidth
+ {newWorkflow === true ?
+
+
- {
- innerWorkflow.video = event.target.value
- setInnerWorkflow(innerWorkflow)
- }}
- InputProps={{
- style: {
- color: "white",
- },
- }}
- color="primary"
- defaultValue={innerWorkflow.video}
- placeholder="A youtube or loom link to the video"
- rows="1"
- label="Video"
- margin="dense"
- fullWidth
- />
- {
- innerWorkflow.default_return_value = event.target.value
- setInnerWorkflow(innerWorkflow)
- }}
- InputProps={{
- style: {
- color: "white",
- },
- }}
- color="primary"
- defaultValue={innerWorkflow.default_return_value}
- placeholder="Default return value (used for Subflows if the subflow fails)"
- rows="3"
- multiline
- label="Default return value"
- margin="dense"
- fullWidth
- />
-
+
: null}
-
- {
- setShowMoreClicked(!showMoreClicked);
- }}
- >
- {showMoreClicked ? : }
-
-
-
{
+ if (setNewWorkflow !== undefined) {
+ setWorkflow({})
+ }
+
setModalOpen(false)
}}
color="primary"
@@ -338,13 +446,35 @@ const EditWorkflow = (props) => {
{
+ innerWorkflow.name = name
+ innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
- setWorkflow(innerWorkflow)
+ if (selectedUsecases.length > 0) {
+ innerWorkflow.usecase_ids = selectedUsecases
+ }
+
+
+ if (setNewWorkflow !== undefined) {
+ setNewWorkflow(
+ innerWorkflow.name,
+ innerWorkflow.description,
+ innerWorkflow.tags,
+ innerWorkflow.default_return_value,
+ innerWorkflow,
+ newWorkflow,
+ innerWorkflow.usecase_ids,
+ innerWorkflow.blogpost,
+ innerWorkflow.status,
+ )
+ setWorkflow({})
+ } else {
+ setWorkflow(innerWorkflow)
+ }
setModalOpen(false)
}}
color="primary"
diff --git a/frontend/src/components/LandingpageUsecases.jsx b/frontend/src/components/LandingpageUsecases.jsx
index 2f4e2478..49a4f332 100644
--- a/frontend/src/components/LandingpageUsecases.jsx
+++ b/frontend/src/components/LandingpageUsecases.jsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import {isMobile} from "react-device-detect";
-import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
+import AppFramework, { usecases } from "../components/AppFramework.jsx";
import {Link} from 'react-router-dom';
import ReactGA from 'react-ga';
@@ -169,7 +169,7 @@ const LandingpageUsecases = (props) => {
{isMobile ? null :
}
{isMobile ? null :
diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx
index 43456cb3..68a9be36 100644
--- a/frontend/src/components/Oauth2Auth.jsx
+++ b/frontend/src/components/Oauth2Auth.jsx
@@ -1,5 +1,6 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { useTheme } from "@material-ui/core/styles";
+import theme from '../theme';
import { v4 as uuidv4 } from "uuid";
import {
@@ -62,6 +63,7 @@ const registeredApps = [
"outlook_office365",
"microsoft_teams",
"microsoft_teams_user_access",
+ "todoist",
]
const AuthenticationOauth2 = (props) => {
@@ -77,8 +79,8 @@ const AuthenticationOauth2 = (props) => {
setNewAppAuth,
setAuthenticationModalOpen,
isCloud,
+ autoAuth,
} = props;
- const theme = useTheme();
//const [update, setUpdate] = React.useState("|")
const [defaultConfigSet, setDefaultConfigSet] = React.useState(
@@ -88,7 +90,7 @@ const AuthenticationOauth2 = (props) => {
authenticationType.client_secret !== undefined &&
authenticationType.client_secret !== null &&
authenticationType.client_secret.length > 0
- );
+ );
const [clientId, setClientId] = React.useState(
defaultConfigSet ? authenticationType.client_id : ""
@@ -123,6 +125,73 @@ const AuthenticationOauth2 = (props) => {
return null;
}
+ const startOauth2Request = () => {
+ console.log("APP: ", selectedApp)
+ if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
+ handleOauth2Request(
+ "efe4c3fe-84a1-4821-a84f-23a6cfe8e72d",
+ "",
+ "https://graph.microsoft.com",
+ ["Mail.ReadWrite"],
+ );
+ } else if (selectedApp.name.toLowerCase() == "gmail") {
+ handleOauth2Request(
+ "253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com",
+ "",
+ "https://gmail.googleapis.com",
+ ["https://www.googleapis.com/auth/gmail.modify",
+ "https://www.googleapis.com/auth/gmail.send",
+ "https://www.googleapis.com/auth/gmail.insert",
+ "https://www.googleapis.com/auth/gmail.compose"]
+ )
+ } else if (selectedApp.name.toLowerCase() == "zoho_desk") {
+ handleOauth2Request(
+ "1000.ZR5MHUW6B0L6W1VUENFGIATFS0TOJT",
+ "",
+ "https://desk.zoho.com",
+ ["Desk.tickets.READ",
+ "Desk.tickets.UPDATE",
+ "Desk.tickets.DELETE",
+ "Desk.tickets.CREATE"]
+ )
+ } else if (selectedApp.name.toLowerCase() == "slack") {
+ handleOauth2Request(
+ "151779186901.2448678750935",
+ "",
+ "https://slack.com",
+ ["admin", "chat:write", "im:read", "im:write", "search:read", "usergroups:read", "usergroups:write"]
+ )
+ } else if (selectedApp.name.toLowerCase() == "webex") {
+ handleOauth2Request(
+ "Cab184f3d7271f540443c79b5b79845e3387abbbdb3db4233a87ea3a5432fb3d5",
+ "",
+ "https://webexapis.com",
+ ["spark:all"]
+ )
+ } else if (selectedApp.name.toLowerCase().includes("microsoft_teams")) {
+ handleOauth2Request(
+ "31cb4c84-658e-43d5-ae84-22c9142e967a",
+ "",
+ "https://graph.microsoft.com",
+ ["ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "Chat.Create", "Chat.ReadWrite", "Chat.Read"]
+ )
+ } else if (selectedApp.name.toLowerCase().includes("todoist")) {
+ handleOauth2Request(
+ "35fa3a384040470db0c8527e90a3c2eb",
+ "",
+ "https://api.todoist.com",
+ ["task:add"]
+ )
+ }
+ }
+
+ useEffect(() => {
+ console.log("Should automatically click the auto-auth button?")
+ if (autoAuth === true && selectedApp !== undefined) {
+ startOauth2Request()
+ }
+ }, [])
+
const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => {
setButtonClicked(true);
console.log("SCOPES: ", scopes);
@@ -164,19 +233,15 @@ const AuthenticationOauth2 = (props) => {
state += `%26refresh_uri%3d${authentication_url}`;
}
+ // No prompt forcing
+ const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`;
// Force new consent
//const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`;
// Admin consent
- //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=admin_consent&state=${state}&access_type=offline`;
-
- // Skip consent
- const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&state=${state}&access_type=offline`;
-
//const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent`
- //console.log("Full URI: ", url)
- //console.log("Redirect Uri: ", redirectUri)
- // &resource=https%3A%2F%2Fgraph.microsoft.com&
+
+ // &resource=https%3A%2F%2Fgraph.microsoft.com&
// FIXME: Awful, but works for prototyping
// How can we get a callback properly realtime?
@@ -188,12 +253,20 @@ const AuthenticationOauth2 = (props) => {
var open = true;
const timer = setInterval(() => {
if (newwin.closed) {
+ console.log("Closed!")
+
+ if (setAuthenticationModalOpen !== undefined) {
+ setAuthenticationModalOpen(false)
+ }
+
setButtonClicked(false);
clearInterval(timer);
//alert('"Secure Payment" window closed!');
getAppAuthentication(true, true);
- }
+ } else {
+ console.log("Not closed")
+ }
}, 1000);
//do {
// setTimeout(() => {
@@ -361,76 +434,48 @@ const AuthenticationOauth2 = (props) => {
{isCloud && registeredApps.includes(selectedApp.name.toLowerCase()) ?
0 || clientId.length > 0
}
- variant="contained"
fullWidth
onClick={() => {
// Hardcode some stuff?
// This could prolly be added to the app itself with a "default" client ID
- console.log("APP: ", selectedApp)
- if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
- handleOauth2Request(
- "efe4c3fe-84a1-4821-a84f-23a6cfe8e72d",
- "",
- "https://graph.microsoft.com",
- ["Mail.ReadWrite", "Mail.Send"],
- );
- } else if (selectedApp.name.toLowerCase() == "gmail") {
- handleOauth2Request(
- "253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com",
- "",
- "https://gmail.googleapis.com",
- ["https://www.googleapis.com/auth/gmail.modify",
- "https://www.googleapis.com/auth/gmail.send",
- "https://www.googleapis.com/auth/gmail.insert",
- "https://www.googleapis.com/auth/gmail.compose"]
- )
- } else if (selectedApp.name.toLowerCase() == "zoho_desk") {
- handleOauth2Request(
- "1000.ZR5MHUW6B0L6W1VUENFGIATFS0TOJT",
- "",
- "https://desk.zoho.com",
- ["Desk.tickets.READ",
- "Desk.tickets.UPDATE",
- "Desk.tickets.DELETE",
- "Desk.tickets.CREATE"]
- )
- } else if (selectedApp.name.toLowerCase() == "slack") {
- handleOauth2Request(
- "151779186901.2448678750935",
- "",
- "https://slack.com",
- ["admin", "chat:write", "im:read", "im:write", "search:read", "usergroups:read", "usergroups:write"]
- )
- } else if (selectedApp.name.toLowerCase() == "webex") {
- handleOauth2Request(
- "Cab184f3d7271f540443c79b5b79845e3387abbbdb3db4233a87ea3a5432fb3d5",
- "",
- "https://webexapis.com",
- ["spark:all"]
- )
- } else if (selectedApp.name.toLowerCase().includes("microsoft_teams")) {
- handleOauth2Request(
- "31cb4c84-658e-43d5-ae84-22c9142e967a",
- "",
- "https://graph.microsoft.com",
- ["ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "Chat.Create", "Chat.ReadWrite", "Chat.Read"]
- )
- }
+ startOauth2Request()
}}
color="primary"
>
{buttonClicked ? (
-
+
) : (
- "Auto-authenticate"
+
+
+
+ Auto-Authenticate
+
+
)}
diff --git a/frontend/src/components/SecurityFramework.jsx b/frontend/src/components/SecurityFramework.jsx
index cfe5196d..7139f7a6 100644
--- a/frontend/src/components/SecurityFramework.jsx
+++ b/frontend/src/components/SecurityFramework.jsx
@@ -1,6 +1,6 @@
import React, {useState } from 'react';
import {isMobile} from "react-device-detect";
-import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
+import AppFramework, { usecases } from "../components/AppFramework.jsx";
import {Link} from 'react-router-dom';
import ReactGA from 'react-ga';
@@ -56,6 +56,8 @@ export const securityFramework = [
]
const LandingpageUsecases = (props) => {
+ const { userdata } = props
+
const [selectedUsecase, setSelectedUsecase] = useState("Phishing")
const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases)
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
@@ -169,7 +171,12 @@ const LandingpageUsecases = (props) => {
}
{isMobile ? null :
diff --git a/frontend/src/components/SuggestedWorkflows.jsx b/frontend/src/components/SuggestedWorkflows.jsx
new file mode 100644
index 00000000..47fa214f
--- /dev/null
+++ b/frontend/src/components/SuggestedWorkflows.jsx
@@ -0,0 +1,235 @@
+import React, { useState, useEffect } from 'react';
+import ReactGA from 'react-ga';
+import theme from '../theme';
+import PaperComponent from "../components/PaperComponent.jsx"
+import UsecaseSearch, { usecaseTypes } from "../components/UsecaseSearch.jsx"
+
+import {
+ Paper,
+ Typography,
+ Divider,
+ IconButton,
+ Badge,
+ CircularProgress,
+ Tooltip,
+ Dialog,
+} from "@material-ui/core";
+
+import {
+ Close as CloseIcon,
+ Delete as DeleteIcon,
+ AutoFixHigh as AutoFixHighIcon,
+ Done as DoneIcon,
+} from "@mui/icons-material";
+
+const SuggestedWorkflows = (props) => {
+ const { globalUrl, userdata, usecaseSuggestions, frameworkData, setUsecaseSuggestions, inputSearch, apps, } = props
+
+ const [usecaseSearch, setUsecaseSearch] = React.useState("")
+ const [usecaseSearchType, setUsecaseSearchType] = React.useState("")
+ const [finishedUsecases, setFinishedUsecases] = React.useState([])
+ const [previousUsecase, setPreviousUsecase] = React.useState("")
+ const [closeWindow, setCloseWindow] = React.useState(false)
+
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
+
+
+ useEffect(() => {
+ if (closeWindow === true) {
+ console.log("WINDOW CLOSED")
+ finishedUsecases.push(usecaseSearch)
+ setFinishedUsecases(finishedUsecases)
+
+ setCloseWindow(false)
+ }
+ }, [closeWindow])
+
+ if (usecaseSuggestions === undefined || usecaseSuggestions.length === 0) {
+ console.log("Closing finished usecases 1")
+ return null
+ }
+
+ if (inputSearch !== previousUsecase) {
+ setPreviousUsecase(inputSearch)
+ setFinishedUsecases([])
+ }
+
+ if (finishedUsecases.length === usecaseSuggestions.length) {
+ console.log("Closing finished usecases 2")
+ return null
+ }
+
+
+ //useEffect(() => {
+ // //if (defaultSearch ===
+ // //setFinishedUsecases(finishedUsecases)
+ // console.log("Finished default usecase?", usecaseSearch)
+ //}, [usecaseSearch])
+
+ const foundZindex = usecaseSearch.length > 0 && usecaseSearchType.length > 0 ? -1 : 12500
+
+ const IndividualUsecase = (props) => {
+ const { usecase, index } = props
+ const [hovering, setHovering] = React.useState(false)
+
+ const usecasename = usecase.name
+ const bordercolor = usecase.color !== undefined ? usecase.color : "rgba(255,255,255,0.3)"
+
+
+ const srcimage = usecase.items[0].app
+ var dstimage = usecase.items[1].app
+ if (usecase.items.length > 2) {
+ dstimage = usecase.items[2].app
+ }
+
+ console.log(finishedUsecases)
+
+ const finished = finishedUsecases.includes(usecasename)
+ const selectedIcon = finished ?
+
+ if (finished) {
+ return null
+ }
+
+ // Simple visual of the usecase
+ return (
+