Added all new common files related to onboarding and workflow suggestions

This commit is contained in:
frikky
2022-10-23 00:03:33 +02:00
parent 3a7aba8119
commit 84201b8e4f
18 changed files with 4095 additions and 687 deletions
+1
View File
@@ -47,6 +47,7 @@
"react": "^16.14.0", "react": "^16.14.0",
"react-alert": "^5.5.0", "react-alert": "^5.5.0",
"react-alert-template-basic": "^1.0.0", "react-alert-template-basic": "^1.0.0",
"react-alice-carousel": "^2.6.4",
"react-avatar-editor": "^11.1.0", "react-avatar-editor": "^11.1.0",
"react-beforeunload": "^2.2.1", "react-beforeunload": "^2.2.1",
"react-chartjs-2": "^2.11.1", "react-chartjs-2": "^2.11.1",
@@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { securityFramework } from "./LandingpageUsecases.jsx"; import { securityFramework} from "./LandingpageUsecases.jsx";
import CytoscapeComponent from 'react-cytoscapejs'; import CytoscapeComponent from 'react-cytoscapejs';
import frameworkStyle from '../frameworkStyle.jsx'; import frameworkStyle from '../frameworkStyle.jsx';
import AppSearch from './Appsearch.jsx'; import AppSearch from './Appsearch.jsx';
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import theme from '../theme'; import theme from '../theme';
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
import { usecaseTypes } from "../components/UsecaseSearch.jsx"
import PaperComponent from "../components/PaperComponent.jsx"
import SuggestedWorkflows from "../components/SuggestedWorkflows.jsx"
import { import {
Paper, Paper,
@@ -16,6 +19,7 @@ import {
Badge, Badge,
CircularProgress, CircularProgress,
Tooltip, Tooltip,
Dialog,
} from "@material-ui/core"; } from "@material-ui/core";
import { import {
@@ -518,24 +522,105 @@ export const usecases = {
} }
const Framework = (props) => { const Framework = (props) => {
const { globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, size, inputUsecase, isLoggedIn, color, discoveryWrapper, setDiscoveryWrapper} = props; const { globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, setFrameworkData, size, inputUsecase, isLoggedIn, color, discoveryWrapper, setDiscoveryWrapper, userdata, apps, } = props;
const [cy, setCy] = React.useState() const [cy, setCy] = React.useState()
const [edgesStarted, setEdgesStarted] = React.useState(false) const [edgesStarted, setEdgesStarted] = React.useState(false)
const [graphDone, setGraphDone] = React.useState(false) const [graphDone, setGraphDone] = React.useState(false)
const [cyDone, setCyDone] = React.useState(false) const [cyDone, setCyDone] = React.useState(false)
const [discoveryData, setDiscoveryData] = React.useState({}) const [discoveryData, setDiscoveryData] = React.useState({})
const [selectionOpen, setSelectionOpen] = React.useState(true) const [selectionOpen, setSelectionOpen] = React.useState(true)
const [frameworkSuggestions, setFrameworkSuggestions] = React.useState([])
const [newSelectedApp, setNewSelectedApp] = React.useState({}) const [newSelectedApp, setNewSelectedApp] = React.useState({})
const [defaultSearch, setDefaultSearch] = React.useState("") const [defaultSearch, setDefaultSearch] = React.useState("")
const [animationStarted, setAnimationStarted] = React.useState(false) const [animationStarted, setAnimationStarted] = React.useState(false)
const [paperTitle, setPaperTitle] = React.useState("") const [paperTitle, setPaperTitle] = React.useState("")
const [changedApp, setChangedApp] = React.useState("")
const [usecaseType, setUsecaseType] = React.useState(0)
const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
const scale = size === undefined ? 1 : size > 5 ? 3 : size const scale = size === undefined ? 1 : size > 5 ? 3 : size
const alert = useAlert() const alert = useAlert()
const showRecommendations = (changed, frameworkData) => {
console.log("Inside recommendation loader")
setChangedApp(changed)
// FIX:
// 0. Get workflows loaded in from usecasesearch
// 1. Search through workflow templates for matching app types
// 2. Validate if template is already in use~ (workflows with same tools)
// 3. Generate the workflow(s) - PS: Fix new workflow templates
// 4. Moving on!
// How can we load templates? UsecaseSearch?
var showusecases = []
//const foundusecase = usecaseTypes.find(data => data.name.toLowerCase() === defaultSearch.toLowerCase())
for (var key in usecaseTypes) {
for (var subkey in usecaseTypes[key].value) {
const usecase = usecaseTypes[key].value[subkey]
if (usecase.active === false) {
continue
}
var potential = false
var matches = []
for (var itemtype in usecase.items) {
const apptype = usecase.items[itemtype].app_type.toLowerCase()
//console.log("OLD: ", changed, "USECASE: ", apptype)
if (changed.toLowerCase() === apptype || changed.toLowerCase().includes(apptype)) {
potential = true
if (frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) {
usecase.items[itemtype].app = frameworkData[apptype]
}
matches.push(usecase.items[itemtype])
} else {
// Check if the type is done in frameworkData
if (frameworkData[apptype] !== undefined) {
//console.log("NOT UNDEFINED: ", frameworkData[apptype])
if (frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) {
console.log("FOUND: ", frameworkData[apptype])
usecase.items[itemtype].app = frameworkData[apptype]
//console.log("Real app!")
matches.push(usecase.items[itemtype])
}
//if (frameworkData[apptype] !== undefined) {
} else {
console.log("UNDEFINED APP (bad name?): ", apptype)
}
}
}
if (potential) {
if (matches.length === usecase.items.length) {
usecase.color = "#c51152"
usecase.type = usecaseTypes[key].name
showusecases.push(usecase)
}
}
}
}
// FIXME: Check if a usecase has already been handled
console.log("")
console.log("GOT USECASES: ", showusecases)
// FIXME: Just showing one usecase at a time for now
if (showusecases.length > 0) {
setFrameworkSuggestions(showusecases.slice(0,1))
}
}
useEffect(() => { useEffect(() => {
//console.log("DISCWRAP CHANG: ", discoveryWrapper) console.log("DISCWRAP CHANG: ", discoveryWrapper)
if (discoveryWrapper === undefined || discoveryWrapper.id === "SHUFFLE" || discoveryWrapper.id === undefined || cy === undefined) { if (discoveryWrapper === undefined || discoveryWrapper.id === "SHUFFLE" || discoveryWrapper.id === undefined || cy === undefined) {
setDiscoveryData({}) setDiscoveryData({})
@@ -548,26 +633,30 @@ const Framework = (props) => {
// Find the node and click it? // Find the node and click it?
setTimeout(() => { //setTimeout(() => {
const nodes = cy.nodes().jsons() const nodes = cy.nodes().jsons()
for (var key in nodes) { for (var key in nodes) {
const node = nodes[key] const node = nodes[key]
var newSearchName = discoveryWrapper.id.valueOf() var newSearchName = discoveryWrapper.id.valueOf()
if (newSearchName === "EMAIL") { if (newSearchName === "EMAIL") {
newSearchName = "COMMS" newSearchName = "COMMS"
}
if (node.data.id === newSearchName) {
const tmpnode = cy.getElementById(node.data.id)
if (tmpnode !== undefined) {
tmpnode.select()
}
setDefaultSearch(discoveryWrapper.id)
setPaperTitle(discoveryWrapper.id)
}
} }
}, 50,)
if (newSearchName === "ERADICATION" || newSearchName === "ENDPOINT") {
newSearchName = "EDR & AV"
}
if (node.data.id === newSearchName) {
const tmpnode = cy.getElementById(node.data.id)
if (tmpnode !== undefined) {
tmpnode.select()
}
setDefaultSearch(discoveryWrapper.id)
setPaperTitle(discoveryWrapper.id)
}
}
//}, 50,)
//setDiscoveryData(discoveryWrapper) //setDiscoveryData(discoveryWrapper)
}, [discoveryWrapper]) }, [discoveryWrapper])
@@ -652,17 +741,23 @@ const Framework = (props) => {
} }
useEffect(() => { useEffect(() => {
if (discoveryData.id === undefined) { console.log(newSelectedApp, discoveryData)
return
}
if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) { if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
return return
} }
if (paperTitle.length > 0) { //if (paperTitle.length > 0) {
setDiscoveryWrapper({}) // console.log("No papertitle (parent button)")
setSelectionOpen(false)
// cy.elements().unselect()
// return
//}
if (discoveryData.id === undefined) {
console.log("No discoverydata (parent button)")
cy.elements().unselect()
return
} }
const submitValue = { const submitValue = {
@@ -683,7 +778,29 @@ const Framework = (props) => {
foundelement.data("height", `${85*scale}px`) foundelement.data("height", `${85*scale}px`)
} }
if (setFrameworkData !== undefined) {
// Find discoveryData.id
var keys = []
for (const [key, value] of Object.entries(frameworkData)) {
if (key.toLowerCase() === discoveryData.id.toLowerCase()) {
keys.push(key)
}
}
if (keys.length === 0) {
console.log("Failed to find: ", discoveryData.id, " IN ", frameworkData)
} else {
for (var key in keys) {
frameworkData[keys[key]] = submitValue
}
setFrameworkData(frameworkData)
showRecommendations(discoveryData.id, frameworkData)
}
}
setFrameworkItem(submitValue) setFrameworkItem(submitValue)
cy.elements().unselect()
}, [newSelectedApp]) }, [newSelectedApp])
@@ -792,8 +909,6 @@ const Framework = (props) => {
//console.log("Framework - update? ", parsedFrameworkData) //console.log("Framework - update? ", parsedFrameworkData)
// 0 = automated, 1 = manual // 0 = automated, 1 = manual
const [usecaseType, setUsecaseType] = React.useState(0)
const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
const elements = [] const elements = []
const surfaceColor = "#27292D" const surfaceColor = "#27292D"
@@ -966,14 +1081,60 @@ const Framework = (props) => {
) )
} }
const onNodeUnselect = (event) => {
var data = event.target.data();
console.log("UNSELECT: ", data)
var parsedStyle = {
"border-width": "10px",
"border-opacity": ".7",
"border-color": "#7fe57f",
}
if (event.target !== undefined && event.target !== null) {
event.target.animate(
{
style: parsedStyle,
},
{
duration: animationDuration,
}
)
setTimeout(() => {
event.target.animate(
{
style: {
"border-width": "3px",
},
},
{
duration: animationDuration,
}
)
}, 2500)
}
//setDiscoveryData({})
setDiscoveryWrapper({})
setSelectionOpen(false)
setDefaultSearch("")
setPaperTitle("")
//setDiscoveryData({})
}
const onNodeSelect = (event) => { const onNodeSelect = (event) => {
const data = event.target.data(); var data = event.target.data();
console.log("Node: ", data) console.log("Node: ", data)
if (data.id === "SHUFFLE") { if (data.id === "SHUFFLE") {
event.target.unselect() event.target.unselect()
return return
} }
if (data.label === "EDR & AV") {
data.label = "ERADICATION"
}
setDiscoveryData(data) setDiscoveryData(data)
setSelectionOpen(true) setSelectionOpen(true)
@@ -1009,6 +1170,9 @@ const Framework = (props) => {
cy.on("select", "node", (e) => { cy.on("select", "node", (e) => {
onNodeSelect(e) onNodeSelect(e)
}) })
cy.on("unselect", "node", (e) => {
onNodeUnselect(e)
})
cy.on("mouseover", "node", (e) => {onNodeHover(e)}) cy.on("mouseover", "node", (e) => {onNodeHover(e)})
cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); cy.on("mouseout", "node", (e) => onNodeHoverOut(e));
@@ -1547,8 +1711,21 @@ const Framework = (props) => {
var usecasediff = -100 var usecasediff = -100
const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color
return ( return (
<div style={{margin: "auto", backgroundColor: bgColor, position: "relative", }}> <div style={{margin: "auto", backgroundColor: bgColor, position: "relative", }}>
<div style={{position: "absolute"}}>
<SuggestedWorkflows
globalUrl={globalUrl}
userdata={userdata}
frameworkData={frameworkData}
usecaseSuggestions={frameworkSuggestions}
setUsecaseSuggestions={setFrameworkSuggestions}
inputSearch={changedApp}
apps={apps}
/>
</div>
{showOptions === false ? null : {showOptions === false ? null :
<div style={{textAlign: "center",}}> <div style={{textAlign: "center",}}>
{Object.keys(usecases).map((data, index) => { {Object.keys(usecases).map((data, index) => {
@@ -1580,7 +1757,7 @@ const Framework = (props) => {
{ {
Object.getOwnPropertyNames(discoveryData).length > 0 ? Object.getOwnPropertyNames(discoveryData).length > 0 ?
<Paper style={{width: 275, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: 25, left: 50, }}> <Paper style={{width: 275, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
{paperTitle.length > 0 ? {paperTitle.length > 0 ?
<span> <span>
<Typography variant="h6" style={{textAlign: "center"}}> <Typography variant="h6" style={{textAlign: "center"}}>
@@ -1718,6 +1895,8 @@ const Framework = (props) => {
defaultSearch={defaultSearch} defaultSearch={defaultSearch}
newSelectedApp={newSelectedApp} newSelectedApp={newSelectedApp}
setNewSelectedApp={setNewSelectedApp} setNewSelectedApp={setNewSelectedApp}
userdata={userdata}
cy={cy}
/> />
: null} : null}
</div> </div>
+42 -15
View File
@@ -10,10 +10,11 @@ import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon }
import algoliasearch from 'algoliasearch'; import algoliasearch from 'algoliasearch';
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core'; import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core';
import aa from 'search-insights'
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const WorkflowSearch = props => { const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits } = 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"; const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
@@ -140,19 +141,19 @@ const WorkflowSearch = props => {
} }
counted += 1 counted += 1
var parsedname = "" var parsedname = data.name.valueOf()
for (var key = 0; key < data.name.length; key++) { //for (var key = 0; key < data.name.length; key++) {
var character = data.name.charAt(key) // var character = data.name.charAt(key)
if (character === character.toUpperCase()) { // if (character === character.toUpperCase()) {
//console.log(data.name[key], data.name[key+1]) // //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()) { // if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) {
} else { // } else {
parsedname += " " // parsedname += " "
} // }
} // }
parsedname += character // parsedname += character
} //}
parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ")
@@ -180,6 +181,32 @@ const WorkflowSearch = props => {
label: "", 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)
}
}
}}> }}>
<div style={{display: "flex"}}> <div style={{display: "flex"}}>
<img alt={data.name} src={data.image_url} style={{width: "100%", maxWidth: 30, minWidth: 30, minHeight: 30, maxHeight: 30, display: "block", }} /> <img alt={data.name} src={data.image_url} style={{width: "100%", maxWidth: 30, minWidth: 30, minHeight: 30, maxHeight: 30, display: "block", }} />
@@ -215,4 +242,4 @@ const WorkflowSearch = props => {
) )
} }
export default WorkflowSearch; export default Appsearch;
+189
View File
@@ -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 (
<Paper style={{width: 275, maxHeight: 400, zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -15, left: 50, overflow: "hidden", }}>
{paperTitle !== undefined && paperTitle.length > 0 ?
<span>
<Typography variant="h6" style={{textAlign: "center"}}>
{paperTitle}
</Typography>
<Divider style={{marginTop: 5, marginBottom: 5 }} />
</span>
: null}
<Tooltip
title="Close window"
placement="top"
style={{ zIndex: 10011 }}
>
<IconButton
style={{ zIndex: 12501, position: "absolute", top: 10, right: 10}}
onClick={(e) => {
//cy.elements().unselectify();
if (cy !== undefined) {
cy.elements().unselect()
}
e.preventDefault();
setSelectionOpen(false)
}}
>
<CloseIcon style={{ color: "white", height: 15, width: 15, }} />
</IconButton>
</Tooltip>
{/* {/*Causes errors in Cytoscape. Removing for now.}
<Tooltip
title="Unselect app"
placement="top"
style={{ zIndex: 10011 }}
>
<IconButton
style={{ zIndex: 12501, position: "absolute", top: 32, right: 10}}
onClick={(e) => {
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)
}}
>
<DeleteIcon style={{ color: "white", height: 15, width: 15, }} />
</IconButton>
</Tooltip>
*/}
<div style={{display: "flex"}}>
{discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
<div style={{border: "1px solid rgba(255,255,255,0.2)", borderRadius: 25, height: 40, width: 40, textAlign: "center", overflow: "hidden",}}>
<img alt={discoveryData.id} src={newSelectedApp.image_url !== undefined && newSelectedApp.image_url !== null && newSelectedApp.image_url.length > 0 ? newSelectedApp.image_url : discoveryData.large_image} style={{height: 40, width: 40, margin: "auto",}}/>
</div>
:
<img alt={discoveryData.id} src={discoveryData.large_image} style={{height: 40,}}/>
}
<Typography variant="body1" style={{marginLeft: 10, marginTop: 6}}>
{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`
}
</Typography>
</div>
<div>
{discoveryData !== undefined && discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
<span>
<Typography variant="body2" color="textSecondary" style={{marginTop: 10, marginBottom: 10, maxHeight: 75, overflowY: "auto", overflowX: "hidden", }}>
{discoveryData.description}
</Typography>
{/*isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ?
{<
newSelectedApp={newSelectedApp}
setNewSelectedApp={setNewSelectedApp}
defaultSearch={defaultSearch}
/>}
:
null
*/}
</span>
:
selectionOpen
?
<span>
<Typography variant="body2" color="textSecondary" style={{marginTop: 10}}>
Click an app below to select it
</Typography>
</span>
:
<Button
variant="contained"
color="primary"
style={{marginTop: 10, }}
onClick={() => {
setSelectionOpen(true)
setDefaultSearch(discoveryData.label)
}}
>
Choose {discoveryData.label} app
</Button>
}
</div>
<div style={{marginTop: 10}}>
{selectionOpen ?
<AppSearch
defaultSearch={defaultSearch}
newSelectedApp={newSelectedApp}
setNewSelectedApp={setNewSelectedApp}
userdata={userdata}
/>
: null}
</div>
</Paper>
)
}
export default AppSearchPopout;
@@ -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 (
<ListItem key={index} style={{ backgroundColor: bgColor }}>
<ListItemText
primary=<img
alt=""
src={data.app.large_image}
style={{
maxWidth: 50,
borderRadius: theme.palette.borderRadius,
}}
/>
style={{ minWidth: 75, maxWidth: 75 }}
/>
<ListItemText
primary={data.label}
style={{
minWidth: 225,
maxWidth: 225,
overflow: "hidden",
}}
/>
<ListItemText
primary={data.app.name}
style={{ minWidth: 175, maxWidth: 175, marginLeft: 10 }}
/>
{/*
<ListItemText
primary={data.defined === false ? "No" : "Yes"}
style={{ minWidth: 100, maxWidth: 100, }}
/>
*/}
<ListItemText
primary={
data.workflow_count === null ? 0 : data.workflow_count
}
style={{
minWidth: 100,
maxWidth: 100,
textAlign: "center",
overflow: "hidden",
}}
/>
{/*
<ListItemText
primary={data.node_count}
style={{
minWidth: 110,
maxWidth: 110,
textAlign: "center",
overflow: "hidden",
}}
/>
*/}
<ListItemText
primary={
data.fields === null || data.fields === undefined
? ""
: data.fields
.map((data) => {
return data.key;
})
.join(", ")
}
style={{
minWidth: 125,
maxWidth: 125,
overflow: "hidden",
}}
/>
<ListItemText
style={{
maxWidth: 230,
minWidth: 230,
overflow: "hidden",
}}
primary={new Date(data.created * 1000).toISOString()}
/>
<ListItemText>
<IconButton
onClick={() => {
updateAppAuthentication(data);
}}
>
<EditIcon color="primary" />
</IconButton>
{data.defined ? (
<Tooltip
color="primary"
title="Set in EVERY workflow"
placement="top"
>
<IconButton
style={{ marginRight: 10 }}
disabled={data.defined === false}
onClick={() => {
editAuthenticationConfig(data.id);
}}
>
<SelectAllIcon
color={data.defined ? "primary" : "secondary"}
/>
</IconButton>
</Tooltip>
) : (
<Tooltip
color="primary"
title="Must edit before you can set in all workflows"
placement="top"
>
<IconButton
style={{ marginRight: 10 }}
onClick={() => {}}
>
<SelectAllIcon
color={data.defined ? "primary" : "secondary"}
/>
</IconButton>
</Tooltip>
)}
<IconButton
onClick={() => {
deleteAuthentication(data);
}}
>
<DeleteIcon color="primary" />
</IconButton>
</ListItemText>
</ListItem>
)
}
export default AuthenticationItem
@@ -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 (
<DialogContent style={{ textAlign: "center", marginTop: 50 }}>
<Typography variant="h4" id="draggable-dialog-title" style={{cursor: "move",}}>
{selectedApp.name} does not require authentication
</Typography>
</DialogContent>
);
}
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 (
<div>
<DialogTitle id="draggable-dialog-title" style={{cursor: "move",}}>
<div style={{ color: "white" }}>
Authentication for {selectedApp.name}
</div>
</DialogTitle>
<DialogContent>
<a
target="_blank"
rel="noopener noreferrer"
href="https://shuffler.io/docs/apps#authentication"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
What is app authentication?
</a>
<div />
These are required fields for authenticating with {selectedApp.name}
<div style={{ marginTop: 15 }} />
<b>Name - what is this used for?</b>
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {
color: "white",
marginLeft: "5px",
maxWidth: "95%",
height: 50,
fontSize: "1em",
},
}}
fullWidth
color="primary"
placeholder={"Auth july 2020"}
defaultValue={`Auth for ${selectedApp.name}`}
onChange={(event) => {
authenticationOption.label = event.target.value;
}}
/>
<Divider
style={{
marginTop: 15,
marginBottom: 15,
backgroundColor: "rgb(91, 96, 100)",
}}
/>
<div />
{selectedApp.authentication.parameters.map((data, index) => {
return (
<div key={index} style={{ marginTop: 10 }}>
<LockOpenIcon style={{ marginRight: 10 }} />
<b>{data.name}</b>
{data.schema !== undefined &&
data.schema !== null &&
data.schema.type === "bool" ? (
<Select
MenuProps={{
disableScrollLock: true,
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
defaultValue={"false"}
fullWidth
onChange={(e) => {
console.log("Value: ", e.target.value);
authenticationOption.fields[data.name] = e.target.value;
}}
style={{
backgroundColor: theme.palette.surfaceColor,
color: "white",
height: 50,
}}
>
<MenuItem
key={"false"}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={"false"}
>
false
</MenuItem>
<MenuItem
key={"true"}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={"true"}
>
true
</MenuItem>
</Select>
) : (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {
color: "white",
marginLeft: "5px",
maxWidth: "95%",
height: 50,
fontSize: "1em",
},
}}
fullWidth
type={
data.example !== undefined && data.example.includes("***")
? "password"
: "text"
}
color="primary"
defaultValue={
data.value !== undefined && data.value !== null
? data.value
: ""
}
placeholder={data.example}
onChange={(event) => {
authenticationOption.fields[data.name] =
event.target.value;
}}
/>
)}
</div>
);
})}
</DialogContent>
<DialogActions>
<Button
style={{ borderRadius: "0px" }}
onClick={() => {
setAuthenticationModalOpen(false);
}}
color="primary"
>
Cancel
</Button>
<Button
style={{ borderRadius: "0px" }}
onClick={() => {
setAuthenticationOptions(authenticationOption);
handleSubmitCheck();
}}
color="primary"
>
Submit
</Button>
</DialogActions>
</div>
);
};
export default AuthenticationData
+207 -115
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { import {
InputAdornment, InputAdornment,
@@ -13,6 +13,7 @@ import {
List, List,
ListItem, ListItem,
ListItemText, ListItemText,
Fade,
} from "@material-ui/core"; } from "@material-ui/core";
import { FavoriteBorder as FavoriteBorderIcon } from "@material-ui/icons"; import { FavoriteBorder as FavoriteBorderIcon } from "@material-ui/icons";
import { FixName } from "../views/Apps.jsx"; import { FixName } from "../views/Apps.jsx";
@@ -43,14 +44,15 @@ const ConfigureWorkflow = (props) => {
isCloud, isCloud,
setAuthenticationType, setAuthenticationType,
alert, alert,
showTriggers,
} = props; } = props;
const [requiredActions, setRequiredActions] = React.useState([]); const [requiredActions, setRequiredActions] = React.useState([]);
const [requiredVariables, setRequiredVariables] = React.useState([]); const [requiredVariables, setRequiredVariables] = React.useState([]);
const [requiredTriggers, setRequiredTriggers] = React.useState([]); const [requiredTriggers, setRequiredTriggers] = React.useState([]);
const [previousAuth, setPreviousAuth] = React.useState(appAuthentication); const [previousAuth, setPreviousAuth] = React.useState(appAuthentication);
const [firstLoad, setFirstLoad] = React.useState("");
const [itemChanged, setItemChanged] = React.useState(false); 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) { if (workflow === undefined || workflow === null) {
return null; return null;
@@ -94,18 +96,13 @@ const ConfigureWorkflow = (props) => {
}; };
if (firstLoad.length === 0 || firstLoad !== workflow.id) { if (firstLoad.length === 0 || firstLoad !== workflow.id) {
if (finished) {
setConfigureWorkflowModalOpen(false);
return null;
}
if (apps === undefined || apps === null || apps.length === 0) { if (apps === undefined || apps === null || apps.length === 0) {
console.log("No apps loaded: ", apps); console.log("No apps loaded: ", apps);
setConfigureWorkflowModalOpen(false); setConfigureWorkflowModalOpen(false);
return null; return null;
} }
setFirstLoad(workflow.id); setFirstLoad(workflow.id)
const newactions = []; const newactions = [];
for (var key in workflow.actions) { for (var key in workflow.actions) {
const action = workflow.actions[key]; const action = workflow.actions[key];
@@ -272,17 +269,19 @@ const ConfigureWorkflow = (props) => {
setRequiredTriggers(requiredTriggers); setRequiredTriggers(requiredTriggers);
setRequiredVariables(requiredVariables); setRequiredVariables(requiredVariables);
setRequiredActions(newactions); setRequiredActions(newactions);
} }
if (appAuthentication.length !== previousAuth.length) { if (appAuthentication.length !== previousAuth.length) {
var newactions = []; var newactions = []
for (var actionkey in requiredActions) { for (var actionkey in requiredActions) {
var newaction = requiredActions[actionkey]; var newaction = requiredActions[actionkey];
const app = newaction.app; const app = newaction.app;
for (var key in appAuthentication) { for (var key in appAuthentication) {
const auth = appAuthentication[key]; 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; newaction.auth_done = true;
break; break;
} }
@@ -502,6 +501,7 @@ const ConfigureWorkflow = (props) => {
return ( return (
<ListItem> <ListItem>
{/*
<ListItemAvatar> <ListItemAvatar>
<Avatar variant="rounded"> <Avatar variant="rounded">
<img <img
@@ -516,19 +516,28 @@ const ConfigureWorkflow = (props) => {
secondary={action.app_version} secondary={action.app_version}
style={{}} style={{}}
/> />
{action.must_authenticate ? ( */}
action.auth_done ? ( {action.must_authenticate ?
<Button color="primary" variant="outlined" onClick={() => {}}> <Button
Authenticated fullWidth
</Button> variant="contained"
) : selectedAction.app_name === action.app_name ? ( disabled={action.auth_done}
<CircularProgress /> style={{
) : ( flex: 1,
<Button textTransform: "none",
color="primary" textAlign: "left",
variant="contained" justifyContent: "flex-start",
onClick={() => { backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
setAuthenticationType( 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={() => {
setAuthenticationType(
action.app.authentication.type === "oauth2" && action.app.authentication.type === "oauth2" &&
action.app.authentication.redirect_uri !== undefined && action.app.authentication.redirect_uri !== undefined &&
action.app.authentication.redirect_uri !== null action.app.authentication.redirect_uri !== null
@@ -541,37 +550,86 @@ const ConfigureWorkflow = (props) => {
: { : {
type: "", type: "",
} }
); )
setItemChanged(true); setItemChanged(true);
setSelectedAction(action.action);
setSelectedApp(action.app); if (setSelectedAction !== undefined) {
setSelectedAction(action.action);
}
if (setSelectedApp !== undefined) {
setSelectedApp(action.app);
}
setAuthenticationModalOpen(true); setAuthenticationModalOpen(true);
}} }}
> >
Authenticate <img
</Button> alt={action.app_name}
) style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
) : null} src={action.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
{action.auth_done ? "Authenticated" : `Authenticate ${action.app_name}`}
</Typography>
</Button>
: null}
{action.must_activate ? ( {action.must_activate ? (
<Button <Button
color="primary" fullWidth
variant="contained" variant="contained"
onClick={() => { disabled={action.auth_done}
console.log("ACTION: ", action) style={{
activateApp(action.action.app_id, action.app_name, action.app_version); flex: 1,
setItemChanged(true); 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("ACTION: ", action)
activateApp(action.action.app_id, action.app_name, action.app_version);
setItemChanged(true);
}}
> >
Activate <img
alt={action.app_name}
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
src={action.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
Activate
</Typography>
</Button> </Button>
) : null} ) : null}
{action.update_version !== action.app_version ? ( {action.update_version !== action.app_version ? (
<Button <Button
color="primary" fullWidth
variant="contained" variant="contained"
style={{marginLeft: 5}} disabled={action.auth_done}
onClick={() => { 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) console.log("Set version to: ", action.update_version)
if (workflow.actions !== null) { if (workflow.actions !== null) {
@@ -592,84 +650,118 @@ const ConfigureWorkflow = (props) => {
} }
}} }}
> >
{action.update_version} <img
alt={action.app_name}
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
src={action.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10 }} variant="body1">
{action.update_version}
</Typography>
</Button> </Button>
) : null} ) : null}
</ListItem> </ListItem>
); );
}; }
// Based on the color here. Default: #f86a3e
//backgroundColor: selectedUsecaseCategory === usecase.name ? usecase.color : theme.palette.surfaceColor,
const topColor = "#f86a3e, #fc3922"
return ( return (
<div> <div>
<Typography variant="h6">{workflow.name}</Typography> <div style={{height: 125, width: "100%", background: `linear-gradient(to right, ${topColor}`, position: "relative",}}>
<Typography variant="body1" color="textSecondary"> </div>
The following configuration makes the workflow ready immediately. <div style={{margin: "25px 50px 50px 50px", maxHeight: 475, }}>
</Typography> <Typography variant="h6">{workflow.name}</Typography>
{requiredActions.length > 0 ? ( <Typography variant="body2" color="textSecondary">
<span> The following configuration makes the workflow ready immediately.
<Typography variant="body1" style={{ marginTop: 10 }}> </Typography>
Actions {requiredActions.length > 0 ? (
</Typography> <span>
<List> <Typography variant="body1" style={{ marginTop: 10 }}>
{requiredActions.map((data, index) => { Required Actions
return <AppSection key={index} action={data} />; </Typography>
})} <List>
</List> {requiredActions.map((data, index) => {
</span> return (
) : null} <AppSection key={index} action={data} />
)
})}
</List>
</span>
) : null}
{requiredVariables.length > 0 ? ( {requiredVariables.length > 0 ? (
<span> <span>
<Typography variant="body1" style={{ marginTop: 10 }}> <Typography variant="body1" style={{ marginTop: 10 }}>
Variables Variables
</Typography> </Typography>
<List> <List>
{requiredVariables.map((data, index) => { {requiredVariables.map((data, index) => {
return <VariableSection key={index} variable={data} />; return <VariableSection key={index} variable={data} />;
})} })}
</List> </List>
</span> </span>
) : null} ) : null}
{requiredTriggers.length > 0 ? ( {requiredTriggers.length > 0 && showTriggers !== false ? (
<span> <span>
<Typography variant="body1" style={{ marginTop: 10 }}> <Typography variant="body1" style={{ marginTop: 10 }}>
Triggers Triggers
</Typography> </Typography>
<List> <List>
{requiredTriggers.map((data, index) => { {requiredTriggers.map((data, index) => {
return <TriggerSection key={index} trigger={data} />; return <TriggerSection key={index} trigger={data} />;
})} })}
</List> </List>
</span> </span>
) : null} ) : null}
<div style={{ textAlign: "center", display: "flex", marginTop: 20 }}>
<ButtonGroup style={{ margin: "auto" }}> <div style={{ textAlign: "center", display: "flex", marginTop: 20 }}>
{/* {showFinalizeAnimation ?
<Button color="primary" variant={"outlined"} style={{ <img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, margin: "auto",}} onLoad={() => {
}} onClick={() => { console.log("Img loaded.")
setConfigureWorkflowModalOpen(false) setTimeout(() => {
}}> console.log("Img closing.")
Skip setConfigureWorkflowModalOpen(false);
</Button> }, 1250)
*/}
<Button }}/>
color="primary" :
variant={itemChanged ? "contained" : "outlined"} <ButtonGroup style={{ margin: "auto" }}>
style={{}} {/*
onClick={() => { <Button color="primary" variant={"outlined"} style={{
if (itemChanged) { }} onClick={() => {
saveWorkflow(workflow); setConfigureWorkflowModalOpen(false)
window.location.reload(); }}>
} else { Skip
setConfigureWorkflowModalOpen(false); </Button>
} */}
}} <Button
> color="textSecondary"
Close window variant={"outlined"}
</Button> style={{}}
</ButtonGroup> onClick={() => {
</div> setShowFinalizeAnimation(true)
setTimeout(() => {
if (itemChanged) {
if (saveWorkflow !== undefined) {
saveWorkflow(workflow);
window.location.reload();
}
} else {
}
}, 1000)
}}
>
Finalize
</Button>
</ButtonGroup>
}
</div>
</div>
</div> </div>
); );
}; };
+374 -244
View File
@@ -2,6 +2,7 @@ import React, { useEffect, useContext } from "react";
import theme from '../theme'; import theme from '../theme';
import { isMobile } from "react-device-detect" import { isMobile } from "react-device-detect"
import ChipInput from "material-ui-chip-input"; import ChipInput from "material-ui-chip-input";
import UsecaseSearch from "../components/UsecaseSearch.jsx"
import { import {
Badge, Badge,
@@ -45,19 +46,91 @@ import {
} from "@material-ui/icons"; } from "@material-ui/icons";
const EditWorkflow = (props) => { 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 [submitLoading, setSubmitLoading] = React.useState(false);
const [selectedUsecases, setSelectedUsecases] = React.useState([]);
const [showMoreClicked, setShowMoreClicked] = React.useState(false); const [showMoreClicked, setShowMoreClicked] = React.useState(false);
const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) const [innerWorkflow, setInnerWorkflow] = React.useState(workflow)
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove 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 [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 return null
} }
const newWorkflow = isEditing === true ? false : true
var upload = ""; var upload = "";
var total_count = 0 var total_count = 0
@@ -71,264 +144,299 @@ const EditWorkflow = (props) => {
style: { style: {
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
color: "white", color: "white",
minWidth: isMobile ? "90%" : "800px", minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
maxWidth: isMobile ? "90%" : "800px", maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
minHeight: 400,
}, },
}} }}
> >
<DialogTitle> <DialogTitle style={{padding: 30, paddingBottom: 0, zIndex: 1000,}}>
<div style={{ color: "rgba(255,255,255,0.9)" }}> <div style={{display: "flex"}}>
{workflow.id !== undefined ? "Editing" : "New"} workflow <div style={{flex: 1, color: "rgba(255,255,255,0.9)" }}>
{showUpload === true ? <Typography variant="h6">
<div style={{ float: "right" }}> {newWorkflow ? "New" : "Editing"} workflow
<Tooltip color="primary" title={"Import manually"} placement="top"> </Typography>
<Button <Typography variant="body2" color="textSecondary" style={{maxWidth: 440,}}>
color="primary" Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
style={{}} </Typography>
variant="text" {showUpload === true ?
onClick={() => upload.click()} <div style={{ float: "right" }}>
> <Tooltip color="primary" title={"Import manually"} placement="top">
<PublishIcon /> <Button
</Button> color="primary"
</Tooltip> style={{}}
</div> variant="text"
onClick={() => upload.click()}
>
<PublishIcon />
</Button>
</Tooltip>
</div>
: null}
</div>
{newWorkflow === true ?
<div style={{flex: 1, marginLeft: 45, }}>
<Typography variant="h6">
Use a Template
</Typography>
<Typography variant="body2" color="textSecondary" style={{maxWidth: 440,}}>
Start your workflow from our templating system. This uses publied workflows from our <a href="/creators" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Creators</a> to generate full Usecases or parts of your Workflow.
</Typography>
</div>
: null} : null}
</div> </div>
</DialogTitle> </DialogTitle>
<FormControl> <FormControl>
<DialogContent> <DialogContent style={{paddingTop: 10, display: "flex", minHeight: 350, zIndex: 1001, }}>
<TextField <div style={{minWidth: newWorkflow ? 450 : 500, maxWidth: newWorkflow ? 450 : 500, }}>
onBlur={(event) => { <TextField
innerWorkflow.name = event.target.value onBlur={(event) => {
}} setName(event.target.value)
InputProps={{
style: {
color: "white",
},
}}
color="primary"
placeholder="Name"
required
margin="dense"
defaultValue={innerWorkflow.name}
label="Name"
autoFocus
fullWidth
/>
<TextField
onBlur={(event) => {
innerWorkflow.description = event.target.value
setInnerWorkflow(innerWorkflow)
}}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
<div style={{display: "flex", marginTop: 10, }}>
<ChipInput
style={{ flex: 1}}
InputProps={{
style: {
color: "white",
},
}} }}
placeholder="Tags" InputProps={{
color="primary" style: {
fullWidth color: "white",
value={newWorkflowTags} },
onAdd={(chip) => { }}
newWorkflowTags.push(chip); color="primary"
setNewWorkflowTags(newWorkflowTags); placeholder="Name"
required
margin="dense"
defaultValue={innerWorkflow.name}
label="Name"
autoFocus
fullWidth
/>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}} }}
onDelete={(chip, index) => { InputProps={{
newWorkflowTags.splice(index, 1); style: {
setNewWorkflowTags(newWorkflowTags); color: "white",
}} },
/> }}
{usecases !== null && usecases !== undefined && usecases.length > 0 ? maxRows={4}
<FormControl style={{flex: 1, marginLeft: 5, }}> color="primary"
<InputLabel htmlFor="grouped-select-usecase">Usecases</InputLabel> defaultValue={innerWorkflow.description}
<Select placeholder="Description"
defaultValue="" multiline
id="grouped-select" label="Description"
label="Matching Usecase" margin="dense"
multiple fullWidth
value={selectedUsecases} />
renderValue={(selected) => selected.join(', ')} <div style={{display: "flex", marginTop: 10, }}>
onChange={(event) => { <ChipInput
console.log("Changed: ", event) style={{ flex: 1, maxHeight: 40, marginTop: 12, overflow: "auto", }}
}} InputProps={{
> style: {
<MenuItem value=""> color: "white",
<em>None</em> },
</MenuItem> }}
{usecases.map((usecase, index) => { placeholder="Tags"
//console.log(usecase) color="primary"
return ( fullWidth
<span key={index}> value={newWorkflowTags}
<ListSubheader onAdd={(chip) => {
style={{color: usecase.color}} newWorkflowTags.push(chip);
> setNewWorkflowTags(newWorkflowTags);
{usecase.name} }}
</ListSubheader> onDelete={(chip, index) => {
{usecase.list.map((subcase, subindex) => { newWorkflowTags.splice(index, 1);
//console.log(subcase) setNewWorkflowTags(newWorkflowTags);
total_count += 1 }}
return ( />
<MenuItem key={subindex} value={total_count} onClick={(event) => { {usecases !== null && usecases !== undefined && usecases.length > 0 ?
if (selectedUsecases.includes(subcase.name)) { <FormControl style={{flex: 1, marginLeft: 5, }}>
const itemIndex = selectedUsecases.indexOf(subcase.name) <InputLabel htmlFor="grouped-select-usecase">Usecases</InputLabel>
if (itemIndex > -1) { <Select
selectedUsecases.splice(itemIndex, 1) defaultValue=""
id="grouped-select"
label="Matching Usecase"
multiple
value={selectedUsecases}
renderValue={(selected) => selected.join(', ')}
onChange={(event) => {
console.log("Changed: ", event)
}}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{usecases.map((usecase, index) => {
//console.log(usecase)
return (
<span key={index}>
<ListSubheader
style={{color: usecase.color}}
>
{usecase.name}
</ListSubheader>
{usecase.list.map((subcase, subindex) => {
//console.log(subcase)
total_count += 1
return (
<MenuItem key={subindex} value={total_count} onClick={(event) => {
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()); setUpdate(Math.random());
setSelectedUsecases(selectedUsecases) setSelectedUsecases(selectedUsecases)
}}> }}>
<Checkbox style={{color: selectedUsecases.includes(subcase.name) ? usecase.color : theme.palette.inputColor}} checked={selectedUsecases.includes(subcase.name)} /> <Checkbox style={{color: selectedUsecases.includes(subcase.name) ? usecase.color : theme.palette.inputColor}} checked={selectedUsecases.includes(subcase.name)} />
<ListItemText primary={subcase.name} /> <ListItemText primary={subcase.name} />
</MenuItem> </MenuItem>
) )
})} })}
</span> </span>
) )
})} })}
</Select> </Select>
</FormControl> </FormControl>
: null}
</div>
{showMoreClicked === true ?
<span style={{marginTop: 25, }}>
<FormControl style={{marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Status</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.status}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
}}
>
<FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="production" control={<Radio />} label="Production" />
</RadioGroup>
</FormControl>
<div />
<FormControl style={{marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Type</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.workflow_type}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
}}
>
<FormControlLabel value="trigger" control={<Radio />} label="Trigger" />
<FormControlLabel value="subflow" control={<Radio />} label="Subflow" />
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" />
</RadioGroup>
</FormControl>
<TextField
onBlur={(event) => {
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
/>
<TextField
onBlur={(event) => {
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
/>
<TextField
onBlur={(event) => {
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
/>
</span>
: null} : null}
<Tooltip color="primary" title={"Add more details"} placement="top">
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
</IconButton>
</Tooltip>
</div> </div>
{newWorkflow === true ?
{showMoreClicked === true ? <div style={{marginLeft: 50, maxWidth: 400, minWidth: 400, position: "relative",}}>
<span style={{marginTop: 25, }}> <UsecaseSearch
globalUrl={globalUrl}
<FormControl style={{marginTop: 15, }}> appFramework={appFramework}
<FormLabel id="demo-row-radio-buttons-group-label">Status</FormLabel> defaultSearch={undefined}
<RadioGroup apps={undefined}
row setFoundWorkflowId={setFoundWorkflowId}
aria-labelledby="demo-row-radio-buttons-group-label" userdata={userdata}
name="row-radio-buttons-group"
defaultValue={innerWorkflow.status}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
}}
>
<FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="production" control={<Radio />} label="Production" />
</RadioGroup>
</FormControl>
<div />
<FormControl style={{marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Type</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.workflow_type}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
}}
>
<FormControlLabel value="trigger" control={<Radio />} label="Trigger" />
<FormControlLabel value="subflow" control={<Radio />} label="Subflow" />
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" />
</RadioGroup>
</FormControl>
<TextField
onBlur={(event) => {
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
/> />
<TextField </div>
onBlur={(event) => {
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
/>
<TextField
onBlur={(event) => {
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
/>
</span>
: null} : null}
<Tooltip color="primary" title={"Add more details"} placement="top">
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Tooltip>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
style={{}} style={{}}
onClick={() => { onClick={() => {
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
setModalOpen(false) setModalOpen(false)
}} }}
color="primary" color="primary"
@@ -338,13 +446,35 @@ const EditWorkflow = (props) => {
<Button <Button
variant="contained" variant="contained"
style={{}} style={{}}
disabled={innerWorkflow.name.length === 0} disabled={name.length === 0}
onClick={() => { onClick={() => {
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) { if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags 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) setModalOpen(false)
}} }}
color="primary" color="primary"
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import {isMobile} from "react-device-detect"; 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 {Link} from 'react-router-dom';
import ReactGA from 'react-ga'; import ReactGA from 'react-ga';
@@ -169,7 +169,7 @@ const LandingpageUsecases = (props) => {
</div> </div>
{isMobile ? null : {isMobile ? null :
<div style={{marginLeft: 200, marginTop: 125, zIndex: 1000}}> <div style={{marginLeft: 200, marginTop: 125, zIndex: 1000}}>
<DetectionFramework showOptions={false} selectedOption={selectedUsecase} rolling={true} /> <AppFramework showOptions={false} selectedOption={selectedUsecase} rolling={true} />
</div> </div>
} }
{isMobile ? null : {isMobile ? null :
+113 -68
View File
@@ -1,5 +1,6 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { useTheme } from "@material-ui/core/styles"; import { useTheme } from "@material-ui/core/styles";
import theme from '../theme';
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { import {
@@ -62,6 +63,7 @@ const registeredApps = [
"outlook_office365", "outlook_office365",
"microsoft_teams", "microsoft_teams",
"microsoft_teams_user_access", "microsoft_teams_user_access",
"todoist",
] ]
const AuthenticationOauth2 = (props) => { const AuthenticationOauth2 = (props) => {
@@ -77,8 +79,8 @@ const AuthenticationOauth2 = (props) => {
setNewAppAuth, setNewAppAuth,
setAuthenticationModalOpen, setAuthenticationModalOpen,
isCloud, isCloud,
autoAuth,
} = props; } = props;
const theme = useTheme();
//const [update, setUpdate] = React.useState("|") //const [update, setUpdate] = React.useState("|")
const [defaultConfigSet, setDefaultConfigSet] = React.useState( const [defaultConfigSet, setDefaultConfigSet] = React.useState(
@@ -88,7 +90,7 @@ const AuthenticationOauth2 = (props) => {
authenticationType.client_secret !== undefined && authenticationType.client_secret !== undefined &&
authenticationType.client_secret !== null && authenticationType.client_secret !== null &&
authenticationType.client_secret.length > 0 authenticationType.client_secret.length > 0
); );
const [clientId, setClientId] = React.useState( const [clientId, setClientId] = React.useState(
defaultConfigSet ? authenticationType.client_id : "" defaultConfigSet ? authenticationType.client_id : ""
@@ -123,6 +125,73 @@ const AuthenticationOauth2 = (props) => {
return null; 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) => { const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => {
setButtonClicked(true); setButtonClicked(true);
console.log("SCOPES: ", scopes); console.log("SCOPES: ", scopes);
@@ -164,19 +233,15 @@ const AuthenticationOauth2 = (props) => {
state += `%26refresh_uri%3d${authentication_url}`; 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 // 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`; //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 // 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` //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 // FIXME: Awful, but works for prototyping
// How can we get a callback properly realtime? // How can we get a callback properly realtime?
@@ -188,12 +253,20 @@ const AuthenticationOauth2 = (props) => {
var open = true; var open = true;
const timer = setInterval(() => { const timer = setInterval(() => {
if (newwin.closed) { if (newwin.closed) {
console.log("Closed!")
if (setAuthenticationModalOpen !== undefined) {
setAuthenticationModalOpen(false)
}
setButtonClicked(false); setButtonClicked(false);
clearInterval(timer); clearInterval(timer);
//alert('"Secure Payment" window closed!'); //alert('"Secure Payment" window closed!');
getAppAuthentication(true, true); getAppAuthentication(true, true);
} } else {
console.log("Not closed")
}
}, 1000); }, 1000);
//do { //do {
// setTimeout(() => { // setTimeout(() => {
@@ -361,76 +434,48 @@ const AuthenticationOauth2 = (props) => {
{isCloud && registeredApps.includes(selectedApp.name.toLowerCase()) ? {isCloud && registeredApps.includes(selectedApp.name.toLowerCase()) ?
<span> <span>
<Button <Button
style={{ fullWidth
marginBottom: 20, variant="contained"
marginTop: 20, style={{
marginBottom: 20,
marginTop: 20,
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: "#ffffff",
color: "#2f2f2f",
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
}} minWidth: 350,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
disabled={ disabled={
clientSecret.length > 0 || clientId.length > 0 clientSecret.length > 0 || clientId.length > 0
} }
variant="contained"
fullWidth fullWidth
onClick={() => { onClick={() => {
// Hardcode some stuff? // Hardcode some stuff?
// This could prolly be added to the app itself with a "default" client ID // This could prolly be added to the app itself with a "default" client ID
console.log("APP: ", selectedApp) startOauth2Request()
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"]
)
}
}} }}
color="primary" color="primary"
> >
{buttonClicked ? ( {buttonClicked ? (
<CircularProgress style={{ color: "white" }} /> <CircularProgress style={{ color: "#f86a3e", width: 45, height: 45, margin: "auto", }} />
) : ( ) : (
"Auto-authenticate" <span style={{display: "flex"}}>
<img
alt={selectedAction.app_name}
style={{ margin: 4, minHeight: 30, maxHeight: 30, borderRadius: theme.palette.borderRadius, }}
src={selectedAction.large_image}
/>
<Typography style={{ margin: 0, marginLeft: 10, marginTop: 5,}} variant="body1">
Auto-Authenticate
</Typography>
</span>
)} )}
</Button> </Button>
<Typography style={{textAlign: "center", marginTop: 0, marginBottom: 10, }}> <Typography style={{textAlign: "center", marginTop: 0, marginBottom: 10, }}>
@@ -1,6 +1,6 @@
import React, {useState } from 'react'; import React, {useState } from 'react';
import {isMobile} from "react-device-detect"; 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 {Link} from 'react-router-dom';
import ReactGA from 'react-ga'; import ReactGA from 'react-ga';
@@ -56,6 +56,8 @@ export const securityFramework = [
] ]
const LandingpageUsecases = (props) => { const LandingpageUsecases = (props) => {
const { userdata } = props
const [selectedUsecase, setSelectedUsecase] = useState("Phishing") const [selectedUsecase, setSelectedUsecase] = useState("Phishing")
const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases) const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases)
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)" const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
@@ -169,7 +171,12 @@ const LandingpageUsecases = (props) => {
</div> </div>
{isMobile ? null : {isMobile ? null :
<div style={{marginLeft: 200, marginTop: 125, zIndex: 1000}}> <div style={{marginLeft: 200, marginTop: 125, zIndex: 1000}}>
<DetectionFramework showOptions={false} selectedOption={selectedUsecase} rolling={true} /> <AppFramework
userdata={userdata}
showOptions={false}
selectedOption={selectedUsecase}
rolling={true}
/>
</div> </div>
} }
{isMobile ? null : {isMobile ? null :
@@ -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 ? <DoneIcon /> : <AutoFixHighIcon />
if (finished) {
return null
}
// Simple visual of the usecase
return (
<Tooltip
title={`Try usecase "${usecasename}"`}
placement="top"
style={{ zIndex: 10011 }}
>
<div key={index} style={{cursor: finished ? "auto" : "pointer", marginTop: 10, padding: 10, borderRadius: theme.palette.borderRadius, border: `1px solid ${bordercolor}`, display: "flex", backgroundColor: hovering === true ? theme.palette.inputColor : theme.palette.surfaceColor, }} onMouseOver={() => {
setHovering(true)
}} onMouseOut={() => {
setHovering(false)
}} onClick={() => {
if (isCloud) {
ReactGA.event({
category: "welcome",
action: "click_suggested_workflow",
label: usecasename,
})
}
console.log("Try usecase ", usecasename)
setUsecaseSearchType(usecase.type)
setUsecaseSearch(usecasename)
}}>
<div style={{flex: 10}}>
<Typography variant="body2">
{usecasename}
</Typography>
<div style={{display: "flex", marginTop: 5, }}>
<img alt={srcimage.name} src={srcimage.large_image} style={{borderRadius: 20, height: 30, width: 30, marginRight: 15, }}/>
<img alt={dstimage.name} src={dstimage.large_image} style={{borderRadius: 20, height: 30, width: 30, }}/>
</div>
</div>
<div style={{flex: 1}}>
{selectedIcon}
</div>
</div>
</Tooltip>
)
}
//<Paper style={{width: 275, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
return (
<Paper style={{margin: "auto", position: "relative", backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette.borderRadius, zIndex: foundZindex, border: "1px solid rgba(255,255,255,0.2)"}}>
<Dialog
open={usecaseSearch.length > 0 && usecaseSearchType.length > 0}
onClose={() => {
finishedUsecases.push(usecaseSearch)
setFinishedUsecases(finishedUsecases)
setUsecaseSearch("")
setUsecaseSearchType("")
}}
PaperProps={{
style: {
pointerEvents: "auto",
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 450,
padding: 50,
overflow: "hidden",
zIndex: 10012,
border: theme.palette.defaultBorder,
},
}}
>
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 14,
right: 18,
color: "grey",
}}
onClick={() => {
finishedUsecases.push(usecaseSearch)
setFinishedUsecases(finishedUsecases)
setUsecaseSearch("")
setUsecaseSearchType("")
}}
>
<CloseIcon />
</IconButton>
<UsecaseSearch
globalUrl={globalUrl}
defaultSearch={usecaseSearchType}
usecaseSearch={usecaseSearch}
appFramework={frameworkData}
userdata={userdata}
autotry={true}
setCloseWindow={setCloseWindow}
setUsecaseSearch={setUsecaseSearch}
apps={apps}
/>
</Dialog>
<div style={{minWidth: 250, maxWidth: 250, padding: 15, borderRadius: theme.palette.borderRadius, position: "relative", }}>
<Typography variant="body1" style={{textAlign: "center"}}>
Suggested Workflows ({finishedUsecases.length}/{usecaseSuggestions.length})
</Typography>
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 8,
right: 8,
color: "grey",
padding: 2,
}}
onClick={() => {
if (setUsecaseSuggestions !== undefined) {
setUsecaseSuggestions([])
}
}}
>
<CloseIcon style={{height: 18, width: 18, }} />
</IconButton>
{usecaseSuggestions.map((usecase, index) => {
return (
<IndividualUsecase
key={index}
usecase={usecase}
index={index}
/>
)
})}
</div>
</Paper>
)
}
export default SuggestedWorkflows;
File diff suppressed because it is too large Load Diff
@@ -1,17 +1,23 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Stepper from "@material-ui/core/Stepper"; import ReactGA from 'react-ga';
import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Checkbox from '@mui/material/Checkbox'; import Checkbox from '@mui/material/Checkbox';
import AliceCarousel from 'react-alice-carousel';
import 'react-alice-carousel/lib/alice-carousel.css';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import EmailIcon from '@mui/icons-material/Email'; import EmailIcon from '@mui/icons-material/Email';
import NewReleasesIcon from '@mui/icons-material/NewReleases'; import NewReleasesIcon from '@mui/icons-material/NewReleases';
import ExtensionIcon from '@mui/icons-material/Extension'; import ExtensionIcon from '@mui/icons-material/Extension';
import LightbulbIcon from '@mui/icons-material/Lightbulb';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import theme from '../theme'; import theme from '../theme';
import { import {
Fade,
IconButton,
FormGroup, FormGroup,
FormControl, FormControl,
InputLabel, InputLabel,
@@ -24,23 +30,91 @@ import {
Typography, Typography,
TextField, TextField,
Zoom, Zoom,
List,
ListItem,
ListItemText,
Divider,
Tooltip,
Chip,
} from "@material-ui/core"; } from "@material-ui/core";
import { useAlert } from "react-alert";
import { useNavigate, Link } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
import WorkflowSearch from '../components/Workflowsearch.jsx'; import WorkflowSearch from '../components/Workflowsearch.jsx';
import AuthenticationItem from '../components/AuthenticationItem.jsx';
import WorkflowPaper from "../components/WorkflowPaper.jsx" import WorkflowPaper from "../components/WorkflowPaper.jsx"
import UsecaseSearch from "../components/UsecaseSearch.jsx"
const responsive = {
0: { items: 1 },
};
const WelcomeForm = (props) => { const WelcomeForm = (props) => {
const { userdata, globalUrl, discoveryWrapper, setDiscoveryWrapper } = props const { userdata, globalUrl, discoveryWrapper, setDiscoveryWrapper, appFramework, getFramework, activeStep, setActiveStep, steps, skipped, setSkipped, getApps, apps, handleSetSearch, usecaseButtons, defaultSearch, setDefaultSearch, selectionOpen, setSelectionOpen, } = props
const usecaseItems = [
<div style={{minWidth: "95%", maxWidth: "95%", marginLeft: 5, marginRight: 5, }}>
<UsecaseSearch
globalUrl={globalUrl}
defaultSearch={"Phishing"}
appFramework={appFramework}
apps={apps}
getFramework={getFramework}
userdata={userdata}
/>
</div>
,
<div style={{minWidth: "95%", maxWidth: "95%", marginLeft: 5, marginRight: 5, }}>
<UsecaseSearch
globalUrl={globalUrl}
defaultSearch={"Enrichment"}
appFramework={appFramework}
apps={apps}
getFramework={getFramework}
userdata={userdata}
/>
</div>
,
<div style={{minWidth: "95%", maxWidth: "95%", marginLeft: 5, marginRight: 5, }}>
<UsecaseSearch
globalUrl={globalUrl}
defaultSearch={"Enrichment"}
usecaseSearch={"SIEM alert enrichment"}
appFramework={appFramework}
apps={apps}
getFramework={getFramework}
userdata={userdata}
/>
</div>
,
<div style={{minWidth: "95%", maxWidth: "95%", marginLeft: 5, marginRight: 5, }}>
<UsecaseSearch
globalUrl={globalUrl}
defaultSearch={"Build your own"}
appFramework={appFramework}
apps={apps}
getFramework={getFramework}
userdata={userdata}
/>
</div>
]
const [activeStep, setActiveStep] = React.useState(0);
const [skipped, setSkipped] = React.useState(new Set());
const [discoveryData, setDiscoveryData] = React.useState({}) const [discoveryData, setDiscoveryData] = React.useState({})
const [name, setName] = React.useState("") const [name, setName] = React.useState("")
const [orgName, setOrgName] = React.useState("") const [orgName, setOrgName] = React.useState("")
const [role, setRole] = React.useState("") const [role, setRole] = React.useState("")
const [orgType, setOrgType] = React.useState("") const [orgType, setOrgType] = React.useState("")
const [finishedApps, setFinishedApps] = React.useState([]) const [finishedApps, setFinishedApps] = React.useState([])
const [authentication, setAuthentication] = React.useState([]);
const [newSelectedApp, setNewSelectedApp] = React.useState({})
const [thumbIndex, setThumbIndex] = useState(0);
const [thumbAnimation, setThumbAnimation] = useState(false);
const [clickdiff, setclickdiff] = useState(0);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const alert = useAlert();
let navigate = useNavigate(); let navigate = useNavigate();
const onNodeSelect = (label) => { const onNodeSelect = (label) => {
@@ -77,8 +151,7 @@ const WelcomeForm = (props) => {
} }
}, [discoveryWrapper]) }, [discoveryWrapper])
useEffect(() => { useEffect(() => {
if ( if (
window.location.search !== undefined && window.location.search !== undefined &&
window.location.search !== null window.location.search !== null
@@ -87,24 +160,20 @@ const WelcomeForm = (props) => {
const params = Object.fromEntries(urlSearchParams.entries()); const params = Object.fromEntries(urlSearchParams.entries());
const foundTab = params["tab"]; const foundTab = params["tab"];
if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) { if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) {
setActiveStep(foundTab-1) if (foundTab === 3 || foundTab === "3") {
console.log("SET SEARCH!!")
//setclickdiff(240)
}
} else { } else {
navigate(`/welcome?tab=1`) //navigate(`/welcome?tab=1`)
} }
} }
}, []) }, [])
const steps = getSteps();
const isStepOptional = step => { const isStepOptional = step => {
return step === 1 return step === 1
} }
const isStepSkipped = step => {
return skipped.has(step)
}
const sendUserUpdate = (name, role, userId) => { const sendUserUpdate = (name, role, userId) => {
const data = { const data = {
"tutorial": "welcome", "tutorial": "welcome",
@@ -177,7 +246,6 @@ const WelcomeForm = (props) => {
console.log("Update of org failed") console.log("Update of org failed")
//alert.error("Failed updating org: ", responseJson.reason); //alert.error("Failed updating org: ", responseJson.reason);
} else { } else {
console.log("Update success!")
//alert.success("Successfully edited org!"); //alert.success("Successfully edited org!");
} }
}) })
@@ -221,12 +289,25 @@ const WelcomeForm = (props) => {
) )
} }
const isStepSkipped = step => {
return skipped.has(step)
}
const handleNext = () => { const handleNext = () => {
setDefaultSearch("") setDefaultSearch("")
if (activeStep === 0) { if (activeStep === 0) {
console.log("Should send basic information about org (fetch)") console.log("Should send basic information about org (fetch)")
setclickdiff(0)
navigate(`/welcome?tab=2`) navigate(`/welcome?tab=2`)
if (isCloud) {
ReactGA.event({
category: "welcome",
action: "click_page_one_next",
label: "",
})
}
if (userdata.active_org !== undefined && userdata.active_org.id !== undefined && userdata.active_org.id !== null && userdata.active_org.id.length > 0) { if (userdata.active_org !== undefined && userdata.active_org.id !== undefined && userdata.active_org.id !== null && userdata.active_org.id.length > 0) {
sendOrgUpdate(orgName, orgType, userdata.active_org.id, "") sendOrgUpdate(orgName, orgType, userdata.active_org.id, "")
@@ -240,11 +321,22 @@ const WelcomeForm = (props) => {
console.log("Should send secondary info about apps and other things") console.log("Should send secondary info about apps and other things")
setDiscoveryWrapper({}) setDiscoveryWrapper({})
setclickdiff(240)
//setclickdiff(0)
navigate(`/welcome?tab=3`) navigate(`/welcome?tab=3`)
//handleSetSearch("Enrichment", "2. Enrich")
handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase)
getApps()
// Make sure it's up to date
if (getFramework !== undefined) {
getFramework()
}
} else if (activeStep === 2) { } else if (activeStep === 2) {
console.log("Should send third page with workflows activated and the like") console.log("Should send third page with workflows activated and the like")
} }
let newSkipped = skipped; let newSkipped = skipped;
if (isStepSkipped(activeStep)) { if (isStepSkipped(activeStep)) {
newSkipped = new Set(newSkipped.values()); newSkipped = new Set(newSkipped.values());
@@ -253,11 +345,27 @@ const WelcomeForm = (props) => {
setActiveStep(prevActiveStep => prevActiveStep + 1); setActiveStep(prevActiveStep => prevActiveStep + 1);
setSkipped(newSkipped); setSkipped(newSkipped);
}; }
const handleBack = () => { const handleBack = () => {
setActiveStep(prevActiveStep => prevActiveStep - 1); setActiveStep(prevActiveStep => prevActiveStep - 1);
if (activeStep === 2) {
setDiscoveryWrapper({})
if (getFramework !== undefined) {
getFramework()
}
setclickdiff(0)
navigate("/welcome?tab=2")
} else if (activeStep === 1) {
//setclickdiff(0)
navigate("/welcome?tab=1")
}
}; };
const handleSkip = () => { const handleSkip = () => {
setclickdiff(240)
if (!isStepOptional(activeStep)) { if (!isStepOptional(activeStep)) {
throw new Error("You can't skip a step that isn't optional."); throw new Error("You can't skip a step that isn't optional.");
} }
@@ -271,27 +379,16 @@ const WelcomeForm = (props) => {
const handleReset = () => { const handleReset = () => {
setActiveStep(0); setActiveStep(0);
}; };
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const [selectionOpen, setSelectionOpen] = React.useState(false)
const [newSelectedApp, setNewSelectedApp] = React.useState({})
const [defaultSearch, setDefaultSearch] = React.useState("")
useEffect(() => { useEffect(() => {
console.log("Selected app changed (effect)") console.log("Selected app changed (effect)")
}, [newSelectedApp]) }, [newSelectedApp])
function getSteps() {
return [
"Help us get to know you",
"Discover integrations",
"Personalize Usecases",
]
}
//const buttonWidth = 145 //const buttonWidth = 145
const buttonWidth = 450 const buttonWidth = 450
const buttonMargin = 10 const buttonMargin = 10
const sizing = 435 const sizing = 475
const buttonStyle = { const buttonStyle = {
flex: 1, flex: 1,
width: "100%", width: "100%",
@@ -300,6 +397,23 @@ const WelcomeForm = (props) => {
fontSize: 18, fontSize: 18,
} }
const slideNext = () => {
if (!thumbAnimation && thumbIndex < usecaseItems.length - 1) {
//handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase)
setThumbIndex(thumbIndex + 1);
} else if (!thumbAnimation && thumbIndex === usecaseItems.length - 1) {
setThumbIndex(0)
}
};
const slidePrev = () => {
if (!thumbAnimation && thumbIndex > 0) {
setThumbIndex(thumbIndex - 1);
} else if (!thumbAnimation && thumbIndex === 0) {
setThumbIndex(usecaseItems.length-1)
}
};
const newButtonStyle = { const newButtonStyle = {
padding: 22, padding: 22,
flex: 1, flex: 1,
@@ -307,22 +421,20 @@ const WelcomeForm = (props) => {
minWidth: buttonWidth, minWidth: buttonWidth,
maxWidth: buttonWidth, maxWidth: buttonWidth,
} }
const getStepContent = (step) => { const getStepContent = (step) => {
switch (step) { switch (step) {
case 0: case 0:
return ( return (
<Grid container spacing={1} style={{width: "100%", marginTop: 20, minHeight: sizing, maxHeight: sizing, }}> <Fade in={true}>
<Grid container spacing={1} style={{margin: "auto", maxWidth: 500, minWidth: 500, minHeight: sizing, maxHeight: sizing, }}>
{/*isCloud ? null : {/*isCloud ? null :
<Typography variant="body1" style={{marginLeft: 8, marginTop: 10, marginRight: 30, }} color="textSecondary"> <Typography variant="body1" style={{marginLeft: 8, marginTop: 10, marginRight: 30, }} color="textSecondary">
This data will be used within the product and NOT be shared unless <a href="https://shuffler.io/docs/organizations#cloud_synchronization" target="_blank" rel="norefferer" style={{color: "#f86a3e", textDecoration: "none"}}>cloud synchronization</a> is configured. This data will be used within the product and NOT be shared unless <a href="https://shuffler.io/docs/organizations#cloud_synchronization" target="_blank" rel="norefferer" style={{color: "#f86a3e", textDecoration: "none"}}>cloud synchronization</a> is configured.
</Typography> </Typography>
*/} */}
<Typography variant="h6" style={{marginLeft: 8, marginTop: 10, marginRight: 30, }} color="textSecondary">
Welcome to Shuffle!
</Typography>
<Typography variant="body1" style={{marginLeft: 8, marginTop: 10, marginRight: 30, }} color="textSecondary"> <Typography variant="body1" style={{marginLeft: 8, marginTop: 10, marginRight: 30, }} color="textSecondary">
We need some more information in order to understand how we best can help you find relevant Usecases in Shuffle. First, we need some more information in order to understand how we best can help you find relevant Usecases. This is optional, but highly encouraged.
</Typography> </Typography>
<Grid item xs={11} style={{marginTop: 16, padding: 0,}}> <Grid item xs={11} style={{marginTop: 16, padding: 0,}}>
<TextField <TextField
@@ -395,12 +507,20 @@ const WelcomeForm = (props) => {
</FormControl> </FormControl>
</Grid> </Grid>
</Grid> </Grid>
</Fade>
) )
case 1: case 1:
return ( return (
<div style={{minHeight: sizing, maxHeight: sizing, marginTop: 20,}}> <Fade in={true}>
<div style={{minHeight: sizing, maxHeight: sizing, marginTop: 20, maxWidth: 500, }}>
<Typography variant="body1" style={{marginLeft: 8, marginTop: 25, marginRight: 30, marginBottom: 0, }} color="textSecondary"> <Typography variant="body1" style={{marginLeft: 8, marginTop: 25, marginRight: 30, marginBottom: 0, }} color="textSecondary">
Find your apps, then we'll help you find relevant workflows. Can't find what you're looking for? Contact support: <a href="mailto:support@shuffler.io" style={{color: "#f86a3e", textDecoration: "none"}}>support@shuffler.io</a> Clicks the buttons below to find your apps, then we will help you find relevant workflows. Can't find your app? <span style={{color: "#f86a3e", cursor: "pointer"}} onClick={() => {
if (window.drift !== undefined) {
window.drift.api.startInteraction({ interactionId: 340043 })
} else {
console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
}
}}>Contact our App Developers!</span>
</Typography> </Typography>
{/*The app framework helps us access and authenticate the most important APIs for you. */} {/*The app framework helps us access and authenticate the most important APIs for you. */}
@@ -420,11 +540,16 @@ const WelcomeForm = (props) => {
*/} */}
<Grid item xs={11} style={{marginTop: 25, }}> <Grid item xs={11} style={{marginTop: 25, }}>
{/*<FormLabel style={{ color: "#B9B9BA" }}>Find your integrations!</FormLabel>*/} {/*<FormLabel style={{ color: "#B9B9BA" }}>Find your integrations!</FormLabel>*/}
<div style={{display: "flex"}}>
<Button disabled={finishedApps.includes("CASES")} variant={defaultSearch === "CASES" ? "contained" : "outlined"} style={buttonStyle} startIcon={<LightbulbIcon />} onClick={(event) => { onNodeSelect("CASES") }} >
Case Management
</Button>
</div>
<div style={{display: "flex"}}> <div style={{display: "flex"}}>
<Button disabled={finishedApps.includes("SIEM")} variant={defaultSearch === "SIEM" ? "contained" : "outlined"} style={buttonStyle} startIcon={<SearchIcon />} onClick={(event) => { onNodeSelect("SIEM") }} > <Button disabled={finishedApps.includes("SIEM")} variant={defaultSearch === "SIEM" ? "contained" : "outlined"} style={buttonStyle} startIcon={<SearchIcon />} onClick={(event) => { onNodeSelect("SIEM") }} >
SIEM SIEM
</Button> </Button>
<Button disabled={finishedApps.includes("EDR & AV")} variant={defaultSearch === "EDR & AV" ? "contained" : "outlined"} style={buttonStyle} startIcon={<NewReleasesIcon />} onClick={(event) => { onNodeSelect("EDR & AV") }} > <Button disabled={finishedApps.includes("EDR & AV") || finishedApps.includes("ERADICATION")} variant={defaultSearch === "Eradication" ? "contained" : "outlined"} style={buttonStyle} startIcon={<NewReleasesIcon />} onClick={(event) => { onNodeSelect("ERADICATION") }} >
Endpoint Endpoint
</Button> </Button>
</div> </div>
@@ -434,7 +559,7 @@ const WelcomeForm = (props) => {
Intel Intel
</Button> </Button>
<Button disabled={finishedApps.includes("COMMS")} variant={defaultSearch === "EMAIL" ? "contained" : "outlined"} style={buttonStyle} startIcon={<EmailIcon />} onClick={(event) => { onNodeSelect("EMAIL") }} > <Button disabled={finishedApps.includes("COMMS") || finishedApps.includes("EMAIL")} variant={defaultSearch === "EMAIL" ? "contained" : "outlined"} style={buttonStyle} startIcon={<EmailIcon />} onClick={(event) => { onNodeSelect("EMAIL") }} >
Email Email
</Button> </Button>
</div> </div>
@@ -478,94 +603,123 @@ const WelcomeForm = (props) => {
</Grid> </Grid>
*/} */}
</div> </div>
</Fade>
) )
case 2: case 2:
return ( return (
<div style={{display: "flex", marginTop: 25, width: 1200, minHeight: sizing, maxHeight: sizing, }}> <Fade in={true}>
<Grid item xs={10} style={{width: "100%", flex: 5, }}> <div style={{marginTop: 0, maxWidth: 700, minWidth: 700, margin: "auto", minHeight: sizing, maxHeight: sizing, }}>
<Typography variant="body1" style={{marginLeft: 8, marginTop: 0, marginRight: 30, marginBottom: 0, maxWidth: 500, }} color="textSecondary"> <Typography variant="body1" style={{marginTop: 15, marginBottom: 0, maxWidth: 500, margin: "auto", marginBottom: 15, }} color="textSecondary">
What usecases are you interested in? This will help us suggest relevant Workflows. These are some of our Workflow templates, used to start new Workflows. Use the right and left buttons to find <a href="/usecases" target="_blank" rel="norefferer" style={{color: "#f86a3e", textDecoration: "none", }}>new Usecases</a>, and click the orange button to build it.
</Typography> </Typography>
{/*<Divider />*/}
<Grid item xs={5} style={{marginTop: 15, display: "flex", flexDirection: "column", width: "100%",}}>
<Button variant={defaultSearch === "Enrichment" ? "contained" : "outlined"} startIcon={<SearchIcon />} style={newButtonStyle} onClick={() => {
setDefaultSearch("Enrichment")
setSelectionOpen(false)
setTimeout(function(){
setSelectionOpen(true)
}, 150)
sendOrgUpdate("", "", userdata.active_org.id, "2. Enrich")
}}>
Enrichment
</Button>
<Button variant={defaultSearch === "Phishing" ? "contained" : "outlined"} startIcon={<EmailIcon />} style={newButtonStyle} onClick={() => {
setDefaultSearch("Phishing")
setSelectionOpen(false)
setTimeout(function(){
setSelectionOpen(true)
}, 150)
sendOrgUpdate("", "", userdata.active_org.id, "Email management")
}}>
Phishing
</Button>
<Button variant={defaultSearch === "Detection" ? "contained" : "outlined"} startIcon={<NewReleasesIcon />} style={newButtonStyle} onClick={() => {
setDefaultSearch("Detection")
setSelectionOpen(false)
setTimeout(function(){
setSelectionOpen(true)
}, 150);
sendOrgUpdate("", "", userdata.active_org.id, "3. Detect")
}}>
Detection
</Button>
<Button variant={defaultSearch === "Response" ? "contained" : "outlined"} startIcon={<NewReleasesIcon />} style={newButtonStyle} onClick={() => {
setDefaultSearch("Response")
setSelectionOpen(false)
setTimeout(function(){
setSelectionOpen(true)
}, 150);
sendOrgUpdate("", "", userdata.active_org.id, "4. Respond")
}}>
Response
</Button>
</Grid>
</Grid>
{/* {/*
<Grid item xs={10} paddingBottom="20px"> <div style={{width: 475, margin: "auto",}}>
<TextField {usecaseButtons.map((usecase, index) => {
required
fullWidth={true} return (
placeholder="Workflows as suggested from tools" <Chip
label="Workflows as suggested from tools" key={usecase.name}
type="astoolsworkflow" style={{
id="standard-required" backgroundColor: defaultSearch === usecase.name ? usecase.color : theme.palette.surfaceColor,
autoComplete="astoolsworkflow" marginRight: 10,
margin="normal" paddingLeft: 5,
variant="outlined" paddingRight: 5,
/> height: 28,
</Grid> cursor: "pointer",
border: `1px solid ${usecase.color}`,
color: "white",
borderRadius: theme.palette.borderRadius,
}}
label={`${index+1}. ${usecase.name}`}
onClick={() => {
console.log("Clicked: ", usecase.name)
if (defaultSearch === usecase.name) {
//setSelectedUsecaseCategory("")
} else {
handleSetSearch(usecase.name, usecase.usecase)
}
//addFilter(usecase.name.slice(3,usecase.name.length))
}}
variant="outlined"
color="primary"
/>
)
})}
</div>
*/} */}
<div style={{marginTop: 15, flex: 6, }}> <div style={{marginTop: 0, }}>
{selectionOpen === true ? {/*
<WorkflowSearch <UsecaseSearch
ConfiguredHits={NewHits} globalUrl={globalUrl}
showSearch={false}
defaultSearch={defaultSearch} defaultSearch={defaultSearch}
newSelectedApp={newSelectedApp} appFramework={appFramework}
setNewSelectedApp={setNewSelectedApp} apps={apps}
/> />
: null} */}
<div className="thumbs" style={{display: "flex"}}>
<Tooltip title={"Previous usecase"}>
<IconButton
style={{
backgroundColor: thumbIndex === 0 ? "inherit" : "white",
zIndex: 5000,
minHeight: 50,
maxHeight: 50,
color: "grey",
marginTop: 150,
borderRadius: 50,
border: "1px solid rgba(255,255,255,0.3)",
}}
onClick={() => {
slidePrev()
}}
>
<ArrowBackIosNewIcon />
</IconButton>
</Tooltip>
<div style={{minWidth: 554, maxWidth: 554, borderRadius: theme.palette.borderRadius, padding: 25, }}>
<AliceCarousel
style={{ backgroundColor: theme.palette.surfaceColor, minHeight: 750, maxHeight: 750, }}
items={usecaseItems}
activeIndex={thumbIndex}
infiniteLoop
mouseTracking
responsive={responsive}
// activeIndex={activeIndex}
controlsStrategy="responsive"
autoPlay={false}
infinite={true}
animationType="fadeout"
animationDuration={800}
disableButtonsControls
disableDotsControls
/>
</div>
<Tooltip title={"Next usecase"}>
<IconButton
style={{
backgroundColor: thumbIndex === usecaseButtons.length-1 ? "inherit" : "white",
zIndex: 5000,
minHeight: 50,
maxHeight: 50,
color: "grey",
marginTop: 150,
borderRadius: 50,
border: "1px solid rgba(255,255,255,0.3)",
}}
onClick={() => {
slideNext()
}}
>
<ArrowForwardIosIcon />
</IconButton>
</Tooltip>
</div>
</div> </div>
</div> </div>
</Fade>
) )
default: default:
return "unknown step" return "unknown step"
@@ -573,26 +727,7 @@ const WelcomeForm = (props) => {
} }
return ( return (
<div style={{paddingTop: 20}}> <div style={{}}>
<Stepper activeStep={activeStep} style={{backgroundColor: theme.palette.platformColor, borderRadius: theme.palette.borderRadius, padding: 12, border: "1px solid rgba(255,255,255,0.3)",}}>
{steps.map((label, index) => {
const stepProps = {}
const labelProps = {}
//if (isStepOptional(index)) {
// labelProps.optional = "optional"
//}
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label} {...stepProps}>
<StepLabel {...labelProps} style={{marginLeft: 10}}>{label}</StepLabel>
</Step>
)
})}
</Stepper>
{/*selectionOpen ? {/*selectionOpen ?
<WorkflowSearch <WorkflowSearch
defaultSearch={defaultSearch} defaultSearch={defaultSearch}
@@ -619,31 +754,78 @@ const WelcomeForm = (props) => {
) : ( ) : (
<div> <div>
{getStepContent(activeStep)} {getStepContent(activeStep)}
<div style={{paddingTop: 20}}> <div style={{marginBottom: 20, }}/>
<Button disabled={activeStep === 0} onClick={handleBack}> {activeStep === 2 || activeStep === 1 ?
Back <div style={{margin: "auto", minWidth: 500, maxWidth: 500, position: "relative", }}>
</Button> <Button
{/*isStepOptional(activeStep) && ( disabled={activeStep === 0}
<Button onClick={handleBack}
variant="contained" variant={"outlined"}
color="primary" style={{marginLeft: 10, height: 64, width: 100, position: "absolute", top: activeStep === 1 ? -594 : -577, left: activeStep === 1 ? 105+clickdiff : -145+clickdiff, }}
onClick={handleSkip} >
> Back
Skip </Button>
</Button> <Button
)*/} variant={"outlined"}
<Button color="primary"
variant={activeStep === 1 ? finishedApps.length === 4 ? "contained" : "outlined" : "contained"} onClick={handleNext}
color="primary" style={{marginLeft: 10, height: 64, width: 100, position: "absolute", top: activeStep === 1 ? -594 : -577, left: activeStep === 1 ? 758+clickdiff : 510+clickdiff, }}
onClick={handleNext} disabled={activeStep === 0 ? orgName.length === 0 || name.length === 0 : false}
style={{marginLeft: 10, }} >
disabled={activeStep === 0 ? orgName.length === 0 || name.length === 0 : false} {activeStep === steps.length - 1 ? "Finish" : "Next"}
> </Button>
{activeStep === steps.length - 1 ? "Finish" : "Next"} </div>
</Button> :
</div> <div style={{margin: "auto", minWidth: 500, maxWidth: 500, marginLeft: activeStep === 1 ? 250 : "auto", marginTop: activeStep === 0 ? 25 : 0, }}>
</div> <Button disabled={activeStep === 0} onClick={handleBack}>
)} Back
</Button>
{/*isStepOptional(activeStep) && (
<Button
variant="contained"
color="primary"
onClick={handleSkip}
>
Skip
</Button>
)*/}
<Button
variant={activeStep === 1 ? finishedApps.length >= 4 ? "contained" : "outlined" : "outlined"}
color="primary"
onClick={handleNext}
style={{marginLeft: 10, }}
disabled={activeStep === 0 ? orgName.length === 0 || name.length === 0 : false}
>
{activeStep === steps.length - 1 ? "Finish" : "Next"}
</Button>
{activeStep === 0 ?
<Button
variant={"outlined"}
color="secondary"
onClick={() => {
console.log("Skip!")
if (isCloud) {
ReactGA.event({
category: "welcome",
action: "click_page_one_skip",
label: "",
})
}
setActiveStep(1)
navigate(`/welcome?tab=2`)
}}
style={{marginLeft: 240, }}
disabled={activeStep !== 0}
>
Skip
</Button>
: null}
</div>
}
</div>
)}
</div> </div>
</div> </div>
); );
+2 -2
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks"; import { useInterval } from "react-powerhooks";
import DetectionFramework from "../components/DetectionFramework.jsx"; import AppFramework from "../components/AppFramework.jsx";
import { makeStyles, useTheme } from "@material-ui/core/styles"; import { makeStyles, useTheme } from "@material-ui/core/styles";
// nodejs library that concatenates classes // nodejs library that concatenates classes
import classNames from "classnames"; import classNames from "classnames";
@@ -834,7 +834,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
border: "1px solid rgba(255,255,255,0.3)", border: "1px solid rgba(255,255,255,0.3)",
}}> }}>
<DetectionFramework <AppFramework
inputUsecase={inputUsecase} inputUsecase={inputUsecase}
frameworkData={frameworkData} frameworkData={frameworkData}
selectedOption={"Draw"} selectedOption={"Draw"}
-1
View File
@@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks"; import { useInterval } from "react-powerhooks";
import DetectionFramework from "../components/DetectionFramework.jsx";
import { makeStyles, useTheme } from "@material-ui/core/styles"; import { makeStyles, useTheme } from "@material-ui/core/styles";
// nodejs library that concatenates classes // nodejs library that concatenates classes
import classNames from "classnames"; import classNames from "classnames";
+2 -2
View File
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import ReactDOM from "react-dom" import ReactDOM from "react-dom"
import DetectionFramework from "../components/DetectionFramework.jsx"; import AppFramework from "../components/AppFramework.jsx";
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import theme from '../theme'; import theme from '../theme';
@@ -69,7 +69,7 @@ const Framework = (props) => {
</Link> </Link>
</div> </div>
{frameworkLoaded === true && isLoaded ? {frameworkLoaded === true && isLoaded ?
<DetectionFramework <AppFramework
frameworkData={frameworkData} frameworkData={frameworkData}
selectedOption={"Draw"} selectedOption={"Draw"}
showOptions={false} showOptions={false}
+298 -35
View File
@@ -1,20 +1,45 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import WelcomeForm from "../components/WelcomeForm.jsx"; import ReactGA from 'react-ga';
import DetectionFramework from "../components/DetectionFramework.jsx"; import WelcomeForm2 from "../components/WelcomeForm2.jsx";
import Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import AppFramework from "../components/AppFramework.jsx";
import { import {
Grid, Grid,
Container,
Fade, Fade,
Typography, Typography,
Paper,
Button,
Card,
CardContent,
CardActionArea,
} from '@mui/material'; } from '@mui/material';
import theme from '../theme'; import theme from '../theme';
import { useNavigate } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
// Should be different if logged in :|
const Welcome = (props) => { const Welcome = (props) => {
const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded } = props; const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded } = props;
const [skipped, setSkipped] = React.useState(new Set());
const [inputUsecase, setInputUsecase] = useState({}); const [inputUsecase, setInputUsecase] = useState({});
const [frameworkData, setFrameworkData] = useState(undefined); const [frameworkData, setFrameworkData] = useState(undefined);
const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined); const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined);
const [activeStep, setActiveStep] = React.useState(0);
const [apps, setApps] = React.useState([]);
const [defaultSearch, setDefaultSearch] = React.useState("")
const [selectionOpen, setSelectionOpen] = React.useState(false)
const [showWelcome, setShowWelcome] = React.useState(false)
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const [steps, setSteps] = useState([
"Help us get to know you",
"Find your Apps",
"Discover Usecases",
])
let navigate = useNavigate(); let navigate = useNavigate();
@@ -51,46 +76,284 @@ const Welcome = (props) => {
}) })
} }
const getApps = () => {
fetch(globalUrl + "/api/v1/apps", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
}
return response.json();
})
.then((responseJson) => {
setApps(responseJson);
})
.catch((error) => {
console.log("App loading error: "+error.toString());
});
}
const usecaseButtons = [{
"name": "Phishing",
"usecase": "Email management",
"color": "#C51152",
}, {
"name": "Enrichment",
"usecase": "2. Enrich",
"color": "#F4C20D",
}, {
"name": "Detection",
"usecase": "3. Detect",
"color": "#3CBA54",
}, {
"name": "Response",
"usecase": "4. Respond",
"color": "#4885ED",
}]
const handleSetSearch = (input, orgupdate) => {
console.log("INPUT & ORGUPDATE: ", input, orgupdate, defaultSearch)
if (input !== defaultSearch) {
setDefaultSearch(input)
setSelectionOpen(false)
setTimeout(function(){
setSelectionOpen(true)
}, 150);
//if (userdata !== undefined && userdata.active_org !== undefined && userdata.active_org.id !== undefined) {
// sendOrgUpdate("", "", userdata.active_org.id, orgupdate)
//}
} else {
setDefaultSearch("")
setSelectionOpen(false)
}
}
useEffect(() => { useEffect(() => {
getFramework() getFramework()
}, []); getApps()
if (
window.location.search !== undefined &&
window.location.search !== null
) {
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const foundTab = params["tab"];
if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) {
console.log("FOUND TAB: ", foundTab)
setShowWelcome(true)
if (foundTab === 3 || foundTab === "3") {
console.log("SET SEARCH!!")
handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase)
}
setActiveStep(foundTab-1)
} else {
navigate(`/welcome?tab=1`)
}
}
}, [])
const isStepSkipped = step => {
return skipped.has(step)
}
const paperObject = {
flex: 1,
padding: 0,
textAlign: "center",
maxWidth: 300,
minWidth: 300,
backgroundColor: theme.palette.surfaceColor,
color: "white",
}
const actionObject = {
padding: "50px 35px 50px 35px",
}
const imageStyle = {
width: 150,
height: 150,
margin: "auto",
marginTop: 30,
}
return ( return (
<Grid container spacing={2} style={{ padding: 70, maxWidth: 1366, minWidth: 1366, margin: "auto", }}> <div style={{width: 1000, margin: "auto", backgroundColor: theme.palette.platformColor, paddingBottom: 150, minHeight: 1500,}}>
<Grid item xs={6}> {/*
<div> <div style={{position: "fixed", bottom: 110, right: 110, display: "flex", }}>
<WelcomeForm <img src="/images/Arrow.png" style={{width: 250, height: "100%",}} />
userdata={userdata} </div>
globalUrl={globalUrl} */}
discoveryWrapper={discoveryWrapper} {showWelcome === true ?
setDiscoveryWrapper={setDiscoveryWrapper} <div>
/> <div style={{minWidth: 500, maxWidth: 500, margin: "auto",}}>
<Stepper
activeStep={activeStep}
color="primary"
style={{
backgroundColor: theme.palette.platformColor,
borderRadius: theme.palette.borderRadius,
padding: 12,
border: "1px solid rgba(255,255,255,0.3)",
maxWidth: 500,
color: "white",
}}
>
{steps.map((label, index) => {
const stepProps = {}
const labelProps = {}
//if (isStepOptional(index)) {
// labelProps.optional = "optional"
//}
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label} {...stepProps} style={{maxWidth: 160, color: "white", }}>
<StepLabel {...labelProps} style={{marginLeft: 10, color: "white",}}>
{label}
</StepLabel>
</Step>
)
})}
</Stepper>
</div> </div>
</Grid> <Grid container spacing={2} style={{ padding: 0, maxWidth: 1000, minWidth: 1000, margin: "auto", }}>
{frameworkData === undefined || window.location.href.includes("tab=3") ? null : <Grid item xs={window.location.href.includes("tab=2") ? 6 : 12}>
<div style={{marginTop: 25}}> <div>
<Typography variant="h6" style={{textAlign: "center", marginBottom: 25, }}> {/*
App Framework <WelcomeForm
</Typography> userdata={userdata}
<Fade>
<DetectionFramework
inputUsecase={inputUsecase}
frameworkData={frameworkData}
selectedOption={"Draw"}
showOptions={false}
isLoaded={true}
isLoggedIn={true}
globalUrl={globalUrl} globalUrl={globalUrl}
size={0.8}
color={theme.palette.platformColor}
discoveryWrapper={discoveryWrapper} discoveryWrapper={discoveryWrapper}
setDiscoveryWrapper={setDiscoveryWrapper} setDiscoveryWrapper={setDiscoveryWrapper}
/> />
</Fade> */}
<WelcomeForm2
userdata={userdata}
globalUrl={globalUrl}
discoveryWrapper={discoveryWrapper}
setDiscoveryWrapper={setDiscoveryWrapper}
appFramework={frameworkData}
getFramework={getFramework}
steps={steps}
skipped={skipped}
setSkipped={setSkipped}
activeStep={activeStep}
setActiveStep={setActiveStep}
getApps={getApps}
apps={apps}
handleSetSearch={handleSetSearch}
usecaseButtons={usecaseButtons}
defaultSearch={defaultSearch}
setDefaultSearch={setDefaultSearch}
selectionOpen={selectionOpen}
setSelectionOpen={setSelectionOpen}
/>
</div>
</Grid>
{frameworkData === undefined || window.location.href.includes("tab=1") || window.location.href.includes("tab=3") ? null :
<div style={{marginTop: 25, }}>
<Typography variant="h6" style={{textAlign: "center", marginBottom: 25, }}>
App Framework
</Typography>
<Fade>
<AppFramework
inputUsecase={inputUsecase}
frameworkData={frameworkData}
setFrameworkData={setFrameworkData}
selectedOption={"Draw"}
showOptions={false}
isLoaded={true}
isLoggedIn={true}
globalUrl={globalUrl}
size={0.78}
color={theme.palette.platformColor}
discoveryWrapper={discoveryWrapper}
setDiscoveryWrapper={setDiscoveryWrapper}
apps={apps}
/>
</Fade>
</div>
}
</Grid>
</div>
:
<Fade in={true}>
<div style={{maxWidth: 700, margin: "auto", marginTop: 50, }}>
<Typography variant="h4" style={{color: "white", textAlign: "center"}}>
Welcome to Shuffle
</Typography>
<Typography variant="body1" style={{textAlign: "center", marginBottom: 50, }}>
Who do you identify with the most?
</Typography>
<div style={{display: "flex", marginTop: 70, width: 700, margin: "auto",}}>
<Card style={paperObject} onClick={() => {
if (isCloud) {
ReactGA.event({
category: "welcome",
action: "click_welcome_continue",
label: "",
})
}
setShowWelcome(true)
}}>
<CardActionArea style={actionObject}>
<Typography variant="h4" style={{color: "#49A928"}}>
New to Shuffle
</Typography>
<img src="/images/welcome_cog.png" style={imageStyle} />
<Typography variant="body1" style={{marginTop: 30, color: "rgba(255,255,255,0.8)"}}>
Follow our short introduction and learn some tips and tricks
</Typography>
</CardActionArea>
</Card>
<div style={{marginLeft: 25, marginRight: 25, }}>
<Typography style={{marginTop: 200, }}>
OR
</Typography>
</div>
<Card style={paperObject} onClick={() => {
if (isCloud) {
ReactGA.event({
category: "welcome",
action: "click_getting_started",
label: "",
})
}
navigate("/getting-started?message=Skipped intro")
}}>
<CardActionArea style={actionObject}>
<Typography variant="h4" style={{color: "#f86a3e"}}>
Experienced
</Typography>
<img src="/images/social/shuffle_logo_round.png" style={imageStyle} />
<Typography variant="body1" style={{marginTop: 30, color: "rgba(255,255,255,0.8)"}}>
You know Shuffle well. Head to the product right away!
</Typography>
</CardActionArea>
</Card>
</div>
</div> </div>
} </Fade>
</Grid> }
) </div>
)
} }
export default Welcome; export default Welcome;