Merge branch 'Shuffle:launch' into launch

This commit is contained in:
Gelot Ayush
2022-11-01 17:22:59 +05:30
committed by GitHub
31 changed files with 4194 additions and 763 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ TZ=Europe/Amsterdam # Timezone-handler in Orborus, Worker and Apps
ORBORUS_CONTAINER_NAME= # Used to FIND the containername. cgroup v2: issue 501
SHUFFLE_ORBORUS_STARTUP_DELAY= # Used for setting up a startup delay for Orborus
SHUFFLE_BASE_IMAGE_NAME=frikky
SHUFFLE_BASE_IMAGE_NAME=shuffle
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.0.0"
+16 -24
View File
@@ -46,38 +46,30 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Ghcr
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Dockerhub Build and push
id: docker_build_dockerhub
uses: docker/build-push-action@v2
- name: Ghcr Build and push
id: docker_build
uses: docker/build-push-action@v3
env:
BUILDX_NO_DEFAULT_LOAD: true
with:
logout: false
context: ${{ matrix.path }}/
file: ${{ matrix.path }}/Dockerfile
platforms: linux/amd64,linux/arm64
#,linux/386 - no node image I guess?
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:nightly
#- name: Login to Ghcr
# uses: docker/login-action@v2
# with:
# registry: ghcr.io
# username: frikky
# password: ${{ secrets.GHCR_PW }}
#- name: Ghcr Build and push
# id: docker_build
# uses: docker/build-push-action@v2
# env:
# BUILDX_NO_DEFAULT_LOAD: true
# with:
# context: ${{ matrix.path }}/
# file: ${{ matrix.path }}/Dockerfile
# platforms: linux/amd64,linux/arm64
# push: true
# tags: frikky/shuffle-${{ matrix.app }}:nightly
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
tags: |
ghcr.io/shuffle/shuffle-${{ matrix.app }}:nightly
${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:nightly
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
+6 -7
View File
@@ -1433,7 +1433,7 @@ class AppBase:
"execution_id": self.current_execution_id
}
self.logger.info("[DEBUG] Before FULLEXEC stream result")
self.logger.info("[ERROR] Before FULLEXEC stream result")
ret = requests.post(
"%s/api/v1/streams/results" % (self.base_url),
headers=headers,
@@ -1444,8 +1444,7 @@ class AppBase:
fullexecution = ret.json()
else:
try:
self.logger.info("[DEBUG] Error: Data: ", ret.json())
self.logger.info("[DEBUG] Error with status code for results. Crashing because ACTION_RESULTS or WORKFLOW_VARIABLE can't be handled. Status: %d" % ret.status_code)
self.logger.info("[ERROR] Error in app with status code for results. Crashing because results can't be handled. Status: %d" % ret.status_code)
except json.decoder.JSONDecodeError:
pass
@@ -1457,7 +1456,7 @@ class AppBase:
self.send_result(self.action_result, headers, stream_path)
return
except requests.exceptions.ConnectionError as e:
self.logger.info("[DEBUG] FullExec Connectionerror: %s" % e)
self.logger.info("[ERROR] FullExec Connectionerror: %s" % e)
self.action_result["result"] = json.dumps({
"success": False,
"reason": f"Connection error during startup: {e}"
@@ -1470,7 +1469,7 @@ class AppBase:
try:
fullexecution = json.loads(self.full_execution)
except json.decoder.JSONDecodeError as e:
self.logger.info("[WARNING] Json decode execution error: %s" % e)
self.logger.info("[ERROR] Json decode execution error: %s" % e)
self.action_result["result"] = "Json error during startup: %s" % e
self.send_result(self.action_result, headers, stream_path)
return
@@ -3425,7 +3424,7 @@ class AppBase:
errorstring = f"{e}"
if "the JSON object must be" in errorstring:
self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly?")
self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (0)?")
try:
e = json.loads(f"{e}")
except:
@@ -3455,7 +3454,7 @@ class AppBase:
})
break
except Exception as e:
self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly?")
self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (1)?")
try:
e = json.loads(f"{e}")
+3 -4
View File
@@ -2,8 +2,7 @@ module main
go 1.16
replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
@@ -15,7 +14,7 @@ require (
github.com/basgys/goxml2json v1.1.0
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
github.com/docker/docker v20.10.12+incompatible
github.com/frikky/kin-openapi v0.41.0
github.com/frikky/kin-openapi v0.42.0
github.com/fsouza/go-dockerclient v1.7.7
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.3.1
@@ -24,7 +23,7 @@ require (
github.com/h2non/filetype v1.1.3
github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.2.87
github.com/shuffle/shuffle-shared v0.3.5
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
google.golang.org/api v0.65.0
+3 -3
View File
@@ -2,7 +2,7 @@ version: '3'
services:
frontend:
#build: ./frontend
image: ghcr.io/frikky/shuffle-frontend:nightly
image: ghcr.io/shuffle/shuffle-frontend:nightly
container_name: shuffle-frontend
hostname: shuffle-frontend
ports:
@@ -17,7 +17,7 @@ services:
- backend
backend:
#build: ./backend
image: ghcr.io/frikky/shuffle-backend:nightly
image: ghcr.io/shuffle/shuffle-backend:nightly
container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME}
# Here for debugging:
@@ -40,7 +40,7 @@ services:
#- opensearch #Not necessary because dependancy is handled within the backend itself instead
#- database
orborus:
image: ghcr.io/frikky/shuffle-orborus:nightly
image: ghcr.io/shuffle/shuffle-orborus:nightly
container_name: shuffle-orborus
hostname: shuffle-orborus
networks:
+1
View File
@@ -47,6 +47,7 @@
"react": "^16.14.0",
"react-alert": "^5.5.0",
"react-alert-template-basic": "^1.0.0",
"react-alice-carousel": "^2.6.4",
"react-avatar-editor": "^11.1.0",
"react-beforeunload": "^2.2.1",
"react-chartjs-2": "^2.11.1",
@@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react';
import { securityFramework } from "./LandingpageUsecases.jsx";
import { securityFramework} from "./LandingpageUsecases.jsx";
import CytoscapeComponent from 'react-cytoscapejs';
import frameworkStyle from '../frameworkStyle.jsx';
import AppSearch from './Appsearch.jsx';
import { v4 as uuidv4 } from "uuid";
import theme from '../theme';
import { useAlert } from "react-alert";
import { usecaseTypes } from "../components/UsecaseSearch.jsx"
import PaperComponent from "../components/PaperComponent.jsx"
import SuggestedWorkflows from "../components/SuggestedWorkflows.jsx"
import {
Paper,
@@ -16,6 +19,7 @@ import {
Badge,
CircularProgress,
Tooltip,
Dialog,
} from "@material-ui/core";
import {
@@ -518,24 +522,105 @@ export const usecases = {
}
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 [edgesStarted, setEdgesStarted] = React.useState(false)
const [graphDone, setGraphDone] = React.useState(false)
const [cyDone, setCyDone] = React.useState(false)
const [discoveryData, setDiscoveryData] = React.useState({})
const [selectionOpen, setSelectionOpen] = React.useState(true)
const [frameworkSuggestions, setFrameworkSuggestions] = React.useState([])
const [newSelectedApp, setNewSelectedApp] = React.useState({})
const [defaultSearch, setDefaultSearch] = React.useState("")
const [animationStarted, setAnimationStarted] = React.useState(false)
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 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(() => {
//console.log("DISCWRAP CHANG: ", discoveryWrapper)
console.log("DISCWRAP CHANG: ", discoveryWrapper)
if (discoveryWrapper === undefined || discoveryWrapper.id === "SHUFFLE" || discoveryWrapper.id === undefined || cy === undefined) {
setDiscoveryData({})
@@ -548,26 +633,30 @@ const Framework = (props) => {
// Find the node and click it?
setTimeout(() => {
const nodes = cy.nodes().jsons()
for (var key in nodes) {
const node = nodes[key]
var newSearchName = discoveryWrapper.id.valueOf()
if (newSearchName === "EMAIL") {
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)
}
//setTimeout(() => {
const nodes = cy.nodes().jsons()
for (var key in nodes) {
const node = nodes[key]
var newSearchName = discoveryWrapper.id.valueOf()
if (newSearchName === "EMAIL") {
newSearchName = "COMMS"
}
}, 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)
}, [discoveryWrapper])
@@ -652,17 +741,23 @@ const Framework = (props) => {
}
useEffect(() => {
if (discoveryData.id === undefined) {
return
}
console.log(newSelectedApp, discoveryData)
if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
return
}
if (paperTitle.length > 0) {
setDiscoveryWrapper({})
setSelectionOpen(false)
//if (paperTitle.length > 0) {
// console.log("No papertitle (parent button)")
// cy.elements().unselect()
// return
//}
if (discoveryData.id === undefined) {
console.log("No discoverydata (parent button)")
cy.elements().unselect()
return
}
const submitValue = {
@@ -683,7 +778,29 @@ const Framework = (props) => {
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)
cy.elements().unselect()
}, [newSelectedApp])
@@ -792,8 +909,6 @@ const Framework = (props) => {
//console.log("Framework - update? ", parsedFrameworkData)
// 0 = automated, 1 = manual
const [usecaseType, setUsecaseType] = React.useState(0)
const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
const elements = []
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 data = event.target.data();
var data = event.target.data();
console.log("Node: ", data)
if (data.id === "SHUFFLE") {
event.target.unselect()
return
}
if (data.label === "EDR & AV") {
data.label = "ERADICATION"
}
setDiscoveryData(data)
setSelectionOpen(true)
@@ -1009,6 +1170,9 @@ const Framework = (props) => {
cy.on("select", "node", (e) => {
onNodeSelect(e)
})
cy.on("unselect", "node", (e) => {
onNodeUnselect(e)
})
cy.on("mouseover", "node", (e) => {onNodeHover(e)})
cy.on("mouseout", "node", (e) => onNodeHoverOut(e));
@@ -1547,8 +1711,21 @@ const Framework = (props) => {
var usecasediff = -100
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={{position: "absolute"}}>
<SuggestedWorkflows
globalUrl={globalUrl}
userdata={userdata}
frameworkData={frameworkData}
usecaseSuggestions={frameworkSuggestions}
setUsecaseSuggestions={setFrameworkSuggestions}
inputSearch={changedApp}
apps={apps}
/>
</div>
{showOptions === false ? null :
<div style={{textAlign: "center",}}>
{Object.keys(usecases).map((data, index) => {
@@ -1580,7 +1757,7 @@ const Framework = (props) => {
{
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 ?
<span>
<Typography variant="h6" style={{textAlign: "center"}}>
@@ -1718,6 +1895,8 @@ const Framework = (props) => {
defaultSearch={defaultSearch}
newSelectedApp={newSelectedApp}
setNewSelectedApp={setNewSelectedApp}
userdata={userdata}
cy={cy}
/>
: null}
</div>
+42 -15
View File
@@ -10,10 +10,11 @@ import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon }
import algoliasearch from 'algoliasearch';
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@material-ui/core';
import aa from 'search-insights'
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const WorkflowSearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits } = props
const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, } = props
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
@@ -140,19 +141,19 @@ const WorkflowSearch = props => {
}
counted += 1
var parsedname = ""
for (var key = 0; key < data.name.length; key++) {
var character = data.name.charAt(key)
if (character === character.toUpperCase()) {
//console.log(data.name[key], data.name[key+1])
if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) {
} else {
parsedname += " "
}
}
var parsedname = data.name.valueOf()
//for (var key = 0; key < data.name.length; key++) {
// var character = data.name.charAt(key)
// if (character === character.toUpperCase()) {
// //console.log(data.name[key], data.name[key+1])
// if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) {
// } else {
// parsedname += " "
// }
// }
parsedname += character
}
// parsedname += character
//}
parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ")
@@ -180,6 +181,32 @@ const WorkflowSearch = props => {
label: "",
})
}
const queryID = ""
if (queryID !== undefined && queryID !== null) {
try {
aa('init', {
appId: searchClient.appId,
apiKey: searchClient.transporter.headers["x-algolia-api-key"]
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'conversion',
eventName: 'App Framework Activation',
index: 'appsearch',
objectIDs: [data.objectID],
timestamp: timestamp,
queryID: queryID,
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
} catch (e) {
console.log("Failed algolia search update: ", e)
}
}
}}>
<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", }} />
@@ -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 {
InputAdornment,
@@ -13,6 +13,7 @@ import {
List,
ListItem,
ListItemText,
Fade,
} from "@material-ui/core";
import { FavoriteBorder as FavoriteBorderIcon } from "@material-ui/icons";
import { FixName } from "../views/Apps.jsx";
@@ -43,14 +44,15 @@ const ConfigureWorkflow = (props) => {
isCloud,
setAuthenticationType,
alert,
showTriggers,
} = props;
const [requiredActions, setRequiredActions] = React.useState([]);
const [requiredVariables, setRequiredVariables] = React.useState([]);
const [requiredTriggers, setRequiredTriggers] = React.useState([]);
const [previousAuth, setPreviousAuth] = React.useState(appAuthentication);
const [firstLoad, setFirstLoad] = React.useState("");
const [itemChanged, setItemChanged] = React.useState(false);
var finished = false;
const [firstLoad, setFirstLoad] = React.useState("");
const [showFinalizeAnimation, setShowFinalizeAnimation] = React.useState(false);
if (workflow === undefined || workflow === null) {
return null;
@@ -94,18 +96,13 @@ const ConfigureWorkflow = (props) => {
};
if (firstLoad.length === 0 || firstLoad !== workflow.id) {
if (finished) {
setConfigureWorkflowModalOpen(false);
return null;
}
if (apps === undefined || apps === null || apps.length === 0) {
console.log("No apps loaded: ", apps);
setConfigureWorkflowModalOpen(false);
return null;
}
setFirstLoad(workflow.id);
setFirstLoad(workflow.id)
const newactions = [];
for (var key in workflow.actions) {
const action = workflow.actions[key];
@@ -272,17 +269,19 @@ const ConfigureWorkflow = (props) => {
setRequiredTriggers(requiredTriggers);
setRequiredVariables(requiredVariables);
setRequiredActions(newactions);
}
}
if (appAuthentication.length !== previousAuth.length) {
var newactions = [];
var newactions = []
for (var actionkey in requiredActions) {
var newaction = requiredActions[actionkey];
const app = newaction.app;
for (var key in appAuthentication) {
const auth = appAuthentication[key];
if (auth.app.name === app.name && auth.active) {
// Does this account for all the different ones of the same?
if (auth.app.name === app.name && auth.active === true) {
newaction.auth_done = true;
break;
}
@@ -502,6 +501,7 @@ const ConfigureWorkflow = (props) => {
return (
<ListItem>
{/*
<ListItemAvatar>
<Avatar variant="rounded">
<img
@@ -516,19 +516,28 @@ const ConfigureWorkflow = (props) => {
secondary={action.app_version}
style={{}}
/>
{action.must_authenticate ? (
action.auth_done ? (
<Button color="primary" variant="outlined" onClick={() => {}}>
Authenticated
</Button>
) : selectedAction.app_name === action.app_name ? (
<CircularProgress />
) : (
<Button
color="primary"
variant="contained"
onClick={() => {
setAuthenticationType(
*/}
{action.must_authenticate ?
<Button
fullWidth
variant="contained"
disabled={action.auth_done}
style={{
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
color: action.auth_done ? "#686a6c" : "#2f2f2f",
borderRadius: theme.palette.borderRadius,
minWidth: 350,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
onClick={() => {
setAuthenticationType(
action.app.authentication.type === "oauth2" &&
action.app.authentication.redirect_uri !== undefined &&
action.app.authentication.redirect_uri !== null
@@ -541,37 +550,86 @@ const ConfigureWorkflow = (props) => {
: {
type: "",
}
);
)
setItemChanged(true);
setSelectedAction(action.action);
setSelectedApp(action.app);
if (setSelectedAction !== undefined) {
setSelectedAction(action.action);
}
if (setSelectedApp !== undefined) {
setSelectedApp(action.app);
}
setAuthenticationModalOpen(true);
}}
}}
>
Authenticate
</Button>
)
) : null}
<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.auth_done ? "Authenticated" : `Authenticate ${action.app_name}`}
</Typography>
</Button>
: null}
{action.must_activate ? (
<Button
color="primary"
variant="contained"
onClick={() => {
console.log("ACTION: ", action)
activateApp(action.action.app_id, action.app_name, action.app_version);
setItemChanged(true);
}}
<Button
fullWidth
variant="contained"
disabled={action.auth_done}
style={{
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
color: action.auth_done ? "#686a6c" : "#2f2f2f",
borderRadius: theme.palette.borderRadius,
minWidth: 350,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
onClick={() => {
console.log("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>
) : null}
{action.update_version !== action.app_version ? (
<Button
color="primary"
variant="contained"
style={{marginLeft: 5}}
onClick={() => {
fullWidth
variant="contained"
disabled={action.auth_done}
style={{
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: action.auth_done ? theme.palette.surfaceColor : "#ffffff",
color: action.auth_done ? "#686a6c" : "#2f2f2f",
borderRadius: theme.palette.borderRadius,
minWidth: 350,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
onClick={() => {
console.log("Set version to: ", action.update_version)
if (workflow.actions !== null) {
@@ -592,84 +650,118 @@ const ConfigureWorkflow = (props) => {
}
}}
>
{action.update_version}
<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>
) : null}
</ListItem>
);
};
}
// Based on the color here. Default: #f86a3e
//backgroundColor: selectedUsecaseCategory === usecase.name ? usecase.color : theme.palette.surfaceColor,
const topColor = "#f86a3e, #fc3922"
return (
<div>
<Typography variant="h6">{workflow.name}</Typography>
<Typography variant="body1" color="textSecondary">
The following configuration makes the workflow ready immediately.
</Typography>
{requiredActions.length > 0 ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Actions
</Typography>
<List>
{requiredActions.map((data, index) => {
return <AppSection key={index} action={data} />;
})}
</List>
</span>
) : null}
<div style={{height: 125, width: "100%", background: `linear-gradient(to right, ${topColor}`, position: "relative",}}>
</div>
<div style={{margin: "25px 50px 50px 50px", maxHeight: 475, }}>
<Typography variant="h6">{workflow.name}</Typography>
<Typography variant="body2" color="textSecondary">
The following configuration makes the workflow ready immediately.
</Typography>
{requiredActions.length > 0 ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Required Actions
</Typography>
<List>
{requiredActions.map((data, index) => {
return (
<AppSection key={index} action={data} />
)
})}
</List>
</span>
) : null}
{requiredVariables.length > 0 ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Variables
</Typography>
<List>
{requiredVariables.map((data, index) => {
return <VariableSection key={index} variable={data} />;
})}
</List>
</span>
) : null}
{requiredVariables.length > 0 ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Variables
</Typography>
<List>
{requiredVariables.map((data, index) => {
return <VariableSection key={index} variable={data} />;
})}
</List>
</span>
) : null}
{requiredTriggers.length > 0 ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Triggers
</Typography>
<List>
{requiredTriggers.map((data, index) => {
return <TriggerSection key={index} trigger={data} />;
})}
</List>
</span>
) : null}
<div style={{ textAlign: "center", display: "flex", marginTop: 20 }}>
<ButtonGroup style={{ margin: "auto" }}>
{/*
<Button color="primary" variant={"outlined"} style={{
}} onClick={() => {
setConfigureWorkflowModalOpen(false)
}}>
Skip
</Button>
*/}
<Button
color="primary"
variant={itemChanged ? "contained" : "outlined"}
style={{}}
onClick={() => {
if (itemChanged) {
saveWorkflow(workflow);
window.location.reload();
} else {
setConfigureWorkflowModalOpen(false);
}
}}
>
Close window
</Button>
</ButtonGroup>
</div>
{requiredTriggers.length > 0 && showTriggers !== false ? (
<span>
<Typography variant="body1" style={{ marginTop: 10 }}>
Triggers
</Typography>
<List>
{requiredTriggers.map((data, index) => {
return <TriggerSection key={index} trigger={data} />;
})}
</List>
</span>
) : null}
<div style={{ textAlign: "center", display: "flex", marginTop: 20 }}>
{showFinalizeAnimation ?
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, margin: "auto",}} onLoad={() => {
console.log("Img loaded.")
setTimeout(() => {
console.log("Img closing.")
setConfigureWorkflowModalOpen(false);
}, 1250)
}}/>
:
<ButtonGroup style={{ margin: "auto" }}>
{/*
<Button color="primary" variant={"outlined"} style={{
}} onClick={() => {
setConfigureWorkflowModalOpen(false)
}}>
Skip
</Button>
*/}
<Button
color="textSecondary"
variant={"outlined"}
style={{}}
onClick={() => {
setShowFinalizeAnimation(true)
setTimeout(() => {
if (itemChanged) {
if (saveWorkflow !== undefined) {
saveWorkflow(workflow);
window.location.reload();
}
} else {
}
}, 1000)
}}
>
Finalize
</Button>
</ButtonGroup>
}
</div>
</div>
</div>
);
};
+374 -244
View File
@@ -2,6 +2,7 @@ import React, { useEffect, useContext } from "react";
import theme from '../theme';
import { isMobile } from "react-device-detect"
import ChipInput from "material-ui-chip-input";
import UsecaseSearch from "../components/UsecaseSearch.jsx"
import {
Badge,
@@ -45,19 +46,91 @@ import {
} from "@material-ui/icons";
const EditWorkflow = (props) => {
const { workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, } = props
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props
const [submitLoading, setSubmitLoading] = React.useState(false);
const [selectedUsecases, setSelectedUsecases] = React.useState([]);
const [showMoreClicked, setShowMoreClicked] = React.useState(false);
const [innerWorkflow, setInnerWorkflow] = React.useState(workflow)
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : [])
const [selectedUsecases, setSelectedUsecases] = React.useState(workflow.usecase_ids !== undefined && workflow.usecase_ids !== null ? JSON.parse(JSON.stringify(workflow.usecase_ids)) : []);
const [foundWorkflowId, setFoundWorkflowId] = React.useState("")
const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "")
const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "")
if (workflow === undefined || workflow.id === undefined || setWorkflow === undefined || modalOpen !== true) {
// Gets the generated workflow
const getGeneratedWorkflow = (workflow_id) => {
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 when getting workflow");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.id === workflow_id) {
console.log("GOT WORKFLOW: ", responseJson)
if (name === "") {
innerWorkflow.name = responseJson.name
setName(responseJson.name)
}
if (description === "") {
innerWorkflow.description = responseJson.description
setDescription(description)
}
if (newWorkflowTags === []) {
innerWorkflow.tags = responseJson.tags
setNewWorkflowTags(responseJson.tags)
}
if (selectedUsecases === []) {
selectedUsecases = responseJson.usecase_ids
}
innerWorkflow.id = responseJson.id
innerWorkflow.blogpost = responseJson.blogpost
innerWorkflow.actions = responseJson.actions
innerWorkflow.triggers = responseJson.triggers
innerWorkflow.branches = responseJson.branches
innerWorkflow.comments = responseJson.comments
innerWorkflow.workflow_variables = responseJson.workflow_variables
innerWorkflow.execution_variables = responseJson.execution_variables
setInnerWorkflow(innerWorkflow)
setUpdate(Math.random())
}
})
.catch((error) => {
//alert.error(error.toString());
console.log("Get workflow error: ", error.toString());
})
}
if (foundWorkflowId.length > 0) {
getGeneratedWorkflow(foundWorkflowId)
setFoundWorkflowId("")
} else {
}
if (modalOpen !== true) {
return null
}
const newWorkflow = isEditing === true ? false : true
var upload = "";
var total_count = 0
@@ -71,264 +144,299 @@ const EditWorkflow = (props) => {
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: isMobile ? "90%" : "800px",
maxWidth: isMobile ? "90%" : "800px",
minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
minHeight: 400,
},
}}
>
<DialogTitle>
<div style={{ color: "rgba(255,255,255,0.9)" }}>
{workflow.id !== undefined ? "Editing" : "New"} workflow
{showUpload === true ?
<div style={{ float: "right" }}>
<Tooltip color="primary" title={"Import manually"} placement="top">
<Button
color="primary"
style={{}}
variant="text"
onClick={() => upload.click()}
>
<PublishIcon />
</Button>
</Tooltip>
</div>
<DialogTitle style={{padding: 30, paddingBottom: 0, zIndex: 1000,}}>
<div style={{display: "flex"}}>
<div style={{flex: 1, color: "rgba(255,255,255,0.9)" }}>
<Typography variant="h6">
{newWorkflow ? "New" : "Editing"} workflow
</Typography>
<Typography variant="body2" color="textSecondary" style={{maxWidth: 440,}}>
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>
</Typography>
{showUpload === true ?
<div style={{ float: "right" }}>
<Tooltip color="primary" title={"Import manually"} placement="top">
<Button
color="primary"
style={{}}
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}
</div>
</div>
</DialogTitle>
<FormControl>
<DialogContent>
<TextField
onBlur={(event) => {
innerWorkflow.name = 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",
},
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 350, zIndex: 1001, }}>
<div style={{minWidth: newWorkflow ? 450 : 500, maxWidth: newWorkflow ? 450 : 500, }}>
<TextField
onBlur={(event) => {
setName(event.target.value)
}}
placeholder="Tags"
color="primary"
fullWidth
value={newWorkflowTags}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
InputProps={{
style: {
color: "white",
},
}}
color="primary"
placeholder="Name"
required
margin="dense"
defaultValue={innerWorkflow.name}
label="Name"
autoFocus
fullWidth
/>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
onDelete={(chip, index) => {
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
}}
/>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<FormControl style={{flex: 1, marginLeft: 5, }}>
<InputLabel htmlFor="grouped-select-usecase">Usecases</InputLabel>
<Select
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)
InputProps={{
style: {
color: "white",
},
}}
maxRows={4}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
<div style={{display: "flex", marginTop: 10, }}>
<ChipInput
style={{ flex: 1, maxHeight: 40, marginTop: 12, overflow: "auto", }}
InputProps={{
style: {
color: "white",
},
}}
placeholder="Tags"
color="primary"
fullWidth
value={newWorkflowTags}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
}}
onDelete={(chip, index) => {
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
}}
/>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<FormControl style={{flex: 1, marginLeft: 5, }}>
<InputLabel htmlFor="grouped-select-usecase">Usecases</InputLabel>
<Select
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());
setSelectedUsecases(selectedUsecases)
}}>
<Checkbox style={{color: selectedUsecases.includes(subcase.name) ? usecase.color : theme.palette.inputColor}} checked={selectedUsecases.includes(subcase.name)} />
<ListItemText primary={subcase.name} />
</MenuItem>
)
})}
</span>
)
})}
</Select>
</FormControl>
setUpdate(Math.random());
setSelectedUsecases(selectedUsecases)
}}>
<Checkbox style={{color: selectedUsecases.includes(subcase.name) ? usecase.color : theme.palette.inputColor}} checked={selectedUsecases.includes(subcase.name)} />
<ListItemText primary={subcase.name} />
</MenuItem>
)
})}
</span>
)
})}
</Select>
</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}
<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>
{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
{newWorkflow === true ?
<div style={{marginLeft: 50, maxWidth: 400, minWidth: 400, position: "relative",}}>
<UsecaseSearch
globalUrl={globalUrl}
appFramework={appFramework}
defaultSearch={undefined}
apps={undefined}
setFoundWorkflowId={setFoundWorkflowId}
userdata={userdata}
/>
<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>
</div>
: 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>
<DialogActions>
<Button
style={{}}
onClick={() => {
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
setModalOpen(false)
}}
color="primary"
@@ -338,13 +446,35 @@ const EditWorkflow = (props) => {
<Button
variant="contained"
style={{}}
disabled={innerWorkflow.name.length === 0}
disabled={name.length === 0}
onClick={() => {
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
setWorkflow(innerWorkflow)
if (selectedUsecases.length > 0) {
innerWorkflow.usecase_ids = selectedUsecases
}
if (setNewWorkflow !== undefined) {
setNewWorkflow(
innerWorkflow.name,
innerWorkflow.description,
innerWorkflow.tags,
innerWorkflow.default_return_value,
innerWorkflow,
newWorkflow,
innerWorkflow.usecase_ids,
innerWorkflow.blogpost,
innerWorkflow.status,
)
setWorkflow({})
} else {
setWorkflow(innerWorkflow)
}
setModalOpen(false)
}}
color="primary"
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import {isMobile} from "react-device-detect";
import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
import AppFramework, { usecases } from "../components/AppFramework.jsx";
import {Link} from 'react-router-dom';
import ReactGA from 'react-ga';
@@ -169,7 +169,7 @@ const LandingpageUsecases = (props) => {
</div>
{isMobile ? null :
<div style={{marginLeft: 200, marginTop: 125, zIndex: 1000}}>
<DetectionFramework showOptions={false} selectedOption={selectedUsecase} rolling={true} />
<AppFramework showOptions={false} selectedOption={selectedUsecase} rolling={true} />
</div>
}
{isMobile ? null :
+113 -68
View File
@@ -1,5 +1,6 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { useTheme } from "@material-ui/core/styles";
import theme from '../theme';
import { v4 as uuidv4 } from "uuid";
import {
@@ -62,6 +63,7 @@ const registeredApps = [
"outlook_office365",
"microsoft_teams",
"microsoft_teams_user_access",
"todoist",
]
const AuthenticationOauth2 = (props) => {
@@ -77,8 +79,8 @@ const AuthenticationOauth2 = (props) => {
setNewAppAuth,
setAuthenticationModalOpen,
isCloud,
autoAuth,
} = props;
const theme = useTheme();
//const [update, setUpdate] = React.useState("|")
const [defaultConfigSet, setDefaultConfigSet] = React.useState(
@@ -88,7 +90,7 @@ const AuthenticationOauth2 = (props) => {
authenticationType.client_secret !== undefined &&
authenticationType.client_secret !== null &&
authenticationType.client_secret.length > 0
);
);
const [clientId, setClientId] = React.useState(
defaultConfigSet ? authenticationType.client_id : ""
@@ -123,6 +125,73 @@ const AuthenticationOauth2 = (props) => {
return null;
}
const startOauth2Request = () => {
console.log("APP: ", selectedApp)
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
handleOauth2Request(
"efe4c3fe-84a1-4821-a84f-23a6cfe8e72d",
"",
"https://graph.microsoft.com",
["Mail.ReadWrite"],
);
} else if (selectedApp.name.toLowerCase() == "gmail") {
handleOauth2Request(
"253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com",
"",
"https://gmail.googleapis.com",
["https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.insert",
"https://www.googleapis.com/auth/gmail.compose"]
)
} else if (selectedApp.name.toLowerCase() == "zoho_desk") {
handleOauth2Request(
"1000.ZR5MHUW6B0L6W1VUENFGIATFS0TOJT",
"",
"https://desk.zoho.com",
["Desk.tickets.READ",
"Desk.tickets.UPDATE",
"Desk.tickets.DELETE",
"Desk.tickets.CREATE"]
)
} else if (selectedApp.name.toLowerCase() == "slack") {
handleOauth2Request(
"151779186901.2448678750935",
"",
"https://slack.com",
["admin", "chat:write", "im:read", "im:write", "search:read", "usergroups:read", "usergroups:write"]
)
} else if (selectedApp.name.toLowerCase() == "webex") {
handleOauth2Request(
"Cab184f3d7271f540443c79b5b79845e3387abbbdb3db4233a87ea3a5432fb3d5",
"",
"https://webexapis.com",
["spark:all"]
)
} else if (selectedApp.name.toLowerCase().includes("microsoft_teams")) {
handleOauth2Request(
"31cb4c84-658e-43d5-ae84-22c9142e967a",
"",
"https://graph.microsoft.com",
["ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "Chat.Create", "Chat.ReadWrite", "Chat.Read"]
)
} else if (selectedApp.name.toLowerCase().includes("todoist")) {
handleOauth2Request(
"35fa3a384040470db0c8527e90a3c2eb",
"",
"https://api.todoist.com",
["task:add"]
)
}
}
useEffect(() => {
console.log("Should automatically click the auto-auth button?")
if (autoAuth === true && selectedApp !== undefined) {
startOauth2Request()
}
}, [])
const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => {
setButtonClicked(true);
console.log("SCOPES: ", scopes);
@@ -164,19 +233,15 @@ const AuthenticationOauth2 = (props) => {
state += `%26refresh_uri%3d${authentication_url}`;
}
// No prompt forcing
const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`;
// Force new consent
//const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`;
// Admin consent
//const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=admin_consent&state=${state}&access_type=offline`;
// Skip consent
const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&state=${state}&access_type=offline`;
//const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent`
//console.log("Full URI: ", url)
//console.log("Redirect Uri: ", redirectUri)
// &resource=https%3A%2F%2Fgraph.microsoft.com&
// &resource=https%3A%2F%2Fgraph.microsoft.com&
// FIXME: Awful, but works for prototyping
// How can we get a callback properly realtime?
@@ -188,12 +253,20 @@ const AuthenticationOauth2 = (props) => {
var open = true;
const timer = setInterval(() => {
if (newwin.closed) {
console.log("Closed!")
if (setAuthenticationModalOpen !== undefined) {
setAuthenticationModalOpen(false)
}
setButtonClicked(false);
clearInterval(timer);
//alert('"Secure Payment" window closed!');
getAppAuthentication(true, true);
}
} else {
console.log("Not closed")
}
}, 1000);
//do {
// setTimeout(() => {
@@ -361,76 +434,48 @@ const AuthenticationOauth2 = (props) => {
{isCloud && registeredApps.includes(selectedApp.name.toLowerCase()) ?
<span>
<Button
style={{
marginBottom: 20,
marginTop: 20,
fullWidth
variant="contained"
style={{
marginBottom: 20,
marginTop: 20,
flex: 1,
textTransform: "none",
textAlign: "left",
justifyContent: "flex-start",
backgroundColor: "#ffffff",
color: "#2f2f2f",
borderRadius: theme.palette.borderRadius,
}}
minWidth: 350,
maxHeight: 50,
overflow: "hidden",
border: `1px solid ${theme.palette.inputColor}`,
}}
color="primary"
disabled={
clientSecret.length > 0 || clientId.length > 0
}
variant="contained"
fullWidth
onClick={() => {
// Hardcode some stuff?
// This could prolly be added to the app itself with a "default" client ID
console.log("APP: ", selectedApp)
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
handleOauth2Request(
"efe4c3fe-84a1-4821-a84f-23a6cfe8e72d",
"",
"https://graph.microsoft.com",
["Mail.ReadWrite", "Mail.Send"],
);
} else if (selectedApp.name.toLowerCase() == "gmail") {
handleOauth2Request(
"253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com",
"",
"https://gmail.googleapis.com",
["https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.insert",
"https://www.googleapis.com/auth/gmail.compose"]
)
} else if (selectedApp.name.toLowerCase() == "zoho_desk") {
handleOauth2Request(
"1000.ZR5MHUW6B0L6W1VUENFGIATFS0TOJT",
"",
"https://desk.zoho.com",
["Desk.tickets.READ",
"Desk.tickets.UPDATE",
"Desk.tickets.DELETE",
"Desk.tickets.CREATE"]
)
} else if (selectedApp.name.toLowerCase() == "slack") {
handleOauth2Request(
"151779186901.2448678750935",
"",
"https://slack.com",
["admin", "chat:write", "im:read", "im:write", "search:read", "usergroups:read", "usergroups:write"]
)
} else if (selectedApp.name.toLowerCase() == "webex") {
handleOauth2Request(
"Cab184f3d7271f540443c79b5b79845e3387abbbdb3db4233a87ea3a5432fb3d5",
"",
"https://webexapis.com",
["spark:all"]
)
} else if (selectedApp.name.toLowerCase().includes("microsoft_teams")) {
handleOauth2Request(
"31cb4c84-658e-43d5-ae84-22c9142e967a",
"",
"https://graph.microsoft.com",
["ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "Chat.Create", "Chat.ReadWrite", "Chat.Read"]
)
}
startOauth2Request()
}}
color="primary"
>
{buttonClicked ? (
<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>
<Typography style={{textAlign: "center", marginTop: 0, marginBottom: 10, }}>
@@ -1,6 +1,6 @@
import React, {useState } from 'react';
import {isMobile} from "react-device-detect";
import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
import AppFramework, { usecases } from "../components/AppFramework.jsx";
import {Link} from 'react-router-dom';
import ReactGA from 'react-ga';
@@ -56,6 +56,8 @@ export const securityFramework = [
]
const LandingpageUsecases = (props) => {
const { userdata } = props
const [selectedUsecase, setSelectedUsecase] = useState("Phishing")
const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases)
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
@@ -169,7 +171,12 @@ const LandingpageUsecases = (props) => {
</div>
{isMobile ? null :
<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>
}
{isMobile ? null :
@@ -305,7 +305,6 @@ const CodeEditor = (props) => {
variable_occurence = new_occurences.valueOf()
console.log("Match2: ", variable_occurence)
// console.log(variable_occurence)
// console.log()
@@ -410,7 +409,7 @@ const CodeEditor = (props) => {
// return
//}
//console.log("FOUND: ", found)
console.log("FOUND: ", found)
// Whelp this is inefficient af. Single loop pls
// When the found array is empty.
@@ -426,8 +425,15 @@ const CodeEditor = (props) => {
for (var j = 0; j < actionlist.length; j++) {
if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){
valuefound = true
console.log("Valuefound: ", fixedVariable, actionlist[j].example)
try {
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
} else {
input = input.replace(fixedVariable, actionlist[j].example)
}
} catch (e) {
input = input.replace(fixedVariable, actionlist[j].example)
}
@@ -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 Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import ReactGA from 'react-ga';
import Button from "@material-ui/core/Button";
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 EmailIcon from '@mui/icons-material/Email';
import NewReleasesIcon from '@mui/icons-material/NewReleases';
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 {
Fade,
IconButton,
FormGroup,
FormControl,
InputLabel,
@@ -24,23 +30,91 @@ import {
Typography,
TextField,
Zoom,
List,
ListItem,
ListItemText,
Divider,
Tooltip,
Chip,
} from "@material-ui/core";
import { useAlert } from "react-alert";
import { useNavigate, Link } from "react-router-dom";
import WorkflowSearch from '../components/Workflowsearch.jsx';
import AuthenticationItem from '../components/AuthenticationItem.jsx';
import WorkflowPaper from "../components/WorkflowPaper.jsx"
import UsecaseSearch from "../components/UsecaseSearch.jsx"
const responsive = {
0: { items: 1 },
};
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 [name, setName] = React.useState("")
const [orgName, setOrgName] = React.useState("")
const [role, setRole] = React.useState("")
const [orgType, setOrgType] = 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();
const onNodeSelect = (label) => {
@@ -77,8 +151,7 @@ const WelcomeForm = (props) => {
}
}, [discoveryWrapper])
useEffect(() => {
useEffect(() => {
if (
window.location.search !== undefined &&
window.location.search !== null
@@ -87,24 +160,20 @@ const WelcomeForm = (props) => {
const params = Object.fromEntries(urlSearchParams.entries());
const foundTab = params["tab"];
if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) {
setActiveStep(foundTab-1)
if (foundTab === 3 || foundTab === "3") {
console.log("SET SEARCH!!")
//setclickdiff(240)
}
} else {
navigate(`/welcome?tab=1`)
//navigate(`/welcome?tab=1`)
}
}
}, [])
const steps = getSteps();
const isStepOptional = step => {
return step === 1
}
const isStepSkipped = step => {
return skipped.has(step)
}
const sendUserUpdate = (name, role, userId) => {
const data = {
"tutorial": "welcome",
@@ -177,7 +246,6 @@ const WelcomeForm = (props) => {
console.log("Update of org failed")
//alert.error("Failed updating org: ", responseJson.reason);
} else {
console.log("Update success!")
//alert.success("Successfully edited org!");
}
})
@@ -221,12 +289,25 @@ const WelcomeForm = (props) => {
)
}
const isStepSkipped = step => {
return skipped.has(step)
}
const handleNext = () => {
setDefaultSearch("")
if (activeStep === 0) {
console.log("Should send basic information about org (fetch)")
setclickdiff(0)
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) {
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")
setDiscoveryWrapper({})
setclickdiff(240)
//setclickdiff(0)
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) {
console.log("Should send third page with workflows activated and the like")
}
let newSkipped = skipped;
if (isStepSkipped(activeStep)) {
newSkipped = new Set(newSkipped.values());
@@ -253,11 +345,27 @@ const WelcomeForm = (props) => {
setActiveStep(prevActiveStep => prevActiveStep + 1);
setSkipped(newSkipped);
};
}
const handleBack = () => {
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 = () => {
setclickdiff(240)
if (!isStepOptional(activeStep)) {
throw new Error("You can't skip a step that isn't optional.");
}
@@ -271,27 +379,16 @@ const WelcomeForm = (props) => {
const handleReset = () => {
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(() => {
console.log("Selected app changed (effect)")
}, [newSelectedApp])
function getSteps() {
return [
"Help us get to know you",
"Discover integrations",
"Personalize Usecases",
]
}
//const buttonWidth = 145
const buttonWidth = 450
const buttonMargin = 10
const sizing = 435
const sizing = 475
const buttonStyle = {
flex: 1,
width: "100%",
@@ -300,6 +397,23 @@ const WelcomeForm = (props) => {
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 = {
padding: 22,
flex: 1,
@@ -307,22 +421,20 @@ const WelcomeForm = (props) => {
minWidth: buttonWidth,
maxWidth: buttonWidth,
}
const getStepContent = (step) => {
switch (step) {
case 0:
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 :
<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.
</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">
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>
<Grid item xs={11} style={{marginTop: 16, padding: 0,}}>
<TextField
@@ -395,12 +507,20 @@ const WelcomeForm = (props) => {
</FormControl>
</Grid>
</Grid>
</Fade>
)
case 1:
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">
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>
{/*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, }}>
{/*<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"}}>
<Button disabled={finishedApps.includes("SIEM")} variant={defaultSearch === "SIEM" ? "contained" : "outlined"} style={buttonStyle} startIcon={<SearchIcon />} onClick={(event) => { onNodeSelect("SIEM") }} >
SIEM
</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
</Button>
</div>
@@ -434,7 +559,7 @@ const WelcomeForm = (props) => {
Intel
</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
</Button>
</div>
@@ -478,94 +603,123 @@ const WelcomeForm = (props) => {
</Grid>
*/}
</div>
</Fade>
)
case 2:
return (
<div style={{display: "flex", marginTop: 25, width: 1200, minHeight: sizing, maxHeight: sizing, }}>
<Grid item xs={10} style={{width: "100%", flex: 5, }}>
<Typography variant="body1" style={{marginLeft: 8, marginTop: 0, marginRight: 30, marginBottom: 0, maxWidth: 500, }} color="textSecondary">
What usecases are you interested in? This will help us suggest relevant Workflows.
</Typography>
<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>
<Fade in={true}>
<div style={{marginTop: 0, maxWidth: 700, minWidth: 700, margin: "auto", minHeight: sizing, maxHeight: sizing, }}>
<Typography variant="body1" style={{marginTop: 15, marginBottom: 0, maxWidth: 500, margin: "auto", marginBottom: 15, }} color="textSecondary">
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>
{/*<Divider />*/}
{/*
<Grid item xs={10} paddingBottom="20px">
<TextField
required
fullWidth={true}
placeholder="Workflows as suggested from tools"
label="Workflows as suggested from tools"
type="astoolsworkflow"
id="standard-required"
autoComplete="astoolsworkflow"
margin="normal"
variant="outlined"
/>
</Grid>
<div style={{width: 475, margin: "auto",}}>
{usecaseButtons.map((usecase, index) => {
return (
<Chip
key={usecase.name}
style={{
backgroundColor: defaultSearch === usecase.name ? usecase.color : theme.palette.surfaceColor,
marginRight: 10,
paddingLeft: 5,
paddingRight: 5,
height: 28,
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, }}>
{selectionOpen === true ?
<WorkflowSearch
ConfiguredHits={NewHits}
showSearch={false}
<div style={{marginTop: 0, }}>
{/*
<UsecaseSearch
globalUrl={globalUrl}
defaultSearch={defaultSearch}
newSelectedApp={newSelectedApp}
setNewSelectedApp={setNewSelectedApp}
appFramework={appFramework}
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>
</Fade>
)
default:
return "unknown step"
@@ -573,26 +727,7 @@ const WelcomeForm = (props) => {
}
return (
<div style={{paddingTop: 20}}>
<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>
<div style={{}}>
{/*selectionOpen ?
<WorkflowSearch
defaultSearch={defaultSearch}
@@ -619,31 +754,78 @@ const WelcomeForm = (props) => {
) : (
<div>
{getStepContent(activeStep)}
<div style={{paddingTop: 20}}>
<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" : "contained"}
color="primary"
onClick={handleNext}
style={{marginLeft: 10, }}
disabled={activeStep === 0 ? orgName.length === 0 || name.length === 0 : false}
>
{activeStep === steps.length - 1 ? "Finish" : "Next"}
</Button>
</div>
</div>
)}
<div style={{marginBottom: 20, }}/>
{activeStep === 2 || activeStep === 1 ?
<div style={{margin: "auto", minWidth: 500, maxWidth: 500, position: "relative", }}>
<Button
disabled={activeStep === 0}
onClick={handleBack}
variant={"outlined"}
style={{marginLeft: 10, height: 64, width: 100, position: "absolute", top: activeStep === 1 ? -594 : -577, left: activeStep === 1 ? 105+clickdiff : -145+clickdiff, }}
>
Back
</Button>
<Button
variant={"outlined"}
color="primary"
onClick={handleNext}
style={{marginLeft: 10, height: 64, width: 100, position: "absolute", top: activeStep === 1 ? -594 : -577, left: activeStep === 1 ? 758+clickdiff : 510+clickdiff, }}
disabled={activeStep === 0 ? orgName.length === 0 || name.length === 0 : false}
>
{activeStep === steps.length - 1 ? "Finish" : "Next"}
</Button>
</div>
:
<div style={{margin: "auto", minWidth: 500, maxWidth: 500, marginLeft: activeStep === 1 ? 250 : "auto", marginTop: activeStep === 0 ? 25 : 0, }}>
<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>
);
+4 -3
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import ReactDOM from "react-dom"
import { useInterval } from "react-powerhooks";
import { makeStyles, useTheme } from "@material-ui/core/styles";
@@ -1400,7 +1401,6 @@ const AngularWorkflow = (defaultprops) => {
setVisited([])
setExecutionRequest({})
stop()
})
var curelements = cy.elements();
for (var i = 0; i < curelements.length; i++) {
@@ -1476,6 +1476,7 @@ const AngularWorkflow = (defaultprops) => {
setExecutionRequestStarted(false)
console.log("Execute workflow err: ", error.toString());
});
})
};
// This can be used to only show prioritzed ones later
@@ -13858,7 +13859,7 @@ const AngularWorkflow = (defaultprops) => {
<span style={{ color: "white" }}>Execution Variable</span>
</DialogTitle>
<DialogContent>
Execution Variables are TEMPORARY variables that you can ony be set
Execution Variables are TEMPORARY variables that you can only be set
and used during execution. Learn more{" "}
<a
rel="noopener noreferrer"
@@ -13955,7 +13956,7 @@ const AngularWorkflow = (defaultprops) => {
const variable = execution.execution_variables.find(
(data) => data.name === newVariableName
);
)
if (variable === undefined || variable.value === undefined) {
return null;
}
+16 -10
View File
@@ -34,8 +34,17 @@ import {
AttachFile as AttachFileIcon,
Apps as AppsIcon,
ErrorOutline as ErrorOutlineIcon,
AddAPhoto as AddAPhotoIcon,
AddAPhotoOutlined as AddAPhotoOutlinedIcon,
ZoomInOutlined as ZoomInOutlinedIcon,
ZoomOutOutlined as ZoomOutOutlinedIcon,
Loop as LoopIcon,
} from "@material-ui/icons";
import {
AddPhotoAlternate as AddPhotoAlternateIcon,
} from '@mui/icons-material';
import { v4 as uuidv4 } from "uuid";
import { Link, useParams } from "react-router-dom";
import YAML from "yaml";
@@ -44,12 +53,6 @@ import { useAlert } from "react-alert";
import words from "shellwords";
import AvatarEditor from "react-avatar-editor";
import AddAPhotoIcon from "@material-ui/icons/AddAPhoto";
import AddAPhotoOutlinedIcon from "@material-ui/icons/AddAPhotoOutlined";
import ZoomInOutlinedIcon from "@material-ui/icons/ZoomInOutlined";
import ZoomOutOutlinedIcon from "@material-ui/icons/ZoomOutOutlined";
import LoopIcon from "@material-ui/icons/Loop";
import AddPhotoAlternateIcon from "@material-ui/icons/AddPhotoAlternate";
const surfaceColor = "#27292D";
const inputColor = "#383B40";
@@ -344,14 +347,14 @@ const AppCreator = (defaultprops) => {
useEffect(() => {
if (window.location.pathname.includes("apps/edit")) {
setIsEditing(true);
handleEditApp();
handleEditApp(props.match.params.appid);
} else {
checkQuery();
}
}, []);
const handleEditApp = () => {
fetch(globalUrl + "/api/v1/apps/" + props.match.params.appid + "/config", {
const handleEditApp = (appid) => {
fetch(globalUrl + "/api/v1/apps/" + appid + "/config", {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -389,7 +392,10 @@ const AppCreator = (defaultprops) => {
setIsAppLoaded(true);
return;
}
//handleEditApp(urlParams.get("id"))
// THIS has to stay due to ID may not exist as normal app yet
fetch(globalUrl + "/api/v1/get_openapi/" + urlParams.get("id"), {
method: "GET",
headers: {
@@ -408,7 +414,7 @@ const AppCreator = (defaultprops) => {
.then((responseJson) => {
setIsAppLoaded(true);
if (!responseJson.success) {
alert.error("Failed to verify");
alert.error("Failed to get app config. Do you have access?");
} else {
parseIncomingOpenapiData(responseJson);
}
+28 -15
View File
@@ -39,6 +39,10 @@ import {
Delete as DeleteIcon,
} from "@material-ui/icons";
import {
ForkRight as ForkRightIcon,
} from '@mui/icons-material';
import { useTheme } from "@material-ui/core/styles";
import YAML from "yaml";
@@ -807,9 +811,11 @@ const Apps = (props) => {
</Tooltip>
) : null;
// FIXME: Add /apps/new?id=<PUBLIC> to allow for changes of the original
// Should always reference the original ID.
var editButton =
//if (selectedApp.name !== undefined && selectedApp.name !== null && selectedApp.name.includes("New")) {
//}
var editButton =
selectedApp.activated &&
selectedApp.private_id !== undefined &&
selectedApp.private_id.length > 0 &&
@@ -829,21 +835,19 @@ const Apps = (props) => {
) : null;
//var editNewButton = editButton === null ?
var editNewButton = selectedApp.generated && selectedApp.activated && props.userdata.id !== selectedApp.owner ?
isCloud ?
var editNewButton = selectedApp.generated && selectedApp.activated && props.userdata.id !== selectedApp.owner && isCloud ?
<Link to={activateUrl} style={{ textDecoration: "none" }}>
<Tooltip title={"Edit this public app to your liking"}>
<Tooltip title={"Fork and Edit this public app to your liking"}>
<Button
variant="contained"
component="label"
color="primary"
style={{ marginTop: 10, marginRight: 10 }}
>
<EditIcon />
<ForkRightIcon />
</Button>
</Tooltip>
</Link>
: null
: null
const activateButton =
@@ -879,8 +883,7 @@ const Apps = (props) => {
((selectedApp.private_id !== undefined &&
selectedApp.private_id.length > 0 &&
selectedApp.generated) ||
(selectedApp.downloaded !== undefined &&
selectedApp.downloaded == true) ||
(selectedApp.downloaded !== undefined && selectedApp.downloaded == true) ||
!selectedApp.generated) &&
activateButton === null ? (
<Tooltip title={"Delete app"}>
@@ -982,6 +985,10 @@ const Apps = (props) => {
const userRoles = ["you", isCloud ? "public" : "everyone"];
// Admin in org or creator of app
// FIXME: Missing check for if same creator account
const canEditApp = userdata !== undefined && (userdata.admin === "true" || userdata.id === selectedApp.owner || selectedApp.owner === "" || (userdata.admin === "true" && userdata.active_org.id === selectedApp.reference_org)) || !selectedApp.generated
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"),
var baseInfo =
newAppname.length > 0 ? (
@@ -1086,14 +1093,20 @@ const Apps = (props) => {
) : null}
{activateButton}
{editNewButton}
{(editNewButton === null && userdata !== undefined && (userdata.admin === "true" || userdata.id === selectedApp.owner || selectedApp.owner === "" )) || !selectedApp.generated ? (
{ /* editNewButton === null && */ }
{canEditApp ? (
<div>
{editButton}
{downloadButton}
{deleteButton}
</div>
) : null}
) :
<div>
{editNewButton}
</div>
}
{selectedApp.tags !== undefined && selectedApp.tags !== null ? (
<div
style={{
@@ -1119,8 +1132,8 @@ const Apps = (props) => {
})}
</div>
) : null}
{props.userdata !== undefined &&
props.userdata.id === selectedApp.owner ? (
{canEditApp
? (
<div style={{ marginTop: 15 }}>
{/*<p><b>ID:</b> {selectedApp.id}</p>*/}
<b style={{ marginRight: 15 }}>Sharing </b>
@@ -1877,7 +1890,7 @@ const Apps = (props) => {
getApps();
}, 1000);
} else {
alert.error("Failed deleting app");
alert.error("Failed deleting app. Does it still exist?");
}
})
.catch((error) => {
+2 -2
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
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";
// nodejs library that concatenates classes
import classNames from "classnames";
@@ -834,7 +834,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
borderRadius: theme.palette.borderRadius,
border: "1px solid rgba(255,255,255,0.3)",
}}>
<DetectionFramework
<AppFramework
inputUsecase={inputUsecase}
frameworkData={frameworkData}
selectedOption={"Draw"}
-1
View File
@@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks";
import DetectionFramework from "../components/DetectionFramework.jsx";
import { makeStyles, useTheme } from "@material-ui/core/styles";
// nodejs library that concatenates classes
import classNames from "classnames";
+2 -2
View File
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import ReactDOM from "react-dom"
import DetectionFramework from "../components/DetectionFramework.jsx";
import AppFramework from "../components/AppFramework.jsx";
import { useAlert } from "react-alert";
import { Link, useParams } from "react-router-dom";
import theme from '../theme';
@@ -69,7 +69,7 @@ const Framework = (props) => {
</Link>
</div>
{frameworkLoaded === true && isLoaded ?
<DetectionFramework
<AppFramework
frameworkData={frameworkData}
selectedOption={"Draw"}
showOptions={false}
+298 -35
View File
@@ -1,20 +1,45 @@
import React, { useState, useEffect } from 'react';
import WelcomeForm from "../components/WelcomeForm.jsx";
import DetectionFramework from "../components/DetectionFramework.jsx";
import ReactGA from 'react-ga';
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 {
Grid,
Container,
Fade,
Typography,
Paper,
Button,
Card,
CardContent,
CardActionArea,
} from '@mui/material';
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 { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded } = props;
const [skipped, setSkipped] = React.useState(new Set());
const [inputUsecase, setInputUsecase] = useState({});
const [frameworkData, setFrameworkData] = 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();
@@ -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(() => {
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 (
<Grid container spacing={2} style={{ padding: 70, maxWidth: 1366, minWidth: 1366, margin: "auto", }}>
<Grid item xs={6}>
<div>
<WelcomeForm
userdata={userdata}
globalUrl={globalUrl}
discoveryWrapper={discoveryWrapper}
setDiscoveryWrapper={setDiscoveryWrapper}
/>
<div style={{width: 1000, margin: "auto", backgroundColor: theme.palette.platformColor, paddingBottom: 150, minHeight: 1500,}}>
{/*
<div style={{position: "fixed", bottom: 110, right: 110, display: "flex", }}>
<img src="/images/Arrow.png" style={{width: 250, height: "100%",}} />
</div>
*/}
{showWelcome === true ?
<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>
</Grid>
{frameworkData === undefined || window.location.href.includes("tab=3") ? null :
<div style={{marginTop: 25}}>
<Typography variant="h6" style={{textAlign: "center", marginBottom: 25, }}>
App Framework
</Typography>
<Fade>
<DetectionFramework
inputUsecase={inputUsecase}
frameworkData={frameworkData}
selectedOption={"Draw"}
showOptions={false}
isLoaded={true}
isLoggedIn={true}
<Grid container spacing={2} style={{ padding: 0, maxWidth: 1000, minWidth: 1000, margin: "auto", }}>
<Grid item xs={window.location.href.includes("tab=2") ? 6 : 12}>
<div>
{/*
<WelcomeForm
userdata={userdata}
globalUrl={globalUrl}
size={0.8}
color={theme.palette.platformColor}
discoveryWrapper={discoveryWrapper}
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>
}
</Grid>
)
</Fade>
}
</div>
)
}
export default Welcome;
+7 -2
View File
@@ -1274,8 +1274,12 @@ const Workflows = (props) => {
for (var subkey in data.actions[key].parameters) {
const param = data.actions[key].parameters[subkey];
// Removed October 10th, 2022 as key usually isn't
// containing anything secret, but rather necessary configurations.
// param.name.includes("key") ||
//
if (
param.name.includes("key") ||
param.name.includes("user") ||
param.name.includes("pass") ||
param.name.includes("api") ||
@@ -1322,8 +1326,9 @@ const Workflows = (props) => {
) {
for (key in data.workflow_variables) {
const param = data.workflow_variables[key];
//param.name.includes("key") ||
if (
param.name.includes("key") ||
param.name.includes("user") ||
param.name.includes("pass") ||
param.name.includes("api") ||
+1 -1
View File
@@ -8,5 +8,5 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/mackerelio/go-osstat v0.2.1
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.2.82
github.com/shuffle/shuffle-shared v0.3.5
)
+2
View File
@@ -794,6 +794,8 @@ github.com/shuffle/shuffle-shared v0.2.64 h1:WpCKiL5tNt7wTJaHkf1zXhjeUB5ltFap1dX
github.com/shuffle/shuffle-shared v0.2.64/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk=
github.com/shuffle/shuffle-shared v0.2.82 h1:V3bYw7MxHPQgydUuWLGHONpX7NwbRPPAALovKqsdaW0=
github.com/shuffle/shuffle-shared v0.2.82/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk=
github.com/shuffle/shuffle-shared v0.3.5 h1:erfXVKjeSkmpoGczZ6hPETg8gDdZeXYgXsrEgVO/uqg=
github.com/shuffle/shuffle-shared v0.3.5/go.mod h1:YuMle0RjwXb3hxR5PdaOOD9e+hUyK34OABS0UbrT/Sk=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
+3 -3
View File
@@ -704,8 +704,8 @@ func initializeImages() {
log.Printf("[DEBUG] Setting baseimageregistry")
}
if baseimagename == "" {
baseimagename = "frikky/shuffle"
baseimagename = "frikky"
baseimagename = "shuffle/shuffle"
baseimagename = "shuffle"
log.Printf("[DEBUG] Setting baseimagename")
}
@@ -713,7 +713,7 @@ func initializeImages() {
// check whether they are the same first
images := []string{
fmt.Sprintf("frikky/shuffle:app_sdk"),
fmt.Sprintf("shuffle/shuffle:app_sdk"),
fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion),
fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion),
// fmt.Sprintf("docker.io/%s:app_sdk", baseimagename),