#168: Got successful Oauth2 implementation with Gmail OpenAPIls

This commit is contained in:
frikky
2021-10-14 02:06:24 +02:00
parent b15ac9f4d5
commit 66ed72b241
11 changed files with 406 additions and 178 deletions
+8 -6
View File
@@ -1378,7 +1378,7 @@ class AppBase:
except IndexError: except IndexError:
print("[DEBUG] INDEXERROR: ", parsersplit[outercnt]) print("[DEBUG] INDEXERROR: ", parsersplit[outercnt])
#ret = innervalue #ret = innervalue
ret, tmp_loop = recurse_json(innervalue, parsersplit[outercnt:]) ret, tmp_loop = recurse_json(basejson[i], parsersplit[outercnt:])
newvalue.append(ret) newvalue.append(ret)
@@ -1738,7 +1738,7 @@ class AppBase:
#self.logger.debug(f"\n\nHandle static data with JSON: {data}\n\n") #self.logger.debug(f"\n\nHandle static data with JSON: {data}\n\n")
#self.logger.info("STATIC PARSED: %s" % actualitem) #self.logger.info("STATIC PARSED: %s" % actualitem)
if len(actualitem) > 0: if len(actualitem) > 0:
self.logger.info("ACTUAL: ", actualitem) #self.logger.info("[DEACTUAL: ", actualitem)
for replace in actualitem: for replace in actualitem:
try: try:
to_be_replaced = replace[0] to_be_replaced = replace[0]
@@ -1774,7 +1774,7 @@ class AppBase:
#self.logger.info("VALUE: %s" % parameter["value"]) #self.logger.info("VALUE: %s" % parameter["value"])
if parameter["variant"] == "WORKFLOW_VARIABLE": if parameter["variant"] == "WORKFLOW_VARIABLE":
self.logger.info("Handling workflow variable") self.logger.info("[DEBUG] Handling workflow variable")
found = False found = False
try: try:
for item in fullexecution["workflow"]["workflow_variables"]: for item in fullexecution["workflow"]["workflow_variables"]:
@@ -2533,7 +2533,8 @@ class AppBase:
break break
except TypeError as e: except TypeError as e:
newres = "" newres = ""
errorstring = "%s" % e self.logger.info(f"[DEBUG] Got exec error: {errorstring}")
errorstring = f"{e}"
if "got an unexpected keyword argument" in errorstring: if "got an unexpected keyword argument" in errorstring:
fieldsplit = errorstring.split("'") fieldsplit = errorstring.split("'")
if len(fieldsplit) > 1: if len(fieldsplit) > 1:
@@ -2541,7 +2542,7 @@ class AppBase:
try: try:
del params[field] del params[field]
self.logger.info("Removed field invalid field %s" % field) self.logger.info("[WARNING] Removed field invalid field %s" % field)
except KeyError: except KeyError:
break break
else: else:
@@ -2774,7 +2775,8 @@ class AppBase:
try: try:
self.action_result["result"] = json.dumps({ self.action_result["result"] = json.dumps({
"success": False, "success": False,
"reason": f"Request error - failing silently. Details: {e}" "reason": f"Request error - failing silently. Details in detail section",
"details": f"{e}",
}) })
except json.decoder.JSONDecodeError as e: except json.decoder.JSONDecodeError as e:
self.action_result["result"] = f"Request error: {e}" self.action_result["result"] = f"Request error: {e}"
+1 -1
View File
@@ -23,7 +23,7 @@ require (
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect
github.com/frikky/kin-openapi v0.39.0 github.com/frikky/kin-openapi v0.39.0
github.com/frikky/shuffle-shared v0.1.13 github.com/frikky/shuffle-shared v0.1.14
github.com/fsouza/go-dockerclient v1.7.2 github.com/fsouza/go-dockerclient v1.7.2
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-billy/v5 v5.0.0
+21 -6
View File
@@ -1985,8 +1985,10 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
location := strings.Split(request.URL.String(), "/") location := strings.Split(request.URL.String(), "/")
var hookId string var hookId string
var queries string
if location[1] == "api" { if location[1] == "api" {
if len(location) <= 4 { if len(location) <= 4 {
log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location))
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
@@ -1995,10 +1997,20 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
hookId = location[4] hookId = location[4]
} }
if strings.Contains(hookId, "?") {
splitter := strings.Split(hookId, "?")
hookId = splitter[0]
if len(splitter) > 1 {
queries = splitter[1]
}
}
// ID: webhook_<UID> // ID: webhook_<UID>
if len(hookId) != 44 { if len(hookId) != 44 {
log.Printf("[INFO] Couldn't handle hookId. Too short in webhook: %d", len(hookId))
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) resp.Write([]byte(`{"success": false, "reason": "Hook ID not valid"}`))
return return
} }
@@ -2022,19 +2034,19 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
if hook.Status == "stopped" { if hook.Status == "stopped" {
log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id) log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Click start to start it"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`)))
return return
} }
if len(hook.Workflows) == 0 { if len(hook.Workflows) == 0 {
log.Printf("Not running because hook isn't connected to any workflows") log.Printf("[DEBUG] Not running because hook isn't connected to any workflows")
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`)))
return return
} }
if hook.Environment == "cloud" { if hook.Environment == "cloud" {
log.Printf("This should trigger in the cloud. Duplicate action allowed onprem.") log.Printf("[DEBUG] This should trigger in the cloud. Duplicate action allowed onprem.")
} }
// Check auth // Check auth
@@ -2050,12 +2062,16 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
body, err := ioutil.ReadAll(request.Body) body, err := ioutil.ReadAll(request.Body)
if err != nil { if err != nil {
log.Printf("Body data error: %s", err) log.Printf("[DEBUG] Body data error: %s", err)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
if len(queries) > 0 && len(body) == 0 {
body = []byte(queries)
}
//log.Printf("BODY: %s", parsedBody) //log.Printf("BODY: %s", parsedBody)
// This is a specific fix for MSteams and may fix other things as well // This is a specific fix for MSteams and may fix other things as well
@@ -5862,7 +5878,6 @@ func initHandlers() {
r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
// FIXME: This should only be cloud. Done locally with ngrok to test
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
+2 -2
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}
+1 -1
View File
@@ -78,7 +78,7 @@ const App = (message, props) => {
.then(response => response.json()) .then(response => response.json())
.then(responseJson => { .then(responseJson => {
if (responseJson.success === true && responseJson.notifications !== null && responseJson.notifications !== undefined && responseJson.notifications.length > 0) { if (responseJson.success === true && responseJson.notifications !== null && responseJson.notifications !== undefined && responseJson.notifications.length > 0) {
console.log("RESP: ", responseJson) //console.log("RESP: ", responseJson)
setNotifications(responseJson.notifications) setNotifications(responseJson.notifications)
} }
}) })
+58 -15
View File
@@ -2,9 +2,22 @@ import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
import { useTheme } from '@material-ui/core/styles'; import { useTheme } from '@material-ui/core/styles';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade } from '@material-ui/core'; import { ListItemText, TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade } from '@material-ui/core';
import { LockOpen as LockOpenIcon } from '@material-ui/icons'; import { LockOpen as LockOpenIcon } from '@material-ui/icons';
const ITEM_HEIGHT = 55
const ITEM_PADDING_TOP = 8
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
minWidth: 500,
maxWidth: 500,
scrollX: "auto",
},
},
}
const AuthenticationOauth2 = (props) => { const AuthenticationOauth2 = (props) => {
const { saveWorkflow, selectedApp, workflow, selectedAction, authenticationType, getAppAuthentication, appAuthentication, setSelectedAction, setNewAppAuth, setAuthenticationModalOpen} = props; const { saveWorkflow, selectedApp, workflow, selectedAction, authenticationType, getAppAuthentication, appAuthentication, setSelectedAction, setNewAppAuth, setAuthenticationModalOpen} = props;
const theme = useTheme(); const theme = useTheme();
@@ -15,6 +28,8 @@ const AuthenticationOauth2 = (props) => {
const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "") const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "")
const [oauthUrl, setOauthUrl] = React.useState("") const [oauthUrl, setOauthUrl] = React.useState("")
const [buttonClicked, setButtonClicked] = React.useState(false) const [buttonClicked, setButtonClicked] = React.useState(false)
const [selectedScopes, setSelectedScopes] = React.useState([])
const allscopes = authenticationType.scope !== undefined ? authenticationType.scope: []
const [manuallyConfigure, setManuallyConfigure] = React.useState(defaultConfigSet ? false : true) const [manuallyConfigure, setManuallyConfigure] = React.useState(defaultConfigSet ? false : true)
const [authenticationOption, setAuthenticationOptions] = React.useState({ const [authenticationOption, setAuthenticationOptions] = React.useState({
@@ -32,22 +47,16 @@ const AuthenticationOauth2 = (props) => {
return null return null
} }
const handleOauth2Request = (client_id, client_secret, oauth_url) => { const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => {
setButtonClicked(true) setButtonClicked(true)
//if (authenticationType.type === "oauth2" && authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null) { console.log("SCOPES: ", scopes)
// These are test credentials
//const client_id = "dae24316-4bec-4832-b660-4cba6dc2477b"
//const client_secret = "._Qu3EvYY-OW_D57uy79qwEo.32qD6.l0z"
const authentication_url = authenticationType.token_uri
var resources = "" var resources = ""
console.log("SCOPES: ", resources) if (scopes !== undefined && scopes !== null & scopes.length > 0) {
if (authenticationType.scope !== undefined && authenticationType.scope !== null) { resources = scopes.join(",")
resources = authenticationType.scope.join(",")
} }
resources = ["AaaServer.profile.READ"] const authentication_url = authenticationType.token_uri
console.log("SCOPES2: ", resources) console.log("SCOPES2: ", resources)
const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication` const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`
@@ -68,7 +77,7 @@ const AuthenticationOauth2 = (props) => {
// How can we properly try-catch without breaks on error? // How can we properly try-catch without breaks on error?
try { try {
var newwin = window.open(url, "", "width=400,height=200") var newwin = window.open(url, "", "width=800,height=600")
//console.log(newwin) //console.log(newwin)
var open = true var open = true
@@ -176,6 +185,18 @@ const AuthenticationOauth2 = (props) => {
} }
const handleScopeChange = (event) => {
const {
target: { value },
} = event;
console.log("VALUE: ", value)
// On autofill we get a the stringified value.
setSelectedScopes(typeof value === 'string' ? value.split(',') : value)
}
if (authenticationOption.label === null || authenticationOption.label === undefined) { if (authenticationOption.label === null || authenticationOption.label === undefined) {
authenticationOption.label = selectedApp.name+" authentication" authenticationOption.label = selectedApp.name+" authentication"
} }
@@ -282,6 +303,29 @@ const AuthenticationOauth2 = (props) => {
</div> </div>
) )
})} })}
{allscopes.length === 0 ? null :
<Select
multiple
value={selectedScopes}
style={{backgroundColor: theme.palette.inputColor, color: "white", }}
onChange={(e) => {
handleScopeChange(e)
}}
fullWidth
input={<Input id="select-multiple-native" />}
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps}
>
{allscopes.map((data, index) => {
return (
<MenuItem key={index} value={data}>
<Checkbox checked={selectedScopes.indexOf(data) > -1} />
<ListItemText primary={data} />
</MenuItem>
)
})}
</Select>
}
<TextField <TextField
style={{marginTop: 20, backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}} style={{marginTop: 20, backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}}
InputProps={{ InputProps={{
@@ -328,8 +372,7 @@ const AuthenticationOauth2 = (props) => {
variant="contained" variant="contained"
fullWidth fullWidth
onClick={() => { onClick={() => {
//setAuthenticationModalOpen(false) handleOauth2Request(clientId, clientSecret, oauthUrl, selectedScopes)
handleOauth2Request(clientId, clientSecret, oauthUrl)
}} }}
color="primary" color="primary"
> >
+1 -1
View File
@@ -1616,7 +1616,7 @@ const ParsedAction = (props) => {
selectedAction.authentication_id = "" selectedAction.authentication_id = ""
for (var key in selectedAction.parameters) { for (var key in selectedAction.parameters) {
console.log(selectedAction.parameters[key]) //console.log(selectedAction.parameters[key])
if (selectedAction.parameters[key].configuration) { if (selectedAction.parameters[key].configuration) {
selectedAction.parameters[key].value = "" selectedAction.parameters[key].value = ""
} }
+16 -15
View File
@@ -31,8 +31,8 @@ import { GetParsedPaths } from "./Apps.jsx";
import ConfigureWorkflow from '../components/ConfigureWorkflow.jsx'; import ConfigureWorkflow from '../components/ConfigureWorkflow.jsx';
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
import ParsedAction from '../components/ParsedAction.jsx'; import ParsedAction from '../components/ParsedAction.jsx';
import Scroll from 'react-scroll' //import Scroll from 'react-scroll'
import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll' //import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll'
const surfaceColor = "#27292D" const surfaceColor = "#27292D"
const inputColor = "#383B40" const inputColor = "#383B40"
@@ -1008,6 +1008,7 @@ const AngularWorkflow = (props) => {
if (hasSaved === false) { if (hasSaved === false) {
//alert.error("You might have forgotten to save before executing.") //alert.error("You might have forgotten to save before executing.")
//const saveWorkflow = (curworkflow) => { //const saveWorkflow = (curworkflow) => {
//setExecutionRunning(true)
setExecutionRequestStarted(true) setExecutionRequestStarted(true)
saveWorkflow(workflow, executionArgument, startNode) saveWorkflow(workflow, executionArgument, startNode)
console.log("FIXME: Might have forgotten to save before executing.") console.log("FIXME: Might have forgotten to save before executing.")
@@ -3013,10 +3014,10 @@ const AngularWorkflow = (props) => {
} }
break; break;
case 83: case 83:
if (previouskey === 17) { //if (previouskey === 17) {
event.preventDefault() // event.preventDefault()
saveWorkflow() // saveWorkflow()
} //}
break; break;
case 70: case 70:
//if (previouskey === 17) { //if (previouskey === 17) {
@@ -3158,7 +3159,7 @@ const AngularWorkflow = (props) => {
setEstablished(true) setEstablished(true)
// Validate if the node is just a node lol // Validate if the node is just a node lol
console.log("CY: ", cy) //console.log("CY: ", cy)
//console.log("CY: ", cy.edgehandles()) //console.log("CY: ", cy.edgehandles())
//try { //try {
cy.edgehandles({ cy.edgehandles({
@@ -5796,7 +5797,7 @@ const AngularWorkflow = (props) => {
const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}` const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
console.log("URL: ", url) console.log("URL: ", url)
var newwin = window.open(url, "", "width=200,height=100") var newwin = window.open(url, "", "width=800,height=600")
// Check whether we got a callback somewhere // Check whether we got a callback somewhere
var id = setInterval(function () { var id = setInterval(function () {
@@ -5871,7 +5872,7 @@ const AngularWorkflow = (props) => {
console.log("URL: ", url) console.log("URL: ", url)
var newwin = window.open(url, "", "width=200,height=100") var newwin = window.open(url, "", "width=800,height=600")
// Check whether we got a callback somewhere // Check whether we got a callback somewhere
var id = setInterval(function () { var id = setInterval(function () {
@@ -9406,10 +9407,10 @@ const AngularWorkflow = (props) => {
//console.log("SCROLL IS NOT 0: ", scrollConfig.top) //console.log("SCROLL IS NOT 0: ", scrollConfig.top)
//rightSideActionView.scrollTop = scrollConfig.top //rightSideActionView.scrollTop = scrollConfig.top
setTimeout(() => { setTimeout(() => {
scroller.scrollTo('elements_wrapper', { //scroller.scrollTo('elements_wrapper', {
containerId: 'rightside_actions', // containerId: 'rightside_actions',
offset: scrollConfig.top, // offset: scrollConfig.top,
}) //})
if (scrollConfig.selected !== undefined && scrollConfig.selected !== null) { if (scrollConfig.selected !== undefined && scrollConfig.selected !== null) {
const selectedField = document.getElementById(scrollConfig.selected) const selectedField = document.getElementById(scrollConfig.selected)
@@ -9437,15 +9438,15 @@ const AngularWorkflow = (props) => {
return ( return (
<div> <div>
<ScrollElement name="elements_wrapper">
<Prompt <Prompt
when={!lastSaved} when={!lastSaved}
message={unloadText} message={unloadText}
/> />
{loadedCheck} {loadedCheck}
</ScrollElement>
</div> </div>
) )
// <ScrollElement name="elements_wrapper">
// </ScrollElement>
} }
export default AngularWorkflow export default AngularWorkflow
+176 -67
View File
@@ -222,7 +222,7 @@ const AppCreator = (props) => {
const [parameterName, setParameterName] = useState(""); const [parameterName, setParameterName] = useState("");
const [parameterLocation, setParameterLocation] = useState(apikeySelection.length > 0 ? apikeySelection[0] : ""); const [parameterLocation, setParameterLocation] = useState(apikeySelection.length > 0 ? apikeySelection[0] : "");
const [refreshUrl, setRefreshUrl] = useState(""); const [refreshUrl, setRefreshUrl] = useState("");
const [oauth2Scopes, setOauth2Scopes] = useState(["OAUTH2.SCOPE.HERE"]); const [oauth2Scopes, setOauth2Scopes] = useState([]);
const [projectCategories, setProjectCategories] = useState([]); const [projectCategories, setProjectCategories] = useState([]);
const [selectedCategory, setSelectedCategory] = useState(""); const [selectedCategory, setSelectedCategory] = useState("");
@@ -396,7 +396,7 @@ const AppCreator = (props) => {
const handleGetRef = (parameter, data) => { const handleGetRef = (parameter, data) => {
try { try {
if (parameter === null || parameter["$ref"] === undefined) { if (parameter === null || parameter["$ref"] === undefined) {
//console.log("$ref not found in getref: ") //console.log("$ref not found in getref for: ", parameter)
return parameter return parameter
} }
} catch (e) { } catch (e) {
@@ -425,12 +425,6 @@ const AppCreator = (props) => {
} }
return newitem return newitem
//console.log("Should get ", parameter["$ref"])
//const subkeys = parameter["$ref"].split("/")
// setBasedata(data)
// handleGetReference(parameter["$ref"])
} }
// Sets the data up as it should be at later points // Sets the data up as it should be at later points
@@ -476,7 +470,18 @@ const AppCreator = (props) => {
document.title = "Apps - "+data.info.title document.title = "Apps - "+data.info.title
if (data.info["x-logo"] !== undefined) { if (data.info["x-logo"] !== undefined) {
setFileBase64(data.info["x-logo"])
if (data.info["x-logo"].url !== undefined) {
console.log("PARSED LOGO: ", data.info["x-logo"].url)
setFileBase64(data.info["x-logo"].url)
} else {
setFileBase64(data.info["x-logo"])
}
console.log("")
console.log("")
console.log("LOGO: ", data.info["x-logo"])
console.log("")
console.log("")
} }
if (data.info.contact !== undefined) { if (data.info.contact !== undefined) {
@@ -542,8 +547,11 @@ const AppCreator = (props) => {
} }
if (!allowedfunctions.includes(method.toUpperCase())) { if (!allowedfunctions.includes(method.toUpperCase())) {
console.log("Invalid method: ", method, "data: ", methodvalue) // Typical YAML issue
alert.info("Skipped method (not allowed): "+method) if (method !== "parameters") {
console.log("Invalid method: ", method, "data: ", methodvalue)
alert.info("Skipped method (not allowed): "+method)
}
continue continue
} }
@@ -552,6 +560,8 @@ const AppCreator = (props) => {
tmpname = methodvalue.operationId tmpname = methodvalue.operationId
} }
tmpname = tmpname.replaceAll(".", " ")
var newaction = { var newaction = {
"name": tmpname, "name": tmpname,
"description": methodvalue.description, "description": methodvalue.description,
@@ -582,10 +592,8 @@ const AppCreator = (props) => {
} }
} }
//console.log("Category:
//console.log("Schema is application/json: ", methodvalue) // Typescript? I think not ;)
//console.log("DATA", data)
if (methodvalue["requestBody"] !== undefined) { if (methodvalue["requestBody"] !== undefined) {
//console.log("Handle requestbody: ", methodvalue["requestBody"]) //console.log("Handle requestbody: ", methodvalue["requestBody"])
if (methodvalue["requestBody"]["content"] !== undefined) { if (methodvalue["requestBody"]["content"] !== undefined) {
@@ -637,15 +645,13 @@ const AppCreator = (props) => {
} }
} }
} else { } else {
//console.log("REQUESTBODY: ", methodvalue["requestBody"]["content"])
if (methodvalue["requestBody"]["content"]["example"] !== undefined) { if (methodvalue["requestBody"]["content"]["example"] !== undefined) {
if (methodvalue["requestBody"]["content"]["example"]["example"] !== undefined) { if (methodvalue["requestBody"]["content"]["example"]["example"] !== undefined) {
newaction["body"] = methodvalue["requestBody"]["content"]["example"]["example"] newaction["body"] = methodvalue["requestBody"]["content"]["example"]["example"]
//JSON.stringify(tmpobject, null, 2) //JSON.stringify(tmpobject, null, 2)
} }
} } else if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) {
//console.log(methodvalue["requestBody"]["content"])
if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) {
if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== undefined && methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== null) { if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== undefined && methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== null) {
if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["type"] === "object") { if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["type"] === "object") {
const fieldname = methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["properties"]["fieldname"] const fieldname = methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["properties"]["fieldname"]
@@ -655,6 +661,63 @@ const AppCreator = (props) => {
} }
} }
} }
} else {
var schemas = []
const content = methodvalue["requestBody"]["content"]
if (content !== undefined && content !== null) {
//console.log("CONTENT: ", content)
for (const [subkey, subvalue] of Object.entries(content)) {
if (subvalue["schema"] !== undefined) {
//console.log("SCHEMA: ", subvalue["schema"])
if (subvalue["schema"]["$ref"] !== undefined) {
//console.log("SCHEMA FOUND REF!")
if (!schemas.includes(subvalue["schema"]["$ref"])) {
schemas.push(subvalue["schema"]["$ref"])
}
}
} else {
console.log("ERROR: couldn't find schema for ", subvalue, method)
}
}
}
if (schemas.length === 1) {
const parameter = handleGetRef({"$ref": schemas[0]}, data)
if (parameter.properties !== undefined && parameter["type"] === "object") {
var newbody = {}
for (var propkey in parameter.properties) {
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase()
if (parameter.properties[propkey].type === undefined) {
console.log("Skipping (4): ", parameter.properties[propkey])
continue
}
if (parameter.properties[propkey].type === "string") {
if (parameter.properties[propkey].description !== undefined) {
newbody[parsedkey] = parameter.properties[propkey].description
} else {
newbody[parsedkey] = ""
}
} else if (parameter.properties[propkey].type.includes("int") || parameter.properties[propkey].type.includes("uint64")) {
newbody[parsedkey] = 0
} else if (parameter.properties[propkey].type.includes("boolean")) {
newbody[parsedkey] = false
} else if (parameter.properties[propkey].type.includes("array")) {
newbody[parsedkey] = []
} else {
console.log("CANT HANDLE JSON TYPE (4)", parameter.properties[propkey].type, parameter.properties[propkey])
newbody[parsedkey] = []
}
}
newaction["body"] = JSON.stringify(newbody, null, 2)
} else {
console.log("CANT HANDLE PARAM: (4) ", parameter.properties)
}
}
} }
} }
} }
@@ -696,7 +759,7 @@ const AppCreator = (props) => {
for (var propkey in parameter.properties) { for (var propkey in parameter.properties) {
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() const parsedkey = propkey.replaceAll(" ", "_").toLowerCase()
if (parameter.properties[propkey].type === undefined) { if (parameter.properties[propkey].type === undefined) {
console.log("Skipping: ", parameter.properties[propkey]) console.log("Skipping (1): ", parameter.properties[propkey])
continue continue
} }
@@ -708,6 +771,13 @@ const AppCreator = (props) => {
} }
} else if (parameter.properties[propkey].type.includes("int")) { } else if (parameter.properties[propkey].type.includes("int")) {
newbody[parsedkey] = 0 newbody[parsedkey] = 0
} else if (parameter.properties[propkey].type.includes("boolean")) {
newbody[parsedkey] = false
} else if (parameter.properties[propkey].type.includes("array")) {
//console.log("Added empty array. Base is: ", parameter.properties[propkey].type)
//const parameter = handleGetRef(selectedExample["content"]["application/json"]["schema"], data)
newbody[parsedkey] = []
} else { } else {
console.log("CANT HANDLE JSON TYPE ", parameter.properties[propkey].type, parameter.properties[propkey]) console.log("CANT HANDLE JSON TYPE ", parameter.properties[propkey].type, parameter.properties[propkey])
newbody[parsedkey] = [] newbody[parsedkey] = []
@@ -732,7 +802,7 @@ const AppCreator = (props) => {
for (var propkey in parameter.properties) { for (var propkey in parameter.properties) {
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() const parsedkey = propkey.replaceAll(" ", "_").toLowerCase()
if (parameter.properties[propkey].type === undefined) { if (parameter.properties[propkey].type === undefined) {
console.log("Skipping: ", parameter.properties[propkey]) console.log("Skipping (2): ", parameter.properties[propkey])
continue continue
} }
@@ -744,6 +814,8 @@ const AppCreator = (props) => {
} }
} else if (parameter.properties[propkey].type.includes("int")) { } else if (parameter.properties[propkey].type.includes("int")) {
newbody[parsedkey] = 0 newbody[parsedkey] = 0
} else if (parameter.properties[propkey].type.includes("boolean")) {
newbody[parsedkey] = false
} else { } else {
console.log("CANT HANDLE JSON TYPE (2) ", parameter.properties[propkey].type) console.log("CANT HANDLE JSON TYPE (2) ", parameter.properties[propkey].type)
newbody[parsedkey] = [] newbody[parsedkey] = []
@@ -767,7 +839,7 @@ const AppCreator = (props) => {
for (var propkey in parameter.properties) { for (var propkey in parameter.properties) {
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() const parsedkey = propkey.replaceAll(" ", "_").toLowerCase()
if (parameter.properties[propkey].type === undefined) { if (parameter.properties[propkey].type === undefined) {
console.log("Skipping: ", parameter.properties[propkey]) console.log("Skipping (3): ", parameter.properties[propkey])
continue continue
} }
@@ -790,7 +862,7 @@ const AppCreator = (props) => {
//newaction.example_response = JSON.stringify(parameter.properties, null, 2) //newaction.example_response = JSON.stringify(parameter.properties, null, 2)
} else { } else {
//newaction.example_response = parameter.properties //newaction.example_response = parameter.properties
console.log("CANT HANDLE PARAM: (2) ", parameter.properties) console.log("CANT HANDLE PARAM: (3) ", parameter.properties)
} }
} }
} }
@@ -894,6 +966,7 @@ const AppCreator = (props) => {
newaction.errors.push("Missing name") newaction.errors.push("Missing name")
} }
} }
newActions.push(newaction) newActions.push(newaction)
} }
} }
@@ -924,33 +997,71 @@ const AppCreator = (props) => {
} }
console.log("SECURITYSCHEMES: ", securitySchemes) //console.log("SECURITYSCHEMES: ", securitySchemes)
if (securitySchemes !== undefined) { if (securitySchemes !== undefined) {
// FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh) // FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh)
//console.log("SECURITY: ", securitySchemes) //console.log("SECURITY: ", securitySchemes)
//if (Object.entries(securitySchemes) > 1 && //if (Object.entries(securitySchemes) > 1 &&
var newauth = [] var newauth = []
for (const [key, value] of Object.entries(securitySchemes)) { for (const [key, value] of Object.entries(securitySchemes)) {
console.log(key, value) //console.log(key, value)
if (value.scheme === "bearer") { if (value.scheme === "bearer") {
setAuthenticationOption("Bearer auth") setAuthenticationOption("Bearer auth")
setAuthenticationRequired(true) setAuthenticationRequired(true)
} else if (key === "Oauth2") { } else if (key === "Oauth2" || key === "Oauth2c") {
//alert.info("Can't handle Oauth2 auth yet.") //alert.info("Can't handle Oauth2 auth yet.")
setAuthenticationOption("Oauth2") setAuthenticationOption("Oauth2")
setAuthenticationRequired(true) setAuthenticationRequired(true)
if (value.flow.authorizationCode.authorizationUrl !== undefined) { //console.log("FLOW-1: ", value)
setParameterName(value.flow.authorizationCode.authorizationUrl) const flowkey = value.flow === undefined ? "flows" : "flow"
} //console.log("FLOW: ", value[flowkey])
if (value.flow.authorizationCode.tokenUrl !== undefined) { const basekey = value[flowkey].authorizationCode !== undefined ? "authorizationCode" : "implicit"
setParameterLocation(value.flow.authorizationCode.tokenUrl) //console.log("FLOW2: ", value[flowkey][basekey])
} if (value[flowkey] !== undefined && value[flowkey][basekey] !== undefined) {
if (value.flow.authorizationCode.refreshUrl !== undefined) { if (value[flowkey][basekey].authorizationUrl !== undefined && parameterName.length === 0) {
setRefreshUrl(value.flow.authorizationCode.refreshUrl) setParameterName(value[flowkey][basekey].authorizationUrl)
} }
if (value.flow.authorizationCode.scopes !== undefined && value.flow.authorizationCode.scopes !== null && value.flow.authorizationCode.scopes.length > 0) {
setOauth2Scopes(value.flow.authorizationCode.scopes) var tokenUrl = ""
if (value[flowkey][basekey].tokenUrl !== undefined) {
setParameterLocation(value[flowkey][basekey].tokenUrl)
tokenUrl = value[flowkey][basekey].tokenUrl
} else {
setParameterLocation("")
}
if (value[flowkey][basekey].refreshUrl !== undefined) {
setRefreshUrl(value[flowkey][basekey].refreshUrl)
} else if (tokenUrl.length > 0) {
setRefreshUrl(tokenUrl)
}
if (value[flowkey][basekey].scopes !== undefined && value[flowkey][basekey].scopes !== null) {
if (value[flowkey][basekey].scopes.length > 0) {
setOauth2Scopes(value[flowkey][basekey].scopes)
} else {
var newscopes = []
for (let [scopekey, scopevalue] of Object.entries(value[flowkey][basekey].scopes)) {
if (scopekey.startsWith("http")) {
const scopekeysplit = scopekey.split("/")
if (scopekeysplit.length < 5) {
console.log("Skipping scope: ", scopekey)
alert.info("Skipping scope: "+scopekey)
continue
}
//console.log("Checking scope for: ", scopekey, scopekeysplit.length)
}
newscopes.push(scopekey)
}
setOauth2Scopes(newscopes)
}
}
} else {
console.log("Bad flowkey and basekey for oauth2: ", flowkey, basekey)
} }
} else if (key === "ApiKeyAuth") { } else if (key === "ApiKeyAuth") {
@@ -973,11 +1084,12 @@ const AppCreator = (props) => {
setAuthenticationOption("Oauth2") setAuthenticationOption("Oauth2")
setAuthenticationRequired(true) setAuthenticationRequired(true)
} else { } else {
newauth.push({ alert.error("Couldn't handle AUTH type: ", key)
"name": key, //newauth.push({
"type": value.in, // "name": key,
"example": "", // "type": value.in,
}) // "example": "",
//})
} }
} }
@@ -1446,15 +1558,6 @@ const AppCreator = (props) => {
}, },
}, },
} }
console.log("SCOPES: ", oauth2Scopes)
//if (oauth2Scopes.scopes > 0) {
// for (var key in oauth2Scopes) {
// const scope = oauth2Scopes[key]
// data.components.securitySchemes["Oauth2"]["flow"]["authorizationCode"]["scopes"].push(scope)
// }
//}
} }
if (setExtraAuth.length > 0) { if (setExtraAuth.length > 0) {
@@ -1826,21 +1929,25 @@ const AppCreator = (props) => {
InputProps={{ InputProps={{
style:{ style:{
color: "white", color: "white",
maxHeight: 50,
}, },
}} }}
style={{maxHeight: 80, overflowX: "hidden", overflowY: "auto",}}
placeholder="Scopes" placeholder="Scopes"
color="primary" color="primary"
fullWidth fullWidth
defaultValue={oauth2Scopes} value={oauth2Scopes}
onAdd={(chip) => { onAdd={(chip) => {
oauth2Scopes.push(chip) oauth2Scopes.push(chip)
console.log(oauth2Scopes) console.log(oauth2Scopes)
setOauth2Scopes(oauth2Scopes) setOauth2Scopes(oauth2Scopes)
setUpdate(Math.random())
}} }}
onDelete={(chip, index) => { onDelete={(chip, index) => {
oauth2Scopes.splice(index, 1) oauth2Scopes.splice(index, 1)
console.log(oauth2Scopes) console.log(oauth2Scopes)
setOauth2Scopes(oauth2Scopes) setOauth2Scopes(oauth2Scopes)
setUpdate(Math.random())
}} }}
/> />
</div> </div>
@@ -3265,10 +3372,14 @@ const AppCreator = (props) => {
0, 0, canvas.width, canvas.height 0, 0, canvas.width, canvas.height
) )
const canvasUrl = canvas.toDataURL() try {
if (canvasUrl !== fileBase64) { const canvasUrl = canvas.toDataURL()
//console.log("SET URL TO: ", canvasUrl) if (canvasUrl !== fileBase64) {
setFileBase64(canvasUrl) //console.log("SET URL TO: ", canvasUrl)
setFileBase64(canvasUrl)
}
} catch (e) {
alert.error("Failed to parse canvasurl!")
} }
} }
@@ -3278,12 +3389,6 @@ const AppCreator = (props) => {
//canvas.height = img.height //canvas.height = img.height
} }
//const imageInfo = file.length === 0 ?
// <div style={{textAlign: "center", marginTop: 20}}>
// Upload logo
// </div> :
// <img src={file} id="logo" style={{width: "100%", height: "100%"}} />
const [imageUploadError, setImageUploadError] = useState(""); const [imageUploadError, setImageUploadError] = useState("");
const [openImageModal, setOpenImageModal] = useState(""); const [openImageModal, setOpenImageModal] = useState("");
const [scale, setScale] = useState(1); const [scale, setScale] = useState(1);
@@ -3293,7 +3398,7 @@ const AppCreator = (props) => {
let imageData = fileBase64; let imageData = fileBase64;
let croppedData = file.length > 0 ? file : fileBase64 let croppedData = file.length > 0 ? file : fileBase64
const imageInfo = <img src={imageData} id="logo" style={{maxWidth: 174, maxHeight: 174, minWidth: 174, minHeight: 174, objectFit: "contain",}} /> const imageInfo = <img crossorigin="anonymous" src={imageData} id="logo" style={{maxWidth: 174, maxHeight: 174, minWidth: 174, minHeight: 174, objectFit: "contain",}} />
const alternateImg = <AddPhotoAlternateIcon style={{ width: 100, height: 100, flex: "1", display: "flex", flexDirection: "row", margin: "auto", marginTop: 30, marginLeft: 40,}} onClick={() => { const alternateImg = <AddPhotoAlternateIcon style={{ width: 100, height: 100, flex: "1", display: "flex", flexDirection: "row", margin: "auto", marginTop: 30, marginLeft: 40,}} onClick={() => {
upload.click() upload.click()
@@ -3326,11 +3431,15 @@ const AppCreator = (props) => {
const onSaveAppIcon = () => { const onSaveAppIcon = () => {
if(editor){ if(editor){
setFile(""); try {
const canvas = editor.getImageScaledToCanvas(); setFile("");
setFileBase64(canvas.toDataURL()); const canvas = editor.getImageScaledToCanvas();
setOpenImageModal(false) setFileBase64(canvas.toDataURL())
setDisableImageUpload(true); setOpenImageModal(false)
setDisableImageUpload(true);
} catch (e) {
alert.error("Failed to set image. Replace it if this persists.")
}
} }
} }
+3 -3
View File
@@ -845,7 +845,7 @@ const Apps = (props) => {
<h2>App Creator</h2> <h2>App Creator</h2>
<a href="https://shuffler.io/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a> <a href="https://shuffler.io/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a>
&nbsp;- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a> &nbsp;- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
&nbsp;- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a> &nbsp;- <a href="https://github.com/APIs-guru/openapi-directory/tree/main/APIs" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
&nbsp;- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a> &nbsp;- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a>
<div/> <div/>
<Typography variant="body2" color="textSecondary"> <Typography variant="body2" color="textSecondary">
@@ -1073,10 +1073,10 @@ const Apps = (props) => {
<CircularProgress style={{width: 40, height: 40, margin: "auto"}}/> <CircularProgress style={{width: 40, height: 40, margin: "auto"}}/>
: :
<Paper square style={uploadViewPaperStyle}> <Paper square style={uploadViewPaperStyle}>
<Typography variant="h6" style={{margin: 10}}> <Typography variant="body1" style={{margin: 10}}>
No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images. No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images.
</Typography> </Typography>
<Typography variant="h6" style={{margin: 10}}> <Typography variant="body1" style={{margin: 10}}>
If you're still not able to see any apps, please follow our <a href={"https://shuffler.io/docs/troubleshooting#load_all_apps_locally"} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">troubleshooting guide for loading apps!</a> If you're still not able to see any apps, please follow our <a href={"https://shuffler.io/docs/troubleshooting#load_all_apps_locally"} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">troubleshooting guide for loading apps!</a>
</Typography> </Typography>
</Paper> </Paper>
+119 -61
View File
@@ -5,6 +5,7 @@ import { useTheme } from '@material-ui/core/styles';
import {Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core'; import {Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core';
import {GridOn as GridOnIcon, List as ListIcon, Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons'; import {GridOn as GridOnIcon, List as ListIcon, Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons';
import NestedMenuItem from "material-ui-nested-menu-item";
//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; //import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
//https://next.material-ui.com/components/material-icons/ //https://next.material-ui.com/components/material-icons/
@@ -27,6 +28,7 @@ import CytoscapeWrapper from '../components/RenderCytoscape'
const inputColor = "#383B40" const inputColor = "#383B40"
const surfaceColor = "#27292D" const surfaceColor = "#27292D"
const svgSize = 24 const svgSize = 24
const imagesize = 22
const flexContainerStyle = { const flexContainerStyle = {
display: "flex", display: "flex",
@@ -1102,7 +1104,6 @@ const Workflows = (props) => {
) )
} }
const WorkflowPaper = (props) => { const WorkflowPaper = (props) => {
const { data } = props; const { data } = props;
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
@@ -1135,7 +1136,69 @@ const Workflows = (props) => {
const actions = data.actions !== null ? data.actions.length : 0 const actions = data.actions !== null ? data.actions.length : 0
const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data)
const imagesize = 22
const workflowMenuButtons = <Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={() => {
setOpen(false)
setAnchorEl(null)
}}
>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
//console.log("DATA:" ,data)
setModalOpen(true)
setEditingWorkflow(data)
setNewWorkflowName(data.name)
setNewWorkflowDescription(data.description)
setDefaultReturnValue(data.default_return_value)
if (data.tags !== undefined && data.tags !== null) {
setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags)))
}
}} key={"change"}>
<EditIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Change details"}
</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setSelectedWorkflow(data)
setPublishModalOpen(true)
}} key={"publish"}>
<CloudUploadIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Publish Workflow"}
</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
copyWorkflow(data)
setOpen(false)
}} key={"duplicate"}>
<FileCopyIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Duplicate Workflow"}
</MenuItem>
<NestedMenuItem disabled={userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length === 1 || userdata.orgs.length >= 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
//copyWorkflow(data)
//setOpen(false)
}} key={"duplicate"}>
<FileCopyIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Copy to Child Org"}
</NestedMenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setExportModalOpen(true)
setExportData(data)
setOpen(false)
}} key={"export"}>
<GetAppIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Export Workflow"}
</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setDeleteModalOpen(true)
setSelectedWorkflowId(data.id)
setOpen(false)
}} key={"delete"}>
<DeleteIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Delete Workflow"}
</MenuItem>
</Menu>
var image = "" var image = ""
var orgName = "" var orgName = ""
@@ -1283,62 +1346,7 @@ const Workflows = (props) => {
> >
<MoreVertIcon /> <MoreVertIcon />
</IconButton> </IconButton>
<Menu {workflowMenuButtons}
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={() => {
setOpen(false)
setAnchorEl(null)
}}
>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
//console.log("DATA:" ,data)
setModalOpen(true)
setEditingWorkflow(data)
setNewWorkflowName(data.name)
setNewWorkflowDescription(data.description)
setDefaultReturnValue(data.default_return_value)
if (data.tags !== undefined && data.tags !== null) {
setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags)))
}
}} key={"change"}>
<EditIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Change details"}
</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setSelectedWorkflow(data)
setPublishModalOpen(true)
}} key={"publish"}>
<CloudUploadIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Publish Workflow"}
</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
copyWorkflow(data)
setOpen(false)
}} key={"duplicate"}>
<FileCopyIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Duplicate Workflow"}
</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setExportModalOpen(true)
setExportData(data)
setOpen(false)
}} key={"export"}>
<GetAppIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Export Workflow"}
</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setDeleteModalOpen(true)
setSelectedWorkflowId(data.id)
setOpen(false)
}} key={"delete"}>
<DeleteIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Delete Workflow"}
</MenuItem>
</Menu>
</Grid> </Grid>
{/* {/*
<Grid> <Grid>
@@ -1547,6 +1555,50 @@ const Workflows = (props) => {
let workflowData = ""; let workflowData = "";
if (workflows.length > 0) { if (workflows.length > 0) {
const columns = [ const columns = [
{ field: 'image', headerName: 'Logo', width: 42, renderCell: (params) => {
const data = params.row.record
var boxColor = "#FECC00"
if (data.is_valid) {
boxColor = "#86c142"
}
if (!data.previously_saved) {
boxColor = "#f85a3e"
}
var image = ""
var orgName = ""
var orgId = ""
if (userdata.orgs !== undefined) {
const foundOrg = userdata.orgs.find(org => org.id === data["org_id"])
if (foundOrg !== undefined && foundOrg !== null) {
//position: "absolute", bottom: 5, right: -5,
const imageStyle = {width: imagesize+7, height: imagesize+7, pointerEvents: "none", marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, borderRadius: 10, border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null, cursor: "pointer", marginTop: 5, }
//<Tooltip title={`Org: ${foundOrg.name}`} placement="bottom">
image = foundOrg.image === "" ?
<img alt={foundOrg.name} src={theme.palette.defaultImage} style={imageStyle} />
:
<img alt={foundOrg.name} src={foundOrg.image} style={imageStyle} onClick={() => {
//setFilteredWorkflows(newWorkflows)
}}/>
orgName = foundOrg.name
orgId = foundOrg.id
}
}
return (
<div styl={{cursor: "pointer"}} onClick={() => {
//addFilter(orgId)
//setFilters(["Org "+orgName])
//setFilteredWorkflows(newWorkflows)
}}>
{image}
</div>
)
}},
{ field: 'title', headerName: 'Title', width: 330, renderCell: (params) => { { field: 'title', headerName: 'Title', width: 330, renderCell: (params) => {
const data = params.row.record const data = params.row.record
@@ -1569,7 +1621,7 @@ const Workflows = (props) => {
</Typography> </Typography>
) )
} */}, } */},
{ field: 'actions', headerName: 'Options', width: 200, sortable: false, { field: 'options', headerName: 'Options', width: 200, sortable: false,
disableClickEventBubbling: true, disableClickEventBubbling: true,
renderCell: (params) => { renderCell: (params) => {
const data = params.row.record; const data = params.row.record;
@@ -1635,7 +1687,7 @@ const Workflows = (props) => {
) )
} }
}, },
{/* field: 'tags', headerName: 'Tags', maxHeight: 15, width: 390, sortable: false, { field: 'tags', headerName: 'Tags', maxHeight: 15, width: 300, sortable: false,
disableClickEventBubbling: true, disableClickEventBubbling: true,
renderCell: (params) => { renderCell: (params) => {
const data = params.row.record; const data = params.row.record;
@@ -1661,7 +1713,12 @@ const Workflows = (props) => {
</Grid> </Grid>
) )
} }
*/} },
{ field: '', headerName: '', maxHeight: 15, width: 100, sortable: false,
disableClickEventBubbling: true,
renderCell: (params) => {
}
}
]; ];
let rows = []; let rows = [];
rows = workflows.map((data, index) => { rows = workflows.map((data, index) => {
@@ -1732,6 +1789,7 @@ const Workflows = (props) => {
placeholder="Name" placeholder="Name"
margin="dense" margin="dense"
defaultValue={newWorkflowName} defaultValue={newWorkflowName}
autoFocus
fullWidth fullWidth
/> />
<TextField <TextField