Added PoC autocomplete feature based on app return example
This commit is contained in:
@@ -142,6 +142,7 @@ type WorkflowAppAction struct {
|
|||||||
} `json:"execution_variable" datastore:"execution_variables"`
|
} `json:"execution_variable" datastore:"execution_variables"`
|
||||||
Returns struct {
|
Returns struct {
|
||||||
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
|
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
|
||||||
|
Example string `json:"example" datastore:"example" yaml:"example"`
|
||||||
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
|
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
|
||||||
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
|
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
|
||||||
} `json:"returns" datastore:"returns"`
|
} `json:"returns" datastore:"returns"`
|
||||||
@@ -4205,6 +4206,16 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if workflowapp.Name == "thehive" {
|
||||||
|
for _, action := range workflowapp.Actions {
|
||||||
|
if len(action.Returns.Example) > 0 {
|
||||||
|
log.Printf("ACTION: %#v", action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if skip {
|
if skip {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import cxtmenu from 'cytoscape-cxtmenu';
|
|||||||
|
|
||||||
import { w3cwebsocket as W3CWebSocket } from "websocket";
|
import { w3cwebsocket as W3CWebSocket } from "websocket";
|
||||||
import { useAlert } from "react-alert";
|
import { useAlert } from "react-alert";
|
||||||
|
import { GetParsedPaths } from "./Apps";
|
||||||
|
|
||||||
const surfaceColor = "#27292D"
|
const surfaceColor = "#27292D"
|
||||||
const inputColor = "#383B40"
|
const inputColor = "#383B40"
|
||||||
@@ -2256,6 +2257,7 @@ const AngularWorkflow = (props) => {
|
|||||||
const [showDropdown, setShowDropdown] = React.useState(false)
|
const [showDropdown, setShowDropdown] = React.useState(false)
|
||||||
const [showDropdownNumber, setShowDropdownNumber] = React.useState(0)
|
const [showDropdownNumber, setShowDropdownNumber] = React.useState(0)
|
||||||
const [actionlist, setActionlist] = React.useState([])
|
const [actionlist, setActionlist] = React.useState([])
|
||||||
|
const [jsonList, setJsonList] = React.useState([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedActionParameters !== null && selectedActionParameters.length === 0) {
|
if (selectedActionParameters !== null && selectedActionParameters.length === 0) {
|
||||||
@@ -2317,6 +2319,20 @@ const AngularWorkflow = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.target.value[event.target.value.length-1] === ".") {
|
||||||
|
console.log("GET THE LAST ARGUMENT FOR !")
|
||||||
|
//const [jsonList, getJsonList] = React.useState([])
|
||||||
|
const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"}
|
||||||
|
const returnJson = GetParsedPaths(inputdata, "")
|
||||||
|
console.log(jsonList)
|
||||||
|
setJsonList(returnJson)
|
||||||
|
|
||||||
|
if (!showDropdown) {
|
||||||
|
setShowDropdown(true)
|
||||||
|
setShowDropdownNumber(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectedActionParameters[count].value = event.target.value
|
selectedActionParameters[count].value = event.target.value
|
||||||
selectedAction.parameters[count].value = event.target.value
|
selectedAction.parameters[count].value = event.target.value
|
||||||
setSelectedAction(selectedAction)
|
setSelectedAction(selectedAction)
|
||||||
@@ -2605,7 +2621,49 @@ const AngularWorkflow = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{datafield}
|
{datafield}
|
||||||
{showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" ?
|
{showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length > 0 ?
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel id="action-autocompleter" style={{marginLeft: 10, color: "white"}}><ArrowUpwardIcon />Autocomplete</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="action-autocompleter"
|
||||||
|
SelectDisplayProps={{
|
||||||
|
style: {
|
||||||
|
marginLeft: 10,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e) => {
|
||||||
|
//parsedValues.push({"name": basekeyname, "autocomplete": `${basekey}.${key}`})
|
||||||
|
console.log("CHANGE!")
|
||||||
|
if (selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === ".") {
|
||||||
|
e.target.value.autocomplete = e.target.value.autocomplete.slice(1, e.target.value.autocomplete.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedActionParameters[count].value += e.target.value.autocomplete
|
||||||
|
selectedAction.parameters[count].value = selectedActionParameters[count].value
|
||||||
|
console.log("TARGET: ", selectedActionParameters)
|
||||||
|
setSelectedAction(selectedAction)
|
||||||
|
setUpdate("action"+e.target.value.name)
|
||||||
|
|
||||||
|
setShowDropdown(false)
|
||||||
|
}}
|
||||||
|
style={{border: `2px solid #f85a3e`, color: "white", height: "50px"}}
|
||||||
|
>
|
||||||
|
{jsonList.map(data => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={data.name} style={{backgroundColor: inputColor, color: "white"}} value={data} onMouseOver={() => {}}>
|
||||||
|
<Tooltip color="primary" title={`Value: ${data.value}`} placement="left">
|
||||||
|
<div style={{display: "flex"}}>
|
||||||
|
{data.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
: null}
|
||||||
|
{showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length === 0 ?
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel id="action-autocompleter" style={{marginLeft: 10, color: "white"}}><ArrowUpwardIcon />Autocomplete</InputLabel>
|
<InputLabel id="action-autocompleter" style={{marginLeft: 10, color: "white"}}><ArrowUpwardIcon />Autocomplete</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -2920,6 +2978,9 @@ const AngularWorkflow = (props) => {
|
|||||||
<AppActionArguments key={selectedAction.id} selectedAction={selectedAction} />
|
<AppActionArguments key={selectedAction.id} selectedAction={selectedAction} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{}}>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
@@ -3288,7 +3349,7 @@ const AngularWorkflow = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button
|
<Button
|
||||||
style={{borderRadius: "0px"}}
|
style={{borderRadius: "0px"}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedEdge({})
|
setSelectedEdge({})
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import Input from '@material-ui/core/Input';
|
|||||||
import YAML from 'yaml'
|
import YAML from 'yaml'
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import Breadcrumbs from '@material-ui/core/Breadcrumbs';
|
import Breadcrumbs from '@material-ui/core/Breadcrumbs';
|
||||||
|
import ReactJson from 'react-json-view'
|
||||||
|
|
||||||
import CachedIcon from '@material-ui/icons/Cached';
|
import CachedIcon from '@material-ui/icons/Cached';
|
||||||
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
|
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
|
||||||
@@ -39,6 +40,51 @@ import CircularProgress from '@material-ui/core/CircularProgress';
|
|||||||
const surfaceColor = "#27292D"
|
const surfaceColor = "#27292D"
|
||||||
const inputColor = "#383B40"
|
const inputColor = "#383B40"
|
||||||
|
|
||||||
|
// Parses JSON data into keys that can be used everywhere :)
|
||||||
|
export const GetParsedPaths = (inputdata, basekey) => {
|
||||||
|
const splitkey = " => "
|
||||||
|
var parsedValues = []
|
||||||
|
for (const [key, value] of Object.entries(inputdata)) {
|
||||||
|
|
||||||
|
// Check if loop or JSON
|
||||||
|
const extra = basekey.length > 0 ? splitkey : ""
|
||||||
|
const basekeyname = `${basekey.slice(1,basekey.length).split(".").join(splitkey)}${extra}${key}`
|
||||||
|
if (typeof(value) === 'object') {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
// Check if each item is object
|
||||||
|
parsedValues.push({"name": basekeyname, "autocomplete": `${basekey}.${key}`})
|
||||||
|
parsedValues.push({"name": `${basekeyname}${splitkey}list`, "autocomplete": `${basekey}.${key}.#`})
|
||||||
|
|
||||||
|
// Only check the first. This would be probably be dumb otherwise.
|
||||||
|
for (var subkey in value) {
|
||||||
|
if (typeof(value) === 'object') {
|
||||||
|
const returnValues = GetParsedPaths(value[subkey], `${basekey}.${key}.#`)
|
||||||
|
for (var subkey in returnValues) {
|
||||||
|
parsedValues.push(returnValues[subkey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't need else as # (all items) is already defined before the loop
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
//console.log(key+" is array")
|
||||||
|
} else {
|
||||||
|
parsedValues.push({"name": basekeyname, "autocomplete": `${basekey}.${key}`})
|
||||||
|
const returnValues = GetParsedPaths(value, `${basekey}.${key}`)
|
||||||
|
for (var subkey in returnValues) {
|
||||||
|
parsedValues.push(returnValues[subkey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedValues.push({"name": basekeyname, "autocomplete": `${basekey}.${key}`})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedValues
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const Apps = (props) => {
|
const Apps = (props) => {
|
||||||
const { globalUrl, isLoggedIn, isLoaded } = props;
|
const { globalUrl, isLoggedIn, isLoaded } = props;
|
||||||
|
|
||||||
@@ -367,6 +413,61 @@ const Apps = (props) => {
|
|||||||
:
|
:
|
||||||
<img alt={selectedApp.title} src={selectedApp.large_image} style={{width: 100, height: 100, maxWidth: "100%"}} />
|
<img alt={selectedApp.title} src={selectedApp.large_image} style={{width: 100, height: 100, maxWidth: "100%"}} />
|
||||||
|
|
||||||
|
const GetAppExample = () => {
|
||||||
|
if (selectedAction.returns === undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var showResult = selectedAction.returns.example
|
||||||
|
if (showResult === undefined || showResult === null || showResult.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonvalid = true
|
||||||
|
try {
|
||||||
|
const tmp = String(JSON.parse(showResult))
|
||||||
|
if (!tmp.includes("{") && !tmp.includes("[")) {
|
||||||
|
jsonvalid = false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
jsonvalid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: In here -> parse the values into a list or something
|
||||||
|
if (jsonvalid) {
|
||||||
|
const paths = GetParsedPaths(JSON.parse(showResult), "")
|
||||||
|
console.log("PATHS: ", paths)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{paths.map(data => {
|
||||||
|
const circleSize = 10
|
||||||
|
return (
|
||||||
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data} onClick={() => console.log(data.autocomplete)}>
|
||||||
|
{data.name}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<ReactJson
|
||||||
|
src={JSON.parse(showResult)}
|
||||||
|
theme="solarized"
|
||||||
|
collapsed={false}
|
||||||
|
displayDataTypes={true}
|
||||||
|
name={"Example return value"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<b>Example return</b><div/>
|
||||||
|
{selectedAction.returns.example}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), {
|
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), {
|
||||||
var baseInfo = newAppname.length > 0 ?
|
var baseInfo = newAppname.length > 0 ?
|
||||||
<div>
|
<div>
|
||||||
@@ -454,6 +555,7 @@ const Apps = (props) => {
|
|||||||
{selectedAction.description}
|
{selectedAction.description}
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
|
<GetAppExample />
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -185,15 +185,15 @@ const Workflows = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson.success === true) {
|
if (responseJson.success === false) {
|
||||||
setWorkflowExecutions(responseJson)
|
alert.error("Failed getting executions")
|
||||||
|
} else {
|
||||||
if (responseJson.length > 0) {
|
if (responseJson.length > 0) {
|
||||||
setSelectedExecution(responseJson[0])
|
setSelectedExecution(responseJson[0])
|
||||||
|
setWorkflowExecutions(responseJson)
|
||||||
|
} else {
|
||||||
|
alert.info("Couldn't find executions for the workflow")
|
||||||
}
|
}
|
||||||
} else if (!responseJson.success && responseJson.reason !== undefined) {
|
|
||||||
alert.error("Failed loading executions: "+responseJson.reason)
|
|
||||||
} else {
|
|
||||||
alert.error("Failed loading executions for workflow")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
Reference in New Issue
Block a user