diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 36a795dc..b725ae9f 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1378,7 +1378,7 @@ class AppBase: except IndexError: print("[DEBUG] INDEXERROR: ", parsersplit[outercnt]) #ret = innervalue - ret, tmp_loop = recurse_json(innervalue, parsersplit[outercnt:]) + ret, tmp_loop = recurse_json(basejson[i], parsersplit[outercnt:]) newvalue.append(ret) @@ -1738,7 +1738,7 @@ class AppBase: #self.logger.debug(f"\n\nHandle static data with JSON: {data}\n\n") #self.logger.info("STATIC PARSED: %s" % actualitem) if len(actualitem) > 0: - self.logger.info("ACTUAL: ", actualitem) + #self.logger.info("[DEACTUAL: ", actualitem) for replace in actualitem: try: to_be_replaced = replace[0] @@ -1774,7 +1774,7 @@ class AppBase: #self.logger.info("VALUE: %s" % parameter["value"]) if parameter["variant"] == "WORKFLOW_VARIABLE": - self.logger.info("Handling workflow variable") + self.logger.info("[DEBUG] Handling workflow variable") found = False try: for item in fullexecution["workflow"]["workflow_variables"]: @@ -2533,7 +2533,8 @@ class AppBase: break except TypeError as e: newres = "" - errorstring = "%s" % e + self.logger.info(f"[DEBUG] Got exec error: {errorstring}") + errorstring = f"{e}" if "got an unexpected keyword argument" in errorstring: fieldsplit = errorstring.split("'") if len(fieldsplit) > 1: @@ -2541,7 +2542,7 @@ class AppBase: try: del params[field] - self.logger.info("Removed field invalid field %s" % field) + self.logger.info("[WARNING] Removed field invalid field %s" % field) except KeyError: break else: @@ -2774,7 +2775,8 @@ class AppBase: try: self.action_result["result"] = json.dumps({ "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: self.action_result["result"] = f"Request error: {e}" diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 836ab746..120e93c5 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -23,7 +23,7 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect 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/ghodss/yaml v1.0.0 github.com/go-git/go-billy/v5 v5.0.0 diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 7dab4a4d..ae80d6d5 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1985,8 +1985,10 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { location := strings.Split(request.URL.String(), "/") var hookId string + var queries string if location[1] == "api" { if len(location) <= 4 { + log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location)) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -1995,10 +1997,20 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { hookId = location[4] } + if strings.Contains(hookId, "?") { + splitter := strings.Split(hookId, "?") + hookId = splitter[0] + + if len(splitter) > 1 { + queries = splitter[1] + } + } + // ID: webhook_ if len(hookId) != 44 { + log.Printf("[INFO] Couldn't handle hookId. Too short in webhook: %d", len(hookId)) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + resp.Write([]byte(`{"success": false, "reason": "Hook ID not valid"}`)) return } @@ -2022,19 +2034,19 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { if hook.Status == "stopped" { log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id) 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 } 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.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) return } 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 @@ -2050,12 +2062,16 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { body, err := ioutil.ReadAll(request.Body) if err != nil { - log.Printf("Body data error: %s", err) + log.Printf("[DEBUG] Body data error: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } + if len(queries) > 0 && len(body) == 0 { + body = []byte(queries) + } + //log.Printf("BODY: %s", parsedBody) // 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/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/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS") diff --git a/docker-compose.yml b/docker-compose.yml index 069fedad..09f810e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: frontend: - #build: ./frontend + build: ./frontend image: ghcr.io/frikky/shuffle-frontend:nightly container_name: shuffle-frontend hostname: shuffle-frontend @@ -16,7 +16,7 @@ services: depends_on: - backend backend: - #build: ./backend + build: ./backend image: ghcr.io/frikky/shuffle-backend:nightly container_name: shuffle-backend hostname: ${BACKEND_HOSTNAME} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 72f4464a..c4ef9a57 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -78,7 +78,7 @@ const App = (message, props) => { .then(response => response.json()) .then(responseJson => { 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) } }) diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 34ab2bfc..4fd89ac7 100644 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -2,9 +2,22 @@ import React, {useRef, useState, useEffect, useLayoutEffect} from 'react'; import { useTheme } from '@material-ui/core/styles'; 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'; +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 { saveWorkflow, selectedApp, workflow, selectedAction, authenticationType, getAppAuthentication, appAuthentication, setSelectedAction, setNewAppAuth, setAuthenticationModalOpen} = props; const theme = useTheme(); @@ -15,6 +28,8 @@ const AuthenticationOauth2 = (props) => { const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "") const [oauthUrl, setOauthUrl] = React.useState("") 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 [authenticationOption, setAuthenticationOptions] = React.useState({ @@ -32,22 +47,16 @@ const AuthenticationOauth2 = (props) => { return null } - const handleOauth2Request = (client_id, client_secret, oauth_url) => { + const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => { setButtonClicked(true) - //if (authenticationType.type === "oauth2" && authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null) { - // 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 + console.log("SCOPES: ", scopes) var resources = "" - console.log("SCOPES: ", resources) - if (authenticationType.scope !== undefined && authenticationType.scope !== null) { - resources = authenticationType.scope.join(",") + if (scopes !== undefined && scopes !== null & scopes.length > 0) { + resources = scopes.join(",") } - resources = ["AaaServer.profile.READ"] + const authentication_url = authenticationType.token_uri console.log("SCOPES2: ", resources) 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? try { - var newwin = window.open(url, "", "width=400,height=200") + var newwin = window.open(url, "", "width=800,height=600") //console.log(newwin) 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) { authenticationOption.label = selectedApp.name+" authentication" } @@ -282,6 +303,29 @@ const AuthenticationOauth2 = (props) => { ) })} + {allscopes.length === 0 ? null : + } + renderValue={(selected) => selected.join(', ')} + MenuProps={MenuProps} + > + {allscopes.map((data, index) => { + return ( + + -1} /> + + + ) + })} + + } { variant="contained" fullWidth onClick={() => { - //setAuthenticationModalOpen(false) - handleOauth2Request(clientId, clientSecret, oauthUrl) + handleOauth2Request(clientId, clientSecret, oauthUrl, selectedScopes) }} color="primary" > diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index e57feade..937f592e 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1616,7 +1616,7 @@ const ParsedAction = (props) => { selectedAction.authentication_id = "" for (var key in selectedAction.parameters) { - console.log(selectedAction.parameters[key]) + //console.log(selectedAction.parameters[key]) if (selectedAction.parameters[key].configuration) { selectedAction.parameters[key].value = "" } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index b6029cd5..274c21bf 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -31,8 +31,8 @@ import { GetParsedPaths } from "./Apps.jsx"; import ConfigureWorkflow from '../components/ConfigureWorkflow.jsx'; import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import ParsedAction from '../components/ParsedAction.jsx'; -import Scroll from 'react-scroll' -import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll' +//import Scroll from 'react-scroll' +//import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll' const surfaceColor = "#27292D" const inputColor = "#383B40" @@ -1008,6 +1008,7 @@ const AngularWorkflow = (props) => { if (hasSaved === false) { //alert.error("You might have forgotten to save before executing.") //const saveWorkflow = (curworkflow) => { + //setExecutionRunning(true) setExecutionRequestStarted(true) saveWorkflow(workflow, executionArgument, startNode) console.log("FIXME: Might have forgotten to save before executing.") @@ -3013,10 +3014,10 @@ const AngularWorkflow = (props) => { } break; case 83: - if (previouskey === 17) { - event.preventDefault() - saveWorkflow() - } + //if (previouskey === 17) { + // event.preventDefault() + // saveWorkflow() + //} break; case 70: //if (previouskey === 17) { @@ -3158,7 +3159,7 @@ const AngularWorkflow = (props) => { setEstablished(true) // Validate if the node is just a node lol - console.log("CY: ", cy) + //console.log("CY: ", cy) //console.log("CY: ", cy.edgehandles()) //try { 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}` 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 var id = setInterval(function () { @@ -5871,7 +5872,7 @@ const AngularWorkflow = (props) => { 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 var id = setInterval(function () { @@ -9406,10 +9407,10 @@ const AngularWorkflow = (props) => { //console.log("SCROLL IS NOT 0: ", scrollConfig.top) //rightSideActionView.scrollTop = scrollConfig.top setTimeout(() => { - scroller.scrollTo('elements_wrapper', { - containerId: 'rightside_actions', - offset: scrollConfig.top, - }) + //scroller.scrollTo('elements_wrapper', { + // containerId: 'rightside_actions', + // offset: scrollConfig.top, + //}) if (scrollConfig.selected !== undefined && scrollConfig.selected !== null) { const selectedField = document.getElementById(scrollConfig.selected) @@ -9437,15 +9438,15 @@ const AngularWorkflow = (props) => { return (
- {loadedCheck} -
) + // + // } export default AngularWorkflow diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index b0a906fd..bf0431fb 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -222,7 +222,7 @@ const AppCreator = (props) => { const [parameterName, setParameterName] = useState(""); const [parameterLocation, setParameterLocation] = useState(apikeySelection.length > 0 ? apikeySelection[0] : ""); const [refreshUrl, setRefreshUrl] = useState(""); - const [oauth2Scopes, setOauth2Scopes] = useState(["OAUTH2.SCOPE.HERE"]); + const [oauth2Scopes, setOauth2Scopes] = useState([]); const [projectCategories, setProjectCategories] = useState([]); const [selectedCategory, setSelectedCategory] = useState(""); @@ -396,7 +396,7 @@ const AppCreator = (props) => { const handleGetRef = (parameter, data) => { try { if (parameter === null || parameter["$ref"] === undefined) { - //console.log("$ref not found in getref: ") + //console.log("$ref not found in getref for: ", parameter) return parameter } } catch (e) { @@ -425,12 +425,6 @@ const AppCreator = (props) => { } 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 @@ -476,7 +470,18 @@ const AppCreator = (props) => { document.title = "Apps - "+data.info.title 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) { @@ -542,8 +547,11 @@ const AppCreator = (props) => { } if (!allowedfunctions.includes(method.toUpperCase())) { - console.log("Invalid method: ", method, "data: ", methodvalue) - alert.info("Skipped method (not allowed): "+method) + // Typical YAML issue + if (method !== "parameters") { + console.log("Invalid method: ", method, "data: ", methodvalue) + alert.info("Skipped method (not allowed): "+method) + } continue } @@ -552,6 +560,8 @@ const AppCreator = (props) => { tmpname = methodvalue.operationId } + tmpname = tmpname.replaceAll(".", " ") + var newaction = { "name": tmpname, "description": methodvalue.description, @@ -582,10 +592,8 @@ const AppCreator = (props) => { } } - //console.log("Category: - //console.log("Schema is application/json: ", methodvalue) - //console.log("DATA", data) + // Typescript? I think not ;) if (methodvalue["requestBody"] !== undefined) { //console.log("Handle requestbody: ", methodvalue["requestBody"]) if (methodvalue["requestBody"]["content"] !== undefined) { @@ -637,15 +645,13 @@ const AppCreator = (props) => { } } } else { + //console.log("REQUESTBODY: ", methodvalue["requestBody"]["content"]) if (methodvalue["requestBody"]["content"]["example"] !== undefined) { if (methodvalue["requestBody"]["content"]["example"]["example"] !== undefined) { newaction["body"] = methodvalue["requestBody"]["content"]["example"]["example"] //JSON.stringify(tmpobject, null, 2) } - } - - //console.log(methodvalue["requestBody"]["content"]) - if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) { + } else 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"]["type"] === "object") { 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) { const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() if (parameter.properties[propkey].type === undefined) { - console.log("Skipping: ", parameter.properties[propkey]) + console.log("Skipping (1): ", parameter.properties[propkey]) continue } @@ -708,6 +771,13 @@ const AppCreator = (props) => { } } else if (parameter.properties[propkey].type.includes("int")) { 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 { console.log("CANT HANDLE JSON TYPE ", parameter.properties[propkey].type, parameter.properties[propkey]) newbody[parsedkey] = [] @@ -732,7 +802,7 @@ const AppCreator = (props) => { for (var propkey in parameter.properties) { const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() if (parameter.properties[propkey].type === undefined) { - console.log("Skipping: ", parameter.properties[propkey]) + console.log("Skipping (2): ", parameter.properties[propkey]) continue } @@ -744,6 +814,8 @@ const AppCreator = (props) => { } } else if (parameter.properties[propkey].type.includes("int")) { newbody[parsedkey] = 0 + } else if (parameter.properties[propkey].type.includes("boolean")) { + newbody[parsedkey] = false } else { console.log("CANT HANDLE JSON TYPE (2) ", parameter.properties[propkey].type) newbody[parsedkey] = [] @@ -767,7 +839,7 @@ const AppCreator = (props) => { for (var propkey in parameter.properties) { const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() if (parameter.properties[propkey].type === undefined) { - console.log("Skipping: ", parameter.properties[propkey]) + console.log("Skipping (3): ", parameter.properties[propkey]) continue } @@ -790,7 +862,7 @@ const AppCreator = (props) => { //newaction.example_response = JSON.stringify(parameter.properties, null, 2) } else { //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") } } + newActions.push(newaction) } } @@ -924,33 +997,71 @@ const AppCreator = (props) => { } - console.log("SECURITYSCHEMES: ", securitySchemes) + //console.log("SECURITYSCHEMES: ", securitySchemes) if (securitySchemes !== undefined) { // FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh) //console.log("SECURITY: ", securitySchemes) //if (Object.entries(securitySchemes) > 1 && var newauth = [] for (const [key, value] of Object.entries(securitySchemes)) { - console.log(key, value) + //console.log(key, value) if (value.scheme === "bearer") { setAuthenticationOption("Bearer auth") setAuthenticationRequired(true) - } else if (key === "Oauth2") { + } else if (key === "Oauth2" || key === "Oauth2c") { //alert.info("Can't handle Oauth2 auth yet.") setAuthenticationOption("Oauth2") setAuthenticationRequired(true) - if (value.flow.authorizationCode.authorizationUrl !== undefined) { - setParameterName(value.flow.authorizationCode.authorizationUrl) - } - if (value.flow.authorizationCode.tokenUrl !== undefined) { - setParameterLocation(value.flow.authorizationCode.tokenUrl) - } - if (value.flow.authorizationCode.refreshUrl !== undefined) { - setRefreshUrl(value.flow.authorizationCode.refreshUrl) - } - if (value.flow.authorizationCode.scopes !== undefined && value.flow.authorizationCode.scopes !== null && value.flow.authorizationCode.scopes.length > 0) { - setOauth2Scopes(value.flow.authorizationCode.scopes) + //console.log("FLOW-1: ", value) + const flowkey = value.flow === undefined ? "flows" : "flow" + //console.log("FLOW: ", value[flowkey]) + const basekey = value[flowkey].authorizationCode !== undefined ? "authorizationCode" : "implicit" + //console.log("FLOW2: ", value[flowkey][basekey]) + if (value[flowkey] !== undefined && value[flowkey][basekey] !== undefined) { + if (value[flowkey][basekey].authorizationUrl !== undefined && parameterName.length === 0) { + setParameterName(value[flowkey][basekey].authorizationUrl) + } + + 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") { @@ -973,11 +1084,12 @@ const AppCreator = (props) => { setAuthenticationOption("Oauth2") setAuthenticationRequired(true) } else { - newauth.push({ - "name": key, - "type": value.in, - "example": "", - }) + alert.error("Couldn't handle AUTH type: ", key) + //newauth.push({ + // "name": key, + // "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) { @@ -1826,21 +1929,25 @@ const AppCreator = (props) => { InputProps={{ style:{ color: "white", + maxHeight: 50, }, }} + style={{maxHeight: 80, overflowX: "hidden", overflowY: "auto",}} placeholder="Scopes" color="primary" fullWidth - defaultValue={oauth2Scopes} + value={oauth2Scopes} onAdd={(chip) => { oauth2Scopes.push(chip) console.log(oauth2Scopes) setOauth2Scopes(oauth2Scopes) + setUpdate(Math.random()) }} onDelete={(chip, index) => { oauth2Scopes.splice(index, 1) console.log(oauth2Scopes) setOauth2Scopes(oauth2Scopes) + setUpdate(Math.random()) }} /> @@ -3265,10 +3372,14 @@ const AppCreator = (props) => { 0, 0, canvas.width, canvas.height ) - const canvasUrl = canvas.toDataURL() - if (canvasUrl !== fileBase64) { - //console.log("SET URL TO: ", canvasUrl) - setFileBase64(canvasUrl) + try { + const canvasUrl = canvas.toDataURL() + if (canvasUrl !== fileBase64) { + //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 } - //const imageInfo = file.length === 0 ? - //
- // Upload logo - //
: - // - const [imageUploadError, setImageUploadError] = useState(""); const [openImageModal, setOpenImageModal] = useState(""); const [scale, setScale] = useState(1); @@ -3293,7 +3398,7 @@ const AppCreator = (props) => { let imageData = fileBase64; let croppedData = file.length > 0 ? file : fileBase64 - const imageInfo = + const imageInfo = const alternateImg = { upload.click() @@ -3326,11 +3431,15 @@ const AppCreator = (props) => { const onSaveAppIcon = () => { if(editor){ - setFile(""); - const canvas = editor.getImageScaledToCanvas(); - setFileBase64(canvas.toDataURL()); - setOpenImageModal(false) - setDisableImageUpload(true); + try { + setFile(""); + const canvas = editor.getImageScaledToCanvas(); + setFileBase64(canvas.toDataURL()) + setOpenImageModal(false) + setDisableImageUpload(true); + } catch (e) { + alert.error("Failed to set image. Replace it if this persists.") + } } } diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 61f8ebdb..03142daa 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -845,7 +845,7 @@ const Apps = (props) => {

App Creator

How it works  - Security API's -  - OpenAPI directory +  - OpenAPI directory  - OpenAPI Validator
@@ -1073,10 +1073,10 @@ const Apps = (props) => { : - + 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. - + If you're still not able to see any apps, please follow our troubleshooting guide for loading apps! diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 8c5ef7a0..5805dd78 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -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 {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'; //https://next.material-ui.com/components/material-icons/ @@ -27,6 +28,7 @@ import CytoscapeWrapper from '../components/RenderCytoscape' const inputColor = "#383B40" const surfaceColor = "#27292D" const svgSize = 24 +const imagesize = 22 const flexContainerStyle = { display: "flex", @@ -1102,7 +1104,6 @@ const Workflows = (props) => { ) } - const WorkflowPaper = (props) => { const { data } = props; const [open, setOpen] = React.useState(false); @@ -1135,7 +1136,69 @@ const Workflows = (props) => { const actions = data.actions !== null ? data.actions.length : 0 const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) - const imagesize = 22 + + const workflowMenuButtons = { + setOpen(false) + setAnchorEl(null) + }} + > + { + //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"}> + + {"Change details"} + + { + setSelectedWorkflow(data) + setPublishModalOpen(true) + }} key={"publish"}> + + {"Publish Workflow"} + + { + copyWorkflow(data) + setOpen(false) + }} key={"duplicate"}> + + {"Duplicate Workflow"} + + = 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => { + //copyWorkflow(data) + //setOpen(false) + }} key={"duplicate"}> + + {"Copy to Child Org"} + + { + setExportModalOpen(true) + setExportData(data) + setOpen(false) + }} key={"export"}> + + {"Export Workflow"} + + { + setDeleteModalOpen(true) + setSelectedWorkflowId(data.id) + setOpen(false) + }} key={"delete"}> + + {"Delete Workflow"} + + var image = "" var orgName = "" @@ -1283,62 +1346,7 @@ const Workflows = (props) => { > - { - setOpen(false) - setAnchorEl(null) - }} - > - { - //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"}> - - {"Change details"} - - { - setSelectedWorkflow(data) - setPublishModalOpen(true) - }} key={"publish"}> - - {"Publish Workflow"} - - { - copyWorkflow(data) - setOpen(false) - }} key={"duplicate"}> - - {"Duplicate Workflow"} - - { - setExportModalOpen(true) - setExportData(data) - setOpen(false) - }} key={"export"}> - - {"Export Workflow"} - - { - setDeleteModalOpen(true) - setSelectedWorkflowId(data.id) - setOpen(false) - }} key={"delete"}> - - {"Delete Workflow"} - - - + {workflowMenuButtons} {/* @@ -1547,6 +1555,50 @@ const Workflows = (props) => { let workflowData = ""; if (workflows.length > 0) { 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, } + + // + image = foundOrg.image === "" ? + {foundOrg.name} + : + {foundOrg.name} { + //setFilteredWorkflows(newWorkflows) + }}/> + + orgName = foundOrg.name + orgId = foundOrg.id + } + } + + return ( +
{ + //addFilter(orgId) + //setFilters(["Org "+orgName]) + //setFilteredWorkflows(newWorkflows) + }}> + {image} +
+ ) + }}, { field: 'title', headerName: 'Title', width: 330, renderCell: (params) => { const data = params.row.record @@ -1569,7 +1621,7 @@ const Workflows = (props) => {
) } */}, - { field: 'actions', headerName: 'Options', width: 200, sortable: false, + { field: 'options', headerName: 'Options', width: 200, sortable: false, disableClickEventBubbling: true, renderCell: (params) => { 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, renderCell: (params) => { const data = params.row.record; @@ -1661,7 +1713,12 @@ const Workflows = (props) => { ) } - */} + }, + { field: '', headerName: '', maxHeight: 15, width: 100, sortable: false, + disableClickEventBubbling: true, + renderCell: (params) => { + } + } ]; let rows = []; rows = workflows.map((data, index) => { @@ -1732,6 +1789,7 @@ const Workflows = (props) => { placeholder="Name" margin="dense" defaultValue={newWorkflowName} + autoFocus fullWidth />