Added usecase editing & app framework handling in any place

This commit is contained in:
frikky
2022-03-08 12:45:45 +01:00
parent f88636579f
commit 923e9e5be2
9 changed files with 926 additions and 442 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ module main
go 1.16 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/kin-openapi => ../../../../git/kin-openapi
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch //replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
@@ -24,7 +24,7 @@ require (
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.2.7 github.com/shuffle/shuffle-shared v0.2.9
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
google.golang.org/api v0.65.0 google.golang.org/api v0.65.0
+3 -3
View File
@@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
frontend: frontend:
#build: ./frontend build: ./frontend
image: ghcr.io/frikky/shuffle-frontend:nightly image: ghcr.io/frikky/shuffle-frontend:nightly
container_name: shuffle-frontend container_name: shuffle-frontend
hostname: shuffle-frontend hostname: shuffle-frontend
@@ -16,7 +16,7 @@ services:
depends_on: depends_on:
- backend - backend
backend: backend:
#build: ./backend build: ./backend
image: ghcr.io/frikky/shuffle-backend:nightly image: ghcr.io/frikky/shuffle-backend:nightly
container_name: shuffle-backend container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME} hostname: ${BACKEND_HOSTNAME}
@@ -68,7 +68,7 @@ services:
container_name: shuffle-opensearch container_name: shuffle-opensearch
environment: environment:
- bootstrap.memory_lock=true - bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
- cluster.routing.allocation.disk.threshold_enabled=false - cluster.routing.allocation.disk.threshold_enabled=false
- cluster.name=shuffle-cluster - cluster.name=shuffle-cluster
- node.name=shuffle-opensearch - node.name=shuffle-opensearch
+91 -19
View File
@@ -4,7 +4,7 @@ import { securityFramework } from "./LandingpageUsecases.jsx";
import CytoscapeComponent from 'react-cytoscapejs'; import CytoscapeComponent from 'react-cytoscapejs';
import frameworkStyle from '../frameworkStyle.jsx'; import frameworkStyle from '../frameworkStyle.jsx';
import WorkflowSearch from './Workflowsearch.jsx'; import WorkflowSearch from './Workflowsearch.jsx';
import uuid from "uuid"; import { v4 as uuidv4 } from "uuid";
import theme from '../theme'; import theme from '../theme';
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
@@ -518,7 +518,7 @@ export const usecases = {
} }
const Framework = (props) => { const Framework = (props) => {
const {globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, size } = props; const {globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, size, inputUsecase, isLoggedIn, } = props;
const [cy, setCy] = React.useState() const [cy, setCy] = React.useState()
const [edgesStarted, setEdgesStarted] = React.useState(false) const [edgesStarted, setEdgesStarted] = React.useState(false)
const [graphDone, setGraphDone] = React.useState(false) const [graphDone, setGraphDone] = React.useState(false)
@@ -532,6 +532,50 @@ const Framework = (props) => {
const alert = useAlert() const alert = useAlert()
const setUsecaseItem = (inputUsecase) => {
var parsedUsecase = inputUsecase
const edges = cy.edges().jsons()
parsedUsecase.process = []
for (var key in edges) {
const edge = edges[key]
console.log("Edge: ", edge)
parsedUsecase.process.push(edge.data)
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(parsedUsecase),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Failed updating: " + responseJson.reason)
} else {
alert.error("Failed to update framework for your org.")
}
} else {
alert.info("Updated usecase.")
}
})
.catch((error) => {
alert.error(error.toString());
//setFrameworkLoaded(true)
})
}
const setFrameworkItem = (data) => { const setFrameworkItem = (data) => {
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
method: "POST", method: "POST",
@@ -586,17 +630,18 @@ const Framework = (props) => {
const foundelement = cy.getElementById(discoveryData.id) const foundelement = cy.getElementById(discoveryData.id)
if (foundelement !== undefined && foundelement !== null) { if (foundelement !== undefined && foundelement !== null) {
foundelement.data("large_image", newSelectedApp.image_url) foundelement.data("large_image", newSelectedApp.image_url)
foundelement.data("text_margin_y", "60px")
foundelement.data("margin_x", "0px") foundelement.data("margin_x", "0px")
foundelement.data("margin_y", "50px") foundelement.data("margin_y", "0px")
foundelement.data("width", "85px") foundelement.data("text_margin_y", `${60*scale}px`)
foundelement.data("height", "85px") foundelement.data("width", `${85*scale}px`)
foundelement.data("height", `${85*scale}px`)
} }
setFrameworkItem(submitValue) setFrameworkItem(submitValue)
}, [newSelectedApp]) }, [newSelectedApp])
const isCloud = const isCloud =
window.location.host === "localhost:3002" || window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io"; window.location.host === "shuffler.io";
@@ -649,7 +694,6 @@ const Framework = (props) => {
const [usecaseType, setUsecaseType] = React.useState(0) const [usecaseType, setUsecaseType] = React.useState(0)
const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing") const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
const elements = [] const elements = []
const surfaceColor = "#27292D" const surfaceColor = "#27292D"
@@ -680,12 +724,15 @@ const Framework = (props) => {
setSelectedUsecase(value) setSelectedUsecase(value)
const allEdges = cy.edges().jsons() const edges = cy.edges()
for (var key in allEdges) { if (edges !== undefined) {
const newedge = allEdges[key] const allEdges = edges.jsons()
const foundelement = cy.getElementById(newedge.data.id) for (var key in allEdges) {
if (foundelement !== undefined && foundelement !== null) { const newedge = allEdges[key]
foundelement.remove() const foundelement = cy.getElementById(newedge.data.id)
if (foundelement !== undefined && foundelement !== null) {
foundelement.remove()
}
} }
} }
@@ -719,7 +766,6 @@ const Framework = (props) => {
if (cy !== undefined && cyDone && !animationStarted) { if (cy !== undefined && cyDone && !animationStarted) {
const foundelement = cy.getElementById("CASES") const foundelement = cy.getElementById("CASES")
console.log("FOUND: ", foundelement)
if (foundelement !== undefined && foundelement !== null) { if (foundelement !== undefined && foundelement !== null) {
setAnimationStarted(true) setAnimationStarted(true)
@@ -819,6 +865,7 @@ const Framework = (props) => {
) )
} }
const onNodeSelect = (event) => { const onNodeSelect = (event) => {
const data = event.target.data(); const data = event.target.data();
console.log("Node: ", data) console.log("Node: ", data)
@@ -841,7 +888,7 @@ const Framework = (props) => {
const onEdgeSelect = (event) => { const onEdgeSelect = (event) => {
console.log(event) console.log(event)
if (event.target.data("human") === undefined) { if (event.target.data("human") === undefined || event.target.data("human") === false) {
event.target.data("human", true) event.target.data("human", true)
} else if (event.target.data("human") === true) { } else if (event.target.data("human") === true) {
event.target.remove() event.target.remove()
@@ -854,9 +901,6 @@ const Framework = (props) => {
} }
if (graphDone && cyDone === false) { if (graphDone && cyDone === false) {
//const [cyDone, setCyDone] = React.useState(false)
console.log("IN CYDONe")
cy.fit(null, 200); cy.fit(null, 200);
cy.on("select", "edge", (e) => onEdgeSelect(e)); cy.on("select", "edge", (e) => onEdgeSelect(e));
cy.on("add", "edge", (e) => onEdgeAdded(e)); cy.on("add", "edge", (e) => onEdgeAdded(e));
@@ -878,9 +922,9 @@ const Framework = (props) => {
const baselocationX = 285*scale const baselocationX = 285*scale
const baselocationY = 50*scale const baselocationY = 50*scale
const shiftmodifier = 3*scale const shiftmodifier = 3*scale
const svgSize = `${40*scale}px` const svgSize = `${40*scale}px`
console.log("Framework: ", parsedFrameworkData) console.log("Framework: ", parsedFrameworkData)
const fontSize = `${12*scale}px` const fontSize = `${12*scale}px`
const defaultSize = `${85*scale}px` const defaultSize = `${85*scale}px`
@@ -1217,6 +1261,27 @@ const Framework = (props) => {
} }
changeUsecase(selectedUsecase, usecaseType) changeUsecase(selectedUsecase, usecaseType)
if (inputUsecase !== undefined && inputUsecase !== null) {
console.log("Got usecase: ", inputUsecase)
for (var key in inputUsecase.process) {
if (inputUsecase.process[key].source === "" || inputUsecase.process[key].target === "") {
continue
}
console.log("Edge: ", inputUsecase.process[key])
inputUsecase.process[key].label = parseInt(key)+1
inputUsecase.process[key].id = uuidv4();
cy.add({
group: "edges",
data: inputUsecase.process[key],
})
}
//changeUsecase(inputUsecase, 0)
//setSelectedUsecase(value)
}
} }
if (selectedOption !== undefined && selectedUsecase !== selectedOption) { if (selectedOption !== undefined && selectedUsecase !== selectedOption) {
@@ -1571,6 +1636,13 @@ const Framework = (props) => {
setCy(incy) setCy(incy)
}} }}
/> />
{isCloud && inputUsecase !== undefined && inputUsecase !== null && isLoggedIn === true && Object.keys(inputUsecase).length > 0 ?
<Button color="secondary" variant={"outlined"} style={{position: "absolute", bottom: -10, right: 0,}} onClick={() => {
setUsecaseItem(inputUsecase)
}}>
Save
</Button>
: null}
</div> </div>
) )
} }
+5 -30
View File
@@ -165,6 +165,8 @@ const ParsedAction = (props) => {
actionDelayChange, actionDelayChange,
getParents, getParents,
isCloud, isCloud,
lastSaved,
setLastSaved,
} = props; } = props;
//const theme = useTheme(); //const theme = useTheme();
@@ -1487,6 +1489,9 @@ const ParsedAction = (props) => {
} }
onBlur={(event) => { onBlur={(event) => {
baseHelperText = calculateHelpertext(event.target.value) baseHelperText = calculateHelpertext(event.target.value)
if (setLastSaved !== undefined) {
setLastSaved(false)
}
}} }}
/> />
); );
@@ -2201,35 +2206,6 @@ const ParsedAction = (props) => {
return null; return null;
}; };
const expansionModal = (
<Dialog
disableEnforceFocus={false}
hideBackdrop={true}
open={expansionModalOpen}
onClose={() => {
setExpansionModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 600,
padding: 50,
pointerEvents: "auto",
maxHeight: 550,
overflowY: "auto",
overflowX: "hidden",
zIndex: 10012,
}
}}
>
<DialogTitle>
<span style={{ color: "white" }}>Workflow Variable</span>
</DialogTitle>
<DialogContent>Hello</DialogContent>
</Dialog>
);
//const CustomPopper = function (props) { //const CustomPopper = function (props) {
// const classes = useStyles() // const classes = useStyles()
// return <Popper {...props} className={classes.root} placement="bottom" /> // return <Popper {...props} className={classes.root} placement="bottom" />
@@ -2239,7 +2215,6 @@ const ParsedAction = (props) => {
return ( return (
<div style={appApiViewStyle} id="parsed_action_view"> <div style={appApiViewStyle} id="parsed_action_view">
{expansionModal}
{hideExtraTypes === true ? null : ( {hideExtraTypes === true ? null : (
<span> <span>
<div style={{ display: "flex", minHeight: 40, marginBottom: 30 }}> <div style={{ display: "flex", minHeight: 40, marginBottom: 30 }}>
+116 -111
View File
@@ -12,6 +12,7 @@ import {
import Checkbox from '@mui/material/Checkbox'; import Checkbox from '@mui/material/Checkbox';
import { orange } from '@mui/material/colors'; import { orange } from '@mui/material/colors';
import { isMobile } from "react-device-detect"
import { import {
FullscreenExit as FullscreenExitIcon, FullscreenExit as FullscreenExitIcon,
@@ -86,7 +87,7 @@ const CodeEditor = (props) => {
var popup = false var popup = false
for(var ch=0; ch<code_line.length; ch++){ for(var ch=0; ch<code_line.length; ch++){
if(code_line[ch] == '$'){ if(code_line[ch] === '$'){
dollar_occurences.push(ch) dollar_occurences.push(ch)
} }
} }
@@ -118,7 +119,7 @@ const CodeEditor = (props) => {
for(var occ = 0; occ<variable_ranges.length; occ++){ for(var occ = 0; occ<variable_ranges.length; occ++){
for(var occ1 = 0; occ1<variable_ranges[occ].length; occ1++){ for(var occ1 = 0; occ1<variable_ranges[occ].length; occ1++){
// console.log(variable_ranges[occ][occ1]) // console.log(variable_ranges[occ][occ1])
if(loc == variable_ranges[occ][occ1]){ if(loc === variable_ranges[occ][occ1]){
popup = true popup = true
setCurrentLocation([line, dollar_occurences[occ]]) setCurrentLocation([line, dollar_occurences[occ]])
console.log("Current Location : "+dollar_occurences[occ]) console.log("Current Location : "+dollar_occurences[occ])
@@ -172,7 +173,7 @@ const CodeEditor = (props) => {
// console.log() // console.log()
// for (var j = 0; j < actionlist.length; j++) { // for (var j = 0; j < actionlist.length; j++) {
// if(found[i].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){ // if(found[i].slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){
// input = input.replace(found[i], JSON.stringify(actionlist[j].example)); // input = input.replace(found[i], JSON.stringify(actionlist[j].example));
// console.log(input) // console.log(input)
// console.log(actionlist[j].example) // console.log(actionlist[j].example)
@@ -182,7 +183,7 @@ const CodeEditor = (props) => {
var dollar_occurence = [] var dollar_occurence = []
for(var ch=0; ch<current_code_line.length; ch++){ for(var ch=0; ch<current_code_line.length; ch++){
if(current_code_line[ch] == '$'){ if(current_code_line[ch] === '$'){
dollar_occurence.push(ch) dollar_occurence.push(ch)
} }
} }
@@ -200,11 +201,11 @@ const CodeEditor = (props) => {
for (var occ = 0; occ<variable_occurence.length; occ++){ for (var occ = 0; occ<variable_occurence.length; occ++){
// value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; border-radius: 4px; color: white"}) // value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; border-radius: 4px; color: white"})
var correctVariable = actionlist.find(action => action.autocomplete.toLowerCase() === variable_occurence[occ].slice(1,).toLowerCase()) var correctVariable = actionlist.find(action => action.autocomplete.toLowerCase() === variable_occurence[occ].slice(1,).toLowerCase())
if(correctVariable == undefined) { if(correctVariable === undefined) {
value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: red; border-radius: 4px; color: white"}) value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #f86a3e; padding-top: 2px; padding-bottom: 2px; color: white"})
} }
else{ else{
value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; border-radius: 4px; color: white"}) value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; padding-top: 2px; padding-bottom: 2px; color: white"})
} }
// console.log(correctVariables) // console.log(correctVariables)
} }
@@ -237,7 +238,13 @@ const CodeEditor = (props) => {
// console.log(currentLocation) // console.log(currentLocation)
var code_lines = localcodedata.split('\n') var code_lines = localcodedata.split('\n')
code_lines[currentLine] = code_lines[currentLine].slice(0,currentLocation[1]) + "$" + swapVariable + code_lines[currentLine].slice(currentLocation[1]+currentVariable.length,) var parsedVariable = currentVariable
if (currentVariable === undefined || currentVariable === null) {
console.log("Location: ", currentLocation)
parsedVariable= "$"
}
code_lines[currentLine] = code_lines[currentLine].slice(0,currentLocation[1]) + "$" + swapVariable + code_lines[currentLine].slice(currentLocation[1]+parsedVariable.length,)
// console.log(code_lines) // console.log(code_lines)
var updatedCode = code_lines.join('\n') var updatedCode = code_lines.join('\n')
// console.log(updatedCode) // console.log(updatedCode)
@@ -255,7 +262,7 @@ const CodeEditor = (props) => {
// console.log(found[i]); // console.log(found[i]);
for (var j = 0; j < actionlist.length; j++) { for (var j = 0; j < actionlist.length; j++) {
if(found[i].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){ if(found[i].slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){
input = input.replace(found[i], JSON.stringify(actionlist[j].example)); input = input.replace(found[i], JSON.stringify(actionlist[j].example));
// console.log(input) // console.log(input)
// console.log(actionlist[j].example) // console.log(actionlist[j].example)
@@ -293,8 +300,8 @@ const CodeEditor = (props) => {
style: { style: {
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
color: "white", color: "white",
minWidth: 600, minWidth: isMobile ? "100%" : 600,
padding: 25, padding: isMobile ? "25px 10px 25px 10px" : 25,
border: theme.palette.defaultBorder, border: theme.palette.defaultBorder,
zIndex: 10012, zIndex: 10012,
}, },
@@ -318,7 +325,7 @@ const CodeEditor = (props) => {
</DialogTitle> </DialogTitle>
<IconButton <IconButton
style={{ style={{
marginLeft: 400, marginLeft: isMobile ? "80%" : 400,
height: 50, height: 50,
width: 50, width: 50,
}} }}
@@ -393,7 +400,7 @@ const CodeEditor = (props) => {
style={{ style={{
margin: 10, margin: 10,
padding: 10, padding: 10,
width: 250, width: isMobile ? "100%" : 250,
// textOverflow: 'ellipsis' // textOverflow: 'ellipsis'
}} }}
> >
@@ -478,10 +485,10 @@ const CodeEditor = (props) => {
Dark Theme Dark Theme
<Checkbox <Checkbox
onClick={() => { onClick={() => {
if (codeTheme == "gruvbox-dark") { if (codeTheme === "gruvbox-dark") {
setcodeTheme("duotone-light") setcodeTheme("duotone-light")
} }
if (codeTheme == "duotone-light"){ if (codeTheme === "duotone-light"){
setcodeTheme("gruvbox-dark") setcodeTheme("gruvbox-dark")
} }
}} }}
@@ -500,68 +507,70 @@ const CodeEditor = (props) => {
</div> </div>
<div> <div>
<DialogTitle {isMobile ? null :
style={{ <DialogTitle
paddingTop: 30,
paddingLeft: 10,
}}
>
<span
style={{ style={{
color: "white" paddingTop: 30,
paddingLeft: 10,
}} }}
> >
Output <span
</span> style={{
</DialogTitle> color: "white"
{validation === true ? }}
<ReactJson >
src={expOutput} Output
theme={theme.palette.jsonTheme} </span>
style={{ </DialogTitle>
borderRadius: 5, }
border: `2px solid ${theme.palette.inputColor}`, {isMobile ? null :
padding: 10, validation === true ?
maxHeight: 250, <ReactJson
minheight: 250, src={expOutput}
overflow: "auto", theme={theme.palette.jsonTheme}
}} style={{
collapsed={false} borderRadius: 5,
enableClipboard={(copy) => { border: `2px solid ${theme.palette.inputColor}`,
//handleReactJsonClipboard(copy); padding: 10,
}} maxHeight: 250,
displayDataTypes={false} minheight: 250,
onSelect={(select) => { overflow: "auto",
//HandleJsonCopy(validate.result, select, "exec"); }}
}} collapsed={false}
name={"JSON autocompletion"} enableClipboard={(copy) => {
/> //handleReactJsonClipboard(copy);
: }}
<p displayDataTypes={false}
id='expOutput' onSelect={(select) => {
style={{ //HandleJsonCopy(validate.result, select, "exec");
whiteSpace: "pre-wrap", }}
color: "#f85a3e", name={"JSON autocompletion"}
fontFamily: "monospace", />
backgroundColor: "#282828", :
padding: 20, <p
marginTop: -2, id='expOutput'
border: `2px solid ${theme.palette.inputColor}`, style={{
borderRadius: theme.palette.borderRadius, whiteSpace: "pre-wrap",
maxHeight: 250, color: "#f85a3e",
overflow: "auto", fontFamily: "monospace",
}} backgroundColor: "#282828",
> padding: 20,
{expOutput} marginTop: -2,
</p> border: `2px solid ${theme.palette.inputColor}`,
borderRadius: theme.palette.borderRadius,
maxHeight: 250,
overflow: "auto",
}}
>
{expOutput}
</p>
} }
<p <p
style={{ style={{
color: "white", color: "white",
fontFamily: "monospace", fontFamily: "monospace",
padding: 20, margin: 20,
paddingLeft: 10, marginTop: 30,
marginTop: -15,
}} }}
> >
JSON Validation: {validation ? "Correct" : "Incorrect"} JSON Validation: {validation ? "Correct" : "Incorrect"}
@@ -573,48 +582,44 @@ const CodeEditor = (props) => {
display: 'flex', display: 'flex',
}} }}
> >
<div> <button
<button style={{
style={{ color: "white",
color: "white", background: "#383b49",
background: "#383b49", border: "none",
border: "none", height: 35,
height: 35, flex: 1,
width: 290, marginLeft: 5,
marginLeft: 5, marginTop: 20,
marginTop: 20, cursor: "pointer"
cursor: "pointer" }}
}} onClick={() => {
onClick={() => { setExpansionModalOpen(false)
setExpansionModalOpen(false) }}
}} >
> Cancel
Cancel </button>
</button> <button
</div> style={{
<div> color: "white",
<button background: "#f85a3e",
style={{ border: "none",
color: "white", height: 35,
background: "#f85a3e", flex: 1,
border: "none", marginLeft: 10,
height: 35, marginTop: 20,
width: 290, cursor: "pointer"
marginLeft: 10, }}
marginTop: 20, onClick={(event) => {
cursor: "pointer" // console.log(codedata)
}} // console.log(fieldCount)
onClick={(event) => { changeActionParameterCodeMirror(event, fieldCount, localcodedata)
// console.log(codedata) setExpansionModalOpen(false)
// console.log(fieldCount) setcodedata(localcodedata)
changeActionParameterCodeMirror(event, fieldCount, localcodedata) }}
setExpansionModalOpen(false) >
setcodedata(localcodedata) Done
}} </button>
>
Done
</button>
</div>
</div> </div>
</Dialog>) </Dialog>)
} }
+242 -191
View File
@@ -87,6 +87,7 @@ import {
ExpandMore as ExpandMoreIcon, ExpandMore as ExpandMoreIcon,
VpnKey as VpnKeyIcon, VpnKey as VpnKeyIcon,
AddComment as AddCommentIcon, AddComment as AddCommentIcon,
Edit as EditIcon,
} from "@material-ui/icons"; } from "@material-ui/icons";
import { import {
@@ -251,6 +252,7 @@ const AngularWorkflow = (defaultprops) => {
const [triggerFolders, setTriggerFolders] = React.useState([]); const [triggerFolders, setTriggerFolders] = React.useState([]);
const [workflows, setWorkflows] = React.useState([]); const [workflows, setWorkflows] = React.useState([]);
const [showEnvironment, setShowEnvironment] = React.useState(false); const [showEnvironment, setShowEnvironment] = React.useState(false);
const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);
const [workflow, setWorkflow] = React.useState({}); const [workflow, setWorkflow] = React.useState({});
const [userSettings, setUserSettings] = React.useState({}); const [userSettings, setUserSettings] = React.useState({});
@@ -1257,98 +1259,100 @@ const AngularWorkflow = (defaultprops) => {
}; };
const executeWorkflow = (executionArgument, startNode, hasSaved) => { const executeWorkflow = (executionArgument, startNode, hasSaved) => {
if (hasSaved === false) { ReactDOM.unstable_batchedUpdates(() => {
setExecutionRequestStarted(true); if (hasSaved === false) {
saveWorkflow(workflow, executionArgument, startNode); setExecutionRequestStarted(true);
console.log("FIXME: Might have forgotten to save before executing."); saveWorkflow(workflow, executionArgument, startNode);
return; console.log("FIXME: Might have forgotten to save before executing.");
} return;
}
if (workflow.public) { if (workflow.public) {
alert.info("Save it to get a new version"); alert.info("Save it to get a new version");
} }
var returncheck = monitorUpdates(); var returncheck = monitorUpdates();
if (!returncheck) { if (!returncheck) {
alert.error("No startnode set."); alert.error("No startnode set.");
return; return;
} }
setVisited([]); setVisited([]);
setExecutionRequest({}); setExecutionRequest({});
stop(); stop();
var curelements = cy.elements(); var curelements = cy.elements();
for (var i = 0; i < curelements.length; i++) { for (var i = 0; i < curelements.length; i++) {
curelements[i].addClass("not-executing-highlight"); curelements[i].addClass("not-executing-highlight");
} }
const data = { execution_argument: executionArgument, start: startNode }; const data = { execution_argument: executionArgument, start: startNode };
fetch( fetch(
globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute", globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute",
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, },
credentials: "include", credentials: "include",
body: JSON.stringify(data), body: JSON.stringify(data),
} }
) )
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!"); console.log("Status not 200 for WORKFLOW EXECUTION :O!");
} }
return response.json(); return response.json();
}) })
.then((responseJson) => { .then((responseJson) => {
if (!responseJson.success) { if (!responseJson.success) {
alert.error("Failed to start: " + responseJson.reason); alert.error("Failed to start: " + responseJson.reason);
setExecutionRunning(false); setExecutionRunning(false);
setExecutionRequestStarted(false); setExecutionRequestStarted(false);
stop(); stop();
for (var i = 0; i < curelements.length; i++) { for (var i = 0; i < curelements.length; i++) {
curelements[i].removeClass("not-executing-highlight"); curelements[i].removeClass("not-executing-highlight");
} }
return; return;
} else { } else {
setExecutionRunning(true); setExecutionRunning(true);
setExecutionRequestStarted(false); setExecutionRequestStarted(false);
} }
if ( if (
responseJson.execution_id === "" || responseJson.execution_id === "" ||
responseJson.execution_id === undefined || responseJson.execution_id === undefined ||
responseJson.authorization === "" || responseJson.authorization === "" ||
responseJson.authorization === undefined responseJson.authorization === undefined
) { ) {
alert.error("Something went wrong during execution startup"); alert.error("Something went wrong during execution startup");
console.log("BAD RESPONSE FOR EXECUTION: ", responseJson); console.log("BAD RESPONSE FOR EXECUTION: ", responseJson);
setExecutionRunning(false); setExecutionRunning(false);
setExecutionRequestStarted(false); setExecutionRequestStarted(false);
stop(); stop();
for (var i = 0; i < curelements.length; i++) { for (var i = 0; i < curelements.length; i++) {
curelements[i].removeClass("not-executing-highlight"); curelements[i].removeClass("not-executing-highlight");
} }
return; return;
} }
setExecutionRequest({ setExecutionRequest({
execution_id: responseJson.execution_id, execution_id: responseJson.execution_id,
authorization: responseJson.authorization, authorization: responseJson.authorization,
}); });
setExecutionData({}); setExecutionData({});
setExecutionModalOpen(true); setExecutionModalOpen(true);
setExecutionModalView(1); setExecutionModalView(1);
start(); start();
}) })
.catch((error) => { .catch((error) => {
alert.error(error.toString()); alert.error(error.toString());
}); });
})
}; };
// This can be used to only show prioritzed ones later // This can be used to only show prioritzed ones later
@@ -1542,6 +1546,35 @@ const AngularWorkflow = (defaultprops) => {
}); });
}; };
// Searhc by username, userId, workflow, appId should all work
const getUserProfile = (username) => {
fetch(`${globalUrl}/api/v1/users/creators/${username}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success !== false) {
console.log("Found creator: ", responseJson)
setCreatorProfile(responseJson)
}
})
.catch((error) => {
console.log(error);
})
}
const getWorkflow = (workflow_id, sourcenode) => { const getWorkflow = (workflow_id, sourcenode) => {
console.log( console.log(
//`Getting workflow ${workflow_id} with append value ${sourcenode}` //`Getting workflow ${workflow_id} with append value ${sourcenode}`
@@ -1582,9 +1615,7 @@ const AngularWorkflow = (defaultprops) => {
} }
if (responseJson.public) { if (responseJson.public) {
alert.info( alert.info("This workflow is public. Save the workflow to start using it.");
"This workflow is public. Save the workflow to "
);
console.log("RESP: ", responseJson) console.log("RESP: ", responseJson)
if (Object.getOwnPropertyNames(creatorProfile).length === 0) { if (Object.getOwnPropertyNames(creatorProfile).length === 0) {
@@ -8686,13 +8717,13 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "auth_headers", "value": ""} workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "auth_headers", "value": ""}
setWorkflow(workflow) setWorkflow(workflow)
} else { } else {
// Always update if (selectedTrigger.environment !== "cloud") {
const newUrl = referenceUrl+"webhook_"+selectedTrigger.id const newUrl = referenceUrl+"webhook_"+selectedTrigger.id
//console.log("Validating webhook url: ", newUrl) if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) { console.log("Url is wrong - should update. This functionality is temporarily disabled.")
console.log("Url is wrong - updating") //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl
workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl //setWorkflow(workflow)
setWorkflow(workflow) }
} }
} }
@@ -8898,13 +8929,13 @@ const AngularWorkflow = (defaultprops) => {
// Always update // Always update
const newUrl = referenceUrl + "webhook_" + selectedTrigger.id; const newUrl = referenceUrl + "webhook_" + selectedTrigger.id;
//console.log("Validating webhook url: ", newUrl); //console.log("Validating webhook url: ", newUrl);
if ( if (selectedTrigger.environment !== "cloud") {
newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
) { console.log("Url is wrong. NOT updating because of hybrid.");
console.log("Url is wrong - updating"); //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl;
workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl; //setWorkflow(workflow);
setWorkflow(workflow); }
} }
} }
const trigger_header_auth = const trigger_header_auth =
@@ -8976,28 +9007,21 @@ const AngularWorkflow = (defaultprops) => {
fullWidth fullWidth
onChange={(e) => { onChange={(e) => {
selectedTrigger.environment = e.target.value; selectedTrigger.environment = e.target.value;
setSelectedTrigger(selectedTrigger);
if (e.target.value === "cloud") { if (e.target.value === "cloud") {
const tmpvalue = const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
workflow.triggers[
selectedTriggerIndex
].parameters[0].value.split("/");
const urlpath = tmpvalue.slice(3, tmpvalue.length); const urlpath = tmpvalue.slice(3, tmpvalue.length);
const newurl = "https://shuffler.io/" + urlpath.join("/"); const newurl = "https://shuffler.io/" + urlpath.join("/");
workflow.triggers[selectedTriggerIndex].parameters[0].value = workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
newurl;
} else { } else {
const tmpvalue = const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
workflow.triggers[
selectedTriggerIndex
].parameters[0].value.split("/");
const urlpath = tmpvalue.slice(3, tmpvalue.length); const urlpath = tmpvalue.slice(3, tmpvalue.length);
const newurl = const newurl = window.location.origin + "/" + urlpath.join("/");
window.location.origin + "/" + urlpath.join("/"); workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
workflow.triggers[selectedTriggerIndex].parameters[0].value =
newurl;
} }
console.log("New value: ", workflow.triggers[selectedTriggerIndex].parameters[0])
selectedTrigger.parameters[0] = workflow.triggers[selectedTriggerIndex].parameters[0]
setSelectedTrigger(selectedTrigger);
setWorkflow(workflow); setWorkflow(workflow);
setUpdate(Math.random()); setUpdate(Math.random());
}} }}
@@ -9086,10 +9110,8 @@ const AngularWorkflow = (defaultprops) => {
} }
}} }}
helperText={ helperText={
workflow.triggers[selectedTriggerIndex].parameters[0] workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined &&
.value !== undefined && workflow.triggers[selectedTriggerIndex].parameters[0].value !== null &&
workflow.triggers[selectedTriggerIndex].parameters[0]
.value !== null &&
(workflow.triggers[ (workflow.triggers[
selectedTriggerIndex selectedTriggerIndex
].parameters[0].value.includes("localhost") || ].parameters[0].value.includes("localhost") ||
@@ -9115,7 +9137,7 @@ const AngularWorkflow = (defaultprops) => {
}} }}
fullWidth fullWidth
disabled disabled
defaultValue={ value={
workflow.triggers[selectedTriggerIndex].parameters[0].value workflow.triggers[selectedTriggerIndex].parameters[0].value
} }
color="primary" color="primary"
@@ -10515,31 +10537,33 @@ const AngularWorkflow = (defaultprops) => {
/> />
</Tooltip> </Tooltip>
)} )}
<Tooltip color="primary" title="Save (ctrl+s)" placement="top"> {userdata.avatar === creatorProfile.github_avatar ? null :
<span> <Tooltip color="primary" title="Save (ctrl+s)" placement="top">
<Button <span>
disabled={savingState !== 0} <Button
color="primary" disabled={savingState !== 0}
style={{ color="primary"
height: workflow.public ? 100 : 50, style={{
width: workflow.public ? 100 : 64, height: workflow.public ? 100 : 50,
marginLeft: 10, width: workflow.public ? 100 : 64,
}} marginLeft: 10,
variant={ }}
lastSaved && !workflow.public ? "outlined" : "contained" variant={
} lastSaved && !workflow.public ? "outlined" : "contained"
onClick={() => saveWorkflow()} }
> onClick={() => saveWorkflow()}
{savingState === 2 ? ( >
<CircularProgress style={{ height: 35, width: 35 }} /> {savingState === 2 ? (
) : savingState === 1 ? ( <CircularProgress style={{ height: 35, width: 35 }} />
<DoneIcon style={{ color: green }} /> ) : savingState === 1 ? (
) : ( <DoneIcon style={{ color: green }} />
<SaveIcon /> ) : (
)} <SaveIcon />
</Button> )}
</span> </Button>
</Tooltip> </span>
</Tooltip>
}
{workflow.public ? ( {workflow.public ? (
<Tooltip <Tooltip
color="secondary" color="secondary"
@@ -10774,6 +10798,8 @@ const AngularWorkflow = (defaultprops) => {
globalUrl={globalUrl} globalUrl={globalUrl}
setSelectedActionEnvironment={setSelectedActionEnvironment} setSelectedActionEnvironment={setSelectedActionEnvironment}
requiresAuthentication={requiresAuthentication} requiresAuthentication={requiresAuthentication}
setLastSaved={setLastSaved}
lastSaved={lastSaved}
/> />
} else if (Object.getOwnPropertyNames(selectedComment).length > 0) { } else if (Object.getOwnPropertyNames(selectedComment).length > 0) {
@@ -10849,34 +10875,6 @@ const AngularWorkflow = (defaultprops) => {
//}}>Execute websocket</Button> //}}>Execute websocket</Button>
// //
// Searhc by username, userId, workflow, appId should all work
const getUserProfile = (username) => {
fetch(`${globalUrl}/api/v1/users/creators/${username}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
return response.json();
})
.then((responseJson) => {
console.log("Found creator: ", responseJson)
if (responseJson.success !== false) {
setCreatorProfile(responseJson)
}
})
.catch((error) => {
console.log(error);
})
}
const leftView = workflow.public === true ? const leftView = workflow.public === true ?
<div style={{minHeight: "80vh", height: "100%", minWidth: leftBarSize-70, maxWidth: leftBarSize-70, zIndex: 0, padding: 35, borderRight: "1px solid rgba(91,96,100,1)",}}> <div style={{minHeight: "80vh", height: "100%", minWidth: leftBarSize-70, maxWidth: leftBarSize-70, zIndex: 0, padding: 35, borderRight: "1px solid rgba(91,96,100,1)",}}>
@@ -10929,14 +10927,6 @@ const AngularWorkflow = (defaultprops) => {
</div> </div>
</div> </div>
: null } : null }
<div style={{display: "flex", marginTop: 10, }}>
<Typography variant="body1">
Mitre Att&ck:
</Typography>
<Typography variant="body1" color="textSecondary">
TBD
</Typography>
</div>
{appGroup.length > 0 ? {appGroup.length > 0 ?
<div style={{display: "flex", marginTop: 10, }}> <div style={{display: "flex", marginTop: 10, }}>
<Typography variant="body1"> <Typography variant="body1">
@@ -10967,6 +10957,15 @@ const AngularWorkflow = (defaultprops) => {
</AvatarGroup> </AvatarGroup>
</div> </div>
: null} : null}
<div style={{display: "flex", marginTop: 10, }}>
<Typography variant="body1">
Mitre Att&ck:
</Typography>
<Typography variant="body1" color="textSecondary">
TBD
</Typography>
</div>
<div style={{display: "flex", marginTop: 10, }}> <div style={{display: "flex", marginTop: 10, }}>
<Typography variant="body1"> <Typography variant="body1">
Related Workflows: Related Workflows:
@@ -10985,6 +10984,19 @@ const AngularWorkflow = (defaultprops) => {
</Typography> </Typography>
</div> </div>
: null} : null}
{userdata.avatar === creatorProfile.github_avatar ?
<Button
color="secondary"
variant="outlined"
fullWidth
style={{marginTop: 15, }}
onClick={() => {
setEditWorkflowDetails(true)
}}
>
Edit Details
</Button>
: null}
</div> </div>
: isMobile && leftViewOpen ? : isMobile && leftViewOpen ?
<div <div
@@ -12311,23 +12323,35 @@ const parsedExecutionArgument = () => {
return ( return (
<div style={{maxWidth: 600, overflowX: "hidden", }}> <div style={{maxWidth: 600, overflowX: "hidden", }}>
<IconButton {data.value.length > 60 ?
style={{ <IconButton
marginBottom: 0, marginTop: 5, cursor: "pointer", style={{
}} marginBottom: 0, marginTop: 5, cursor: "pointer",
onClick={() => { padding: 3,
if (!showVariable) { border: "1px solid rgba(255,255,255,0.3)",
setOpen(!open) borderRadius: theme.palette.borderRadius,
} }}
}} onClick={() => {
> if (!showVariable) {
setOpen(!open)
}
}}
>
<Typography
variant="body1"
style={{}}
>
<b>{data.name}</b> {showVariable ? data.value : null}
</Typography>
</IconButton>
:
<Typography <Typography
variant="body1" variant="body1"
style={{}} style={{}}
> >
<b>{data.name}</b>: {showVariable ? data.value : null} <b>{data.name}</b>: {showVariable ? data.value : null}
</Typography> </Typography>
</IconButton> }
{open ? {open ?
<Typography <Typography
variant="body2" variant="body2"
@@ -12697,6 +12721,32 @@ const parsedExecutionArgument = () => {
</div> </div>
); );
const editWorkflowModal =
<Dialog
open={editWorkflowDetails}
PaperComponent={PaperComponent}
hideBackdrop={true}
disableEnforceFocus={true}
disableBackdropClick={true}
style={{ pointerEvents: "none" }}
aria-labelledby="draggable-dialog-title"
onClose={() => {
setEditWorkflowDetails(false)
}}
PaperProps={{
style: {
pointerEvents: "auto",
backgroundColor: surfaceColor,
color: "white",
border: theme.palette.defaultBorder,
maxWidth: "100%",
padding: 50,
},
}}
>
Edit workflow!
</Dialog>
const ExecutionVariableModal = (props) => { const ExecutionVariableModal = (props) => {
const { variableInfo } = props const { variableInfo } = props
@@ -13604,6 +13654,7 @@ const parsedExecutionArgument = () => {
{authenticationModal} {authenticationModal}
{codePopoutModal} {codePopoutModal}
{configureWorkflowModal} {configureWorkflowModal}
{editWorkflowModal}
<TextField <TextField
id="copy_element_shuffle" id="copy_element_shuffle"
value={to_be_copied} value={to_be_copied}
+359 -67
View File
@@ -4,6 +4,7 @@ import DetectionFramework from "../components/DetectionFramework.jsx";
// nodejs library that concatenates classes // nodejs library that concatenates classes
import classNames from "classnames"; import classNames from "classnames";
import theme from '../theme'; import theme from '../theme';
import { useNavigate, Link, useParams } from "react-router-dom";
// react plugin used to create charts // react plugin used to create charts
//import { Line, Bar } from "react-chartjs-2"; //import { Line, Bar } from "react-chartjs-2";
@@ -11,7 +12,9 @@ import { useAlert } from "react-alert";
import { import {
Tooltip, Tooltip,
TextField,
IconButton, IconButton,
Button,
Typography, Typography,
Grid, Grid,
Paper, Paper,
@@ -23,6 +26,7 @@ import {
DoneAll as DoneAllIcon, DoneAll as DoneAllIcon,
Description as DescriptionIcon, Description as DescriptionIcon,
PlayArrow as PlayArrowIcon, PlayArrow as PlayArrowIcon,
Edit as EditIcon,
} from "@material-ui/icons"; } from "@material-ui/icons";
import WorkflowPaper from "../components/WorkflowPaper.jsx" import WorkflowPaper from "../components/WorkflowPaper.jsx"
@@ -53,13 +57,114 @@ import {
TreeMapRect, TreeMapRect,
} from 'reaviz'; } from 'reaviz';
const UsecaseListComponent = ({keys, isCloud, globalUrl}) => { const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLoggedIn}) => {
const [expandedIndex, setExpandedIndex] = useState(-1); const [expandedIndex, setExpandedIndex] = useState(-1);
const [expandedItem, setExpandedItem] = useState(-1); const [expandedItem, setExpandedItem] = useState(-1);
const [inputUsecase, setInputUsecase] = useState({});
const [editing, setEditing] = useState(false);
const [description, setDescription] = useState("");
const [video, setVideo] = useState("");
const [blogpost, setBlogpost] = useState("");
const [mitreTags, setMitreTags] = useState([]);
let navigate = useNavigate();
if (keys === undefined || keys === null || keys.length === 0) { if (keys === undefined || keys === null || keys.length === 0) {
return null return null
} }
const getUsecase = (name, index, subindex) => {
fetch(globalUrl + "/api/v1/workflows/usecases/"+name.replaceAll(" ", "_"), {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
setInputUsecase({
"name": name,
})
} else {
setInputUsecase(responseJson)
}
setExpandedIndex(index)
setExpandedItem(subindex)
})
.catch((error) => {
//alert.error(error.toString());
setInputUsecase({})
setExpandedIndex(index)
setExpandedItem(subindex)
})
}
const setUsecaseItem = (inputUsecase) => {
var parsedUsecase = inputUsecase
if (blogpost !== inputUsecase.blogpost) {
inputUsecase.blogpost = blogpost
parsedUsecase.blogpost = blogpost
}
if (video !== inputUsecase.video) {
inputUsecase.video = video
parsedUsecase.video = video
}
if (description !== inputUsecase.description) {
inputUsecase.description = description
parsedUsecase.description = description
}
if (mitreTags !== inputUsecase.mitre) {
inputUsecase.mitre = mitreTags
parsedUsecase.mitre = mitreTags
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(parsedUsecase),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
//alert.error("Failed updating: " + responseJson.reason)
} else {
//alert.error("Failed to update framework for your org.")
}
} else {
//alert.info("Updated usecase.")
}
})
.catch((error) => {
//alert.error(error.toString());
//setFrameworkLoaded(true)
})
}
return ( return (
<div style={{marginTop: 25, minHeight: 1000,}}> <div style={{marginTop: 25, minHeight: 1000,}}>
<Typography variant="h1"> <Typography variant="h1">
@@ -81,6 +186,23 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => {
} }
const selectedItem = subindex === expandedItem && index === expandedIndex const selectedItem = subindex === expandedItem && index === expandedIndex
if (selectedItem && subcase.name !== undefined && inputUsecase.name !== undefined) {
if (subcase.name.toLowerCase().replaceAll(" ", "_") === inputUsecase.name.toLowerCase().replaceAll(" ", "_")) {
console.log("Input: ", inputUsecase)
if (inputUsecase.description !== undefined && inputUsecase.description !== null) {
subcase.description = inputUsecase.description
}
if (inputUsecase.blogpost !== undefined && inputUsecase.blogpost !== null) {
subcase.blogpost = inputUsecase.blogpost
}
if (inputUsecase.video !== undefined && inputUsecase.video !== null) {
subcase.video = inputUsecase.video
}
}
}
const finished = subcase.matches.length > 0 const finished = subcase.matches.length > 0
//const backgroundColor = selectedItem ? "inherit" : finished ? "inherit" : usecase.color //const backgroundColor = selectedItem ? "inherit" : finished ? "inherit" : usecase.color
const backgroundColor = "inherit" const backgroundColor = "inherit"
@@ -90,14 +212,12 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => {
<Grid item xs={selectedItem ? 12 : 4} key={subindex} style={{minHeight: 110,}} onClick={() => { <Grid item xs={selectedItem ? 12 : 4} key={subindex} style={{minHeight: 110,}} onClick={() => {
if (selectedItem) { if (selectedItem) {
} else { } else {
if (subcase.description !== undefined && subcase.description !== null && subcase.description.length > 0) { //if (subcase.description !== undefined && subcase.description !== null && subcase.description.length > 0) {
setExpandedIndex(index) getUsecase(subcase.name, index, subindex)
setExpandedItem(subindex) //}
}
} }
}}> }}>
<Paper style={{padding: "30px 30px 30px 30px", minHeight: 75, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => { <Paper style={{padding: "30px 30px 30px 30px", minHeight: 75, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => {
console.log("Clicked: ", subcase)
}}> }}>
{!selectedItem ? {!selectedItem ?
<div style={{textAlign: "left", position: "relative",}}> <div style={{textAlign: "left", position: "relative",}}>
@@ -131,7 +251,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => {
placement="top" placement="top"
> >
<IconButton <IconButton
style={{paddingTop: 5, }} style={{marginTop: finished ? 5 : 40, }}
onClick={(e) => { onClick={(e) => {
}} }}
> >
@@ -168,10 +288,37 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => {
<Typography variant="h6"> <Typography variant="h6">
<b>{subcase.name}</b> <b>{subcase.name}</b>
</Typography> </Typography>
<Typography variant="body2">
Description: {subcase.description}
</Typography>
<div style={{ position: "absolute", top: 0, right: 0 }}> <div style={{ position: "absolute", top: 0, right: 0 }}>
{isLoggedIn === true ?
<Tooltip
title="Click to edit"
placement="top"
>
<IconButton
style={{paddingTop: 5, }}
onClick={(e) => {
setEditing(true)
if (subcase.description !== undefined && subcase.description !== null) {
setDescription(subcase.description)
}
if (subcase.blogpost !== undefined && subcase.blogpost !== null) {
setBlogpost(subcase.blogpost)
}
if (subcase.video !== undefined && subcase.video !== null) {
setVideo(subcase.video)
}
if (subcase.mitre !== undefined && subcase.mitre !== null) {
setMitreTags(subcase.mitre)
}
}}
>
<EditIcon style={{ color: usecase.color }} />
</IconButton>
</Tooltip>
: null}
{subcase.blogpost !== null && subcase.blogpost !== undefined && subcase.blogpost.length > 0 ? {subcase.blogpost !== null && subcase.blogpost !== undefined && subcase.blogpost.length > 0 ?
<a <a
href={subcase.blogpost} href={subcase.blogpost}
@@ -224,6 +371,8 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => {
onClick={(e) => { onClick={(e) => {
setExpandedItem(-1) setExpandedItem(-1)
setExpandedIndex(-1) setExpandedIndex(-1)
setEditing(false)
setInputUsecase({})
}} }}
> >
<CloseIcon style={{ color: "white" }} /> <CloseIcon style={{ color: "white" }} />
@@ -231,65 +380,168 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => {
</Tooltip> </Tooltip>
</div> </div>
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, }}> <div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, }}>
{/* {editing ?
<img <div style={{flex: 1, marginRight: 50, }}>
alt={subcase.name} <Typography variant="h6">
src={"/images/detectionframework.png"} Editing!
style={{ </Typography>
flex: 1, <TextField
height: 400, style={{
marginTop: 10,
marginRight: 10,
}}
variant="outlined"
fullWidth
color="primary"
label="Description"
placeholder={"Description"}
value={description}
onChange={(event) => {
setDescription(event.target.value)
}}
id="descriptionEditng"
/>
<TextField
style={{
marginTop: 10,
marginRight: 10,
}}
variant="outlined"
fullWidth
color="primary"
label="Blogpost"
placeholder={"Blogpost"}
value={blogpost}
onChange={(event) => {
setBlogpost(event.target.value)
}}
id="blogpostEditing"
/>
<TextField
style={{
marginTop: 10,
marginRight: 10,
}}
variant="outlined"
fullWidth
color="primary"
label="Video"
placeholder={"Video"}
value={video}
onChange={(event) => {
setVideo(event.target.value)
}}
id="videoEditing"
/>
<div
style={{
display: 'flex',
}}
>
<Button
style={{
color: "white",
background: "#383b49",
border: "none",
height: 35,
flex: 1,
marginLeft: 5,
marginTop: 20,
cursor: "pointer"
}}
onClick={() => {
setDescription("")
setVideo("")
setBlogpost("")
setEditing(false)
}}
>
Cancel
</Button>
<Button
style={{
color: "white",
background: "#f85a3e",
border: "none",
height: 35,
flex: 1,
marginLeft: 10,
marginTop: 20,
cursor: "pointer"
}}
onClick={(event) => {
setEditing(false)
setUsecaseItem(inputUsecase)
setDescription("")
setVideo("")
setBlogpost("")
}}
>
Save
</Button>
</div>
</div>
:
<div style={{flex: 1, textAlign: "left",}}>
<Typography variant="body1">
{subcase.description}
</Typography>
<Typography variant="h6" style={{marginTop: 15, }}>
Your workflow{subcase.matches.length === 1 ? "" : "s"} ({subcase.matches.length})
</Typography>
{subcase.matches.length > 0 ?
<Grid container xs={3} style={{maxWidth: 325, marginTop: 10, }}>
{subcase.matches.map((workflow, workflowindex) => {
return (
<Grid key={workflowindex} item index={workflowindex} xs={12}>
<WorkflowPaper key={workflowindex} data={workflow} />
</Grid>
)
})}
</Grid>
:
<div>
<Typography variant="body1" color="textSecondary">
No workflow selected yet.
</Typography>
</div>
}
{isCloud !== false ?
<div>
<Typography variant="h6" style={{marginTop: 15, cursor: "pointer",}} onClick={() => {
navigate("/search?tab=workflows&q="+subcase.name)
}}>
Public workflows
</Typography>
{/*
<div>
<Typography variant="body1" color="textSecondary">
No workflows yet.
</Typography>
</div>
*/}
</div>
: null}
</div>
}
<div style={{
height: 400,
width: 400, width: 400,
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
border: "1px solid rgba(255,255,255,0.3)", border: "1px solid rgba(255,255,255,0.3)",
}} }}>
/> <DetectionFramework
*/} inputUsecase={inputUsecase}
frameworkData={frameworkData}
<div style={{ selectedOption={"Draw"}
height: 400, showOptions={false}
width: 400, isLoaded={true}
borderRadius: theme.palette.borderRadius, isLoggedIn={true}
border: "1px solid rgba(255,255,255,0.3)", globalUrl={globalUrl}
}}> size={0.7}
<DetectionFramework />
frameworkData={undefined} </div>
selectedOption={"Draw"}
showOptions={false}
isLoaded={true}
isLoggedIn={true}
globalUrl={globalUrl}
size={0.7}
/>
</div> </div>
<div style={{flex: 1, marginLeft: 10, textAlign: "center",}}>
<Typography variant="h6">
Your workflow{subcase.matches.length === 1 ? "" : "s"} ({subcase.matches.length})
</Typography>
{subcase.matches.length > 0 ?
<Grid container xs={3} style={{maxWidth: 325, margin: "auto", marginTop: 10, itemAlign: "center", }}>
{subcase.matches.map((workflow, workflowindex) => {
return (
<Grid key={workflowindex} item index={workflowindex} xs={12}>
<WorkflowPaper key={workflowindex} data={workflow} />
</Grid>
)
})}
</Grid>
:
<div>
<Typography variant="body1" color="textSecondary">
No workflow selected yet.
</Typography>
</div>
}
{isCloud !== false ?
<Typography variant="h6">
Public workflows
</Typography>
: null}
</div>
</div>
</div> </div>
} }
</Paper> </Paper>
@@ -449,11 +701,44 @@ const Dashboard = (props) => {
const [selectedUsecases, setSelectedUsecases] = useState([]); const [selectedUsecases, setSelectedUsecases] = useState([]);
const [usecases, setUsecases] = useState([]); const [usecases, setUsecases] = useState([]);
const [workflows, setWorkflows] = useState([]); const [workflows, setWorkflows] = useState([]);
const [frameworkData, setFrameworkData] = useState(undefined);
const isCloud = const isCloud =
window.location.host === "localhost:3002" || window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io"; window.location.host === "shuffler.io";
const getFramework = () => {
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
//alert.error("Failed loading: " + responseJson.reason)
} else {
//alert.error("Failed to load framework for your org.")
}
} else {
setFrameworkData(responseJson)
}
})
.catch((error) => {
alert.error(error.toString());
})
}
const getAvailableWorkflows = () => { const getAvailableWorkflows = () => {
fetch(globalUrl + "/api/v1/workflows", { fetch(globalUrl + "/api/v1/workflows", {
method: "GET", method: "GET",
@@ -605,6 +890,7 @@ const Dashboard = (props) => {
useEffect(() => { useEffect(() => {
getAvailableWorkflows() getAvailableWorkflows()
getFramework()
//fetchUsecases() //fetchUsecases()
}, []); }, []);
@@ -865,7 +1151,7 @@ const Dashboard = (props) => {
</div> </div>
{usecases !== null && usecases !== undefined && usecases.length > 0 ? {usecases !== null && usecases !== undefined && usecases.length > 0 ?
<div style={{ display: "flex", marginLeft: 120,}}> <div style={{ display: "flex", marginLeft: 180, }}>
{usecases.map((usecase, index) => { {usecases.map((usecase, index) => {
return ( return (
<Chip <Chip
@@ -898,7 +1184,13 @@ const Dashboard = (props) => {
</div> </div>
: null} : null}
<UsecaseListComponent keys={selectedUsecases} isCloud={isCloud} globalUrl={globalUrl} /> <UsecaseListComponent
isLoggedIn={isLoggedIn}
frameworkData={frameworkData}
keys={selectedUsecases}
isCloud={isCloud}
globalUrl={globalUrl}
/>
{treeKeys.length > 0 ? {treeKeys.length > 0 ?
<TreeChart keys={treeKeys} /> <TreeChart keys={treeKeys} />
+9 -4
View File
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import ReactDOM from "react-dom"
import DetectionFramework from "../components/DetectionFramework.jsx"; import DetectionFramework from "../components/DetectionFramework.jsx";
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
@@ -39,14 +40,18 @@ const Framework = (props) => {
} else { } else {
alert.error("Failed to load framework for your org.") alert.error("Failed to load framework for your org.")
} }
} else {
setFrameworkLoaded(true) setFrameworkLoaded(true)
setFrameworkData(responseJson) } else {
ReactDOM.unstable_batchedUpdates(() => {
setFrameworkData(responseJson)
setFrameworkLoaded(true)
})
} }
}) })
.catch((error) => { .catch((error) => {
alert.error(error.toString());
setFrameworkLoaded(true) setFrameworkLoaded(true)
alert.error(error.toString());
}) })
} }
@@ -63,7 +68,7 @@ const Framework = (props) => {
</Button> </Button>
</Link> </Link>
</div> </div>
{frameworkLoaded === true ? {frameworkLoaded === true && isLoaded ?
<DetectionFramework <DetectionFramework
frameworkData={frameworkData} frameworkData={frameworkData}
selectedOption={"Draw"} selectedOption={"Draw"}
+99 -15
View File
@@ -509,6 +509,8 @@ const Workflows = (props) => {
const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); const [newWorkflowTags, setNewWorkflowTags] = React.useState([]);
const [defaultReturnValue, setDefaultReturnValue] = React.useState(""); const [defaultReturnValue, setDefaultReturnValue] = React.useState("");
const [blogpost, setBlogpost] = React.useState("");
const [status, setStatus] = React.useState("test");
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
const [publishModalOpen, setPublishModalOpen] = React.useState(false); const [publishModalOpen, setPublishModalOpen] = React.useState(false);
@@ -816,7 +818,10 @@ const Workflows = (props) => {
data.tags, data.tags,
data.default_return_value, data.default_return_value,
{}, {},
false false,
[],
"",
data.status,
) )
.then((response) => { .then((response) => {
if (response !== undefined) { if (response !== undefined) {
@@ -830,7 +835,10 @@ const Workflows = (props) => {
data.tags, data.tags,
data.default_return_value, data.default_return_value,
data, data,
false false,
[],
"",
data.status
).then((response) => { ).then((response) => {
if (response !== undefined) { if (response !== undefined) {
alert.success(`Successfully imported ${data.name}`); alert.success(`Successfully imported ${data.name}`);
@@ -883,12 +891,18 @@ const Workflows = (props) => {
if (responseJson !== undefined) { if (responseJson !== undefined) {
setWorkflows(responseJson); setWorkflows(responseJson);
fetchUsecases(responseJson) fetchUsecases(responseJson)
var setProdFilter = false
if (responseJson !== undefined) { if (responseJson !== undefined) {
var actionnamelist = []; var actionnamelist = [];
var parsedactionlist = []; var parsedactionlist = [];
for (var key in responseJson) { for (var key in responseJson) {
const workflow = responseJson[key]
if (workflow.status === "production") {
setProdFilter = true
}
for (var actionkey in responseJson[key].actions) { for (var actionkey in responseJson[key].actions) {
const action = responseJson[key].actions[actionkey]; const action = responseJson[key].actions[actionkey];
//console.log("Action: ", action) //console.log("Action: ", action)
@@ -905,10 +919,22 @@ const Workflows = (props) => {
setActionImageList(parsedactionlist); setActionImageList(parsedactionlist);
} }
setFilteredWorkflows(responseJson);
setWorkflowDone(true); if (setProdFilter === true) {
setFilters(["status:production"]);
const newWorkflows = responseJson.filter(workflow => workflow.status === "production")
console.log(newWorkflows)
if (newWorkflows !== undefined && newWorkflows !== null) {
setFilteredWorkflows(newWorkflows);
} else {
setFilteredWorkflows(responseJson);
}
} else {
setFilteredWorkflows(responseJson);
}
// Ensures the zooming happens only once per load // Ensures the zooming happens only once per load
setWorkflowDone(true);
setTimeout(() => { setTimeout(() => {
setFirstLoad(false) setFirstLoad(false)
}, 100) }, 100)
@@ -946,7 +972,7 @@ const Workflows = (props) => {
if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) {
for (var usecasekey in workflow.usecase_ids) { for (var usecasekey in workflow.usecase_ids) {
if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) {
console.log("Got match: ", workflow.usecase_ids[usecasekey]) //console.log("Got match: ", workflow.usecase_ids[usecasekey])
category.matches.push({ category.matches.push({
"workflow": workflow.id, "workflow": workflow.id,
@@ -1247,7 +1273,8 @@ const Workflows = (props) => {
// Add correct ID's for triggers // Add correct ID's for triggers
// Add mag // Add mag
data.status = "test"
let dataStr = JSON.stringify(data); let dataStr = JSON.stringify(data);
let dataUri = let dataUri =
"data:application/json;charset=utf-8," + encodeURIComponent(dataStr); "data:application/json;charset=utf-8," + encodeURIComponent(dataStr);
@@ -1638,6 +1665,7 @@ const Workflows = (props) => {
} }
} }
console.log("data: ", data)
return ( return (
<div style={{width: "100%", position: "relative",}}> <div style={{width: "100%", position: "relative",}}>
<Paper square style={paperAppStyle}> <Paper square style={paperAppStyle}>
@@ -1667,7 +1695,16 @@ const Workflows = (props) => {
{image} {image}
</div> </div>
</Tooltip> </Tooltip>
<Tooltip title={`Edit ${data.name}`} placement="bottom"> <Tooltip arrow title={
<span style={{}}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
<img src={data.image} alt={data.name} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 250, minHeigth: 250, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Edit {data.name}
</Typography>
</span>
} placement="bottom">
<Typography <Typography
variant="body1" variant="body1"
style={{ style={{
@@ -1732,7 +1769,7 @@ const Workflows = (props) => {
} }
<Tooltip <Tooltip
color="primary" color="primary"
title="Trigger amount" title="Amount of triggers"
placement="bottom" placement="bottom"
> >
<span <span
@@ -1884,6 +1921,8 @@ const Workflows = (props) => {
editingWorkflow, editingWorkflow,
redirect, redirect,
currentUsecases, currentUsecases,
inputblogpost,
inputstatus,
) => { ) => {
var method = "POST"; var method = "POST";
var extraData = ""; var extraData = "";
@@ -1905,6 +1944,8 @@ const Workflows = (props) => {
if (tags !== undefined) { if (tags !== undefined) {
workflowdata["tags"] = tags; workflowdata["tags"] = tags;
} }
workflowdata["blogpost"] = inputblogpost
workflowdata["status"] = inputstatus
if (defaultReturnValue !== undefined) { if (defaultReturnValue !== undefined) {
workflowdata["default_return_value"] = defaultReturnValue; workflowdata["default_return_value"] = defaultReturnValue;
@@ -2005,7 +2046,10 @@ const Workflows = (props) => {
data.tags, data.tags,
data.default_return_value, data.default_return_value,
{}, {},
false false,
[],
"",
data.status,
) )
.then((response) => { .then((response) => {
if (response !== undefined) { if (response !== undefined) {
@@ -2022,7 +2066,10 @@ const Workflows = (props) => {
data.tags, data.tags,
data.default_return_value, data.default_return_value,
data, data,
false false,
[],
"",
data.status,
).then((response) => { ).then((response) => {
if (response !== undefined) { if (response !== undefined) {
alert.success("Successfully imported " + data.name); alert.success("Successfully imported " + data.name);
@@ -2212,7 +2259,7 @@ const Workflows = (props) => {
} }
<Tooltip <Tooltip
color="primary" color="primary"
title="Trigger amount" title="Amount of triggers"
placement="bottom" placement="bottom"
> >
<span <span
@@ -2535,7 +2582,41 @@ const Workflows = (props) => {
</div> </div>
{showMoreClicked ? {showMoreClicked ?
<span> <span style={{marginTop: 25, }}>
<TextField
onBlur={(event) => {
if (event.target.value.toLowerCase() === "test") {
setStatus("test")
} else if (event.target.value.toLowerCase() === "production") {
setStatus("production")
}
}}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={status}
placeholder="The status of the workflow. Can be test or production."
rows="1"
margin="dense"
fullWidth
/>
<TextField
onBlur={(event) => setBlogpost(event.target.value)}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={blogpost}
placeholder="A blogpost or other reference for how this work workflow was built, and what it's for."
rows="1"
margin="dense"
fullWidth
/>
<TextField <TextField
onBlur={(event) => setDefaultReturnValue(event.target.value)} onBlur={(event) => setDefaultReturnValue(event.target.value)}
InputProps={{ InputProps={{
@@ -2596,6 +2677,8 @@ const Workflows = (props) => {
editingWorkflow, editingWorkflow,
false, false,
selectedUsecases, selectedUsecases,
blogpost,
status,
); );
setNewWorkflowName(""); setNewWorkflowName("");
@@ -2612,6 +2695,8 @@ const Workflows = (props) => {
{}, {},
true, true,
selectedUsecases, selectedUsecases,
blogpost,
status,
); );
} }
@@ -2999,7 +3084,6 @@ const Workflows = (props) => {
backgroundColor: filters.includes(usecase.name.toLowerCase()) ? usecase.color : theme.palette.surfaceColor, backgroundColor: filters.includes(usecase.name.toLowerCase()) ? usecase.color : theme.palette.surfaceColor,
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
marginRight: index === usecases.length-1 ? 0 : 10, marginRight: index === usecases.length-1 ? 0 : 10,
height: 60,
cursor: "pointer", cursor: "pointer",
border: `2px solid ${usecase.color}`, border: `2px solid ${usecase.color}`,
overflow: "hidden", overflow: "hidden",