Merge branch 'Shuffle:launch' into launch
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
+214
-35
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 :
|
||||
|
||||
@@ -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
+348
-166
@@ -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>
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) => {
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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") ||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user