From 206ab2decb25b15bec30d2009b0dafb68ae07ee9 Mon Sep 17 00:00:00 2001 From: frikky Date: Wed, 16 Sep 2020 20:52:11 +0200 Subject: [PATCH 01/31] #149: Properly fixed app sdk loop issues --- backend/app_sdk/app_base.py | 168 ++++++++++++++++++++---------- backend/go-app/main.go | 5 - backend/go-app/walkoff.go | 16 ++- docker-compose.yml | 2 +- frontend/src/App.jsx | 2 +- frontend/src/components/Header.js | 2 +- 6 files changed, 121 insertions(+), 74 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index e2a7f4f5..71e50a56 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -361,11 +361,99 @@ class AppBase: newlist.append("parsing_error") return " ".join(newlist) + def recurse_json(basejson, parsersplit): + match = "#(\d+):?-?([0-9a-z]+)?#?" + print("Split: %s\n%s" % (parsersplit, basejson)) + try: + outercnt = 0 + for value in parsersplit: + print("VALUE: %s\n" % value) + actualitem = re.findall(match, value, re.MULTILINE) + if value == "#": + newvalue = [] + for innervalue in basejson: + # 1. Check the next item (message) + # 2. Call this function again + + try: + ret = recurse_json(innervalue, parsersplit[outercnt+1:]) + except IndexError: + print("INDEXERROR: ", parsersplit[outercnt]) + #ret = innervalue + ret = recurse_json(innervalue, parsersplit[outercnt:]) + + print(ret) + #exit() + newvalue.append(ret) + + return newvalue + elif len(actualitem) > 0: + # FIXME: This is absolutely not perfect. + print("IN HERE: ", actualitem) + + newvalue = [] + firstitem = actualitem[0][0] + seconditem = actualitem[0][1] + if seconditem == "": + print("In first") + basejson = basejson[int(firstitem)] + else: + if seconditem == "max": + seconditem = len(basejson) + if seconditem == "min": + seconditem = 0 + + newvalue = [] + for i in range(int(firstitem), int(seconditem)): + # 1. Check the next item (message) + # 2. Call this function again + print("Base: %s" % basejson[i]) + + try: + ret = recurse_loop(basejson[i], parsersplit[outercnt+1:]) + except IndexError: + print("INDEXERROR: ", parsersplit[outercnt]) + #ret = innervalue + ret = recurse_loop(innervalue, parsersplit[outercnt:]) + + print(ret) + #exit() + newvalue.append(ret) + + return newvalue + + # FIXME: Add specific loop for other indexes + else: + #print("BEFORE NORMAL VALUE: ", basejson, value) + if len(value) == 0: + return basejson + + if isinstance(basejson[value], str): + print(f"LOADING STRING '%s' AS JSON" % basejson[value]) + try: + basejson = json.loads(basejson[value]) + except json.decoder.JSONDecodeError as e: + print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value]) + return basejson[value] + else: + basejson = basejson[value] + + outercnt += 1 + + except KeyError as e: + print("Lower keyerror: %s" % e) + #return basejson + #return "KeyError: Couldn't find key: %s" % e + + return basejson + # Takes a workflow execution as argument # Returns a string if the result is single, or a list if it's a list def get_json_value(execution_data, input_data): parsersplit = input_data.split(".") actionname = parsersplit[0][1:].replace(" ", "_", -1) + #Actionname: Start_node + print(f"Actionname: {actionname}") # 1. Find the action @@ -433,59 +521,7 @@ class AppBase: except json.decoder.JSONDecodeError as e: return baseresult - # This whole thing should be recursive. - try: - cnt = 0 - for value in parsersplit[1:]: - cnt += 1 - - print("VALUE: %s" % value) - if value == "#": - # FIXME - not recursive - should go deeper if there are more # - print("HANDLE RECURSIVE LOOP OF %s" % basejson) - returnlist = [] - try: - for innervalue in basejson: - print("Value: %s" % innervalue[parsersplit[cnt+1]]) - returnlist.append(innervalue[parsersplit[cnt+1]]) - except IndexError as e: - print("Indexerror inner: %s" % e) - # Basically means its a normal list, not a crazy one :) - # Custom format for ${name[0,1,2,...]}$ - indexvalue = "${NO_SPLITTER%s}$" % json.dumps(basejson) - if len(returnlist) > 0: - indexvalue = "${NO_SPLITTER%s}$" % json.dumps(returnlist) - - print("INDEXVAL: ", indexvalue) - return indexvalue - except TypeError as e: - print("TypeError inner: %s" % e) - - # Example format: ${[]}$ - parseditem = "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist)) - print("PARSED LOOP ITEM: %s" % parseditem) - return parseditem - - else: - print("BEFORE NORMAL VALUE: ", basejson, value) - if len(value) == 0: - return basejson - - if isinstance(basejson[value], str): - print(f"LOADING STRING '%s' AS JSON" % basejson[value]) - try: - basejson = json.loads(basejson[value]) - except json.decoder.JSONDecodeError as e: - print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value]) - return basejson[value] - else: - basejson = basejson[value] - - except KeyError as e: - print("Lower keyerror: %s" % e) - return "KeyError: Couldn't find key: %s" % e - - return basejson + return recurse_json(basejson, parsersplit[1:]) # Parses parameters sent to it and returns whether it did it successfully with the values found def parse_params(action, fullexecution, parameter): @@ -510,6 +546,7 @@ class AppBase: except IndexError: continue + # Handles for loops etc. value = get_json_value(fullexecution, to_be_replaced) if isinstance(value, str): parameter["value"] = parameter["value"].replace(to_be_replaced, value) @@ -849,7 +886,7 @@ class AppBase: replacement = replacement[1:len(replacement)-1] #except json.decoder.JSONDecodeError as e: - print("REPLACING %s with %s" % (key, replacement)) + #print("REPLACING %s with %s" % (key, replacement)) #replacement = parse_wrapper_start(replacement) tmpitem = tmpitem.replace(key, replacement, -1) @@ -911,6 +948,7 @@ class AppBase: results.append(json.loads(ret)) json_object = True except json.decoder.JSONDecodeError as e: + #print("Json: %s" % e) results.append(ret) # Dump the result as a string of a list @@ -920,7 +958,25 @@ class AppBase: if json_object: result = json.dumps(results) else: - result = "[\""+"\", \"".join(results)+"\"]" + result = "[" + for item in results: + try: + json.loads(item) + result += item + except json.decoder.JSONDecodeError as e: + # Common nested issue which puts " around everything + try: + tmpitem = item.replace("\\\"", "\"", -1) + json.loads(tmpitem) + result += tmpitem + + except: + result += "\"%s\"" % item + + result += ", " + + result = result[:-2] + result += "]" else: print("Normal result?") result = results @@ -932,7 +988,7 @@ class AppBase: action_result["result"] = result self.logger.debug(f"Executed {action['label']}-{action['id']} with result: {result}") - self.logger.debug(f"Data: %s" % action_result) + #self.logger.debug(f"Data: %s" % action_result) except TypeError as e: print("TypeError issue: %s" % e) action_result["status"] = "FAILURE" diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 3530ad73..bf365926 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -6467,11 +6467,6 @@ func init() { r := mux.NewRouter() r.HandleFunc("/api/v1/_ah/health", healthCheckHandler) - // Sends an email if the right things are specified - r.HandleFunc("/functions/sendmail", handleSendalert).Methods("POST", "OPTIONS") - r.HandleFunc("/functions/outlook/register", handleNewOutlookRegister).Methods("GET", "OPTIONS") - r.HandleFunc("/functions/outlook/getFolders", handleGetOutlookFolders).Methods("GET", "OPTIONS") - // Make user related locations r.HandleFunc("/api/v1/users/generateapikey", handleApiGeneration).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/users/login", handleLogin).Methods("POST", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 0d06dc37..2d80874e 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -2042,16 +2042,14 @@ func cleanupExecutions(resp http.ResponseWriter, request *http.Request) { return } - //if user.Role != "admin" { - // resp.WriteHeader(401) - // resp.Write([]byte(`{"success": false, "message": "Insufficient permissions"}`)) - // return - //} - - log.Printf("CLEANUP!") - log.Printf("%#v", user) + if user.Role != "admin" { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "Insufficient permissions"}`)) + return + } ctx := context.Background() + // Removes three months from today timestamp := int64(time.Now().AddDate(0, -2, 0).Unix()) log.Println(timestamp) @@ -2065,8 +2063,6 @@ func cleanupExecutions(resp http.ResponseWriter, request *http.Request) { return } - log.Println(len(workflowExecutions)) - resp.WriteHeader(200) resp.Write([]byte("OK")) } diff --git a/docker-compose.yml b/docker-compose.yml index 02efa84a..4f0c008d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: frontend: - #build: ./frontend + build: ./frontend image: frikky/shuffle:frontend container_name: shuffle-frontend hostname: shuffle-frontend diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ea4224f0..5ff1969b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -146,7 +146,7 @@ const App = (message, props) => { { window.location.pathname = "/docs/about" }} /> } /> } /> - { window.location.pathname = "/login" }} /> + } /> //
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 7495d06d..6c37c125 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -195,7 +195,7 @@ const Header = props => { - {userdata === undefined || userdata.orgs.length <= 1 ? null : + {userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null : } + } + // Shows nested list of nodes > their JSON lists + const ActionlistWrapper = (props) => { + + const handleMenuClose = () => { + setShowAutocomplete(false) + + if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") { + setShowDropdown(false) + } + + setUpdate(Math.random()) + setMenuPosition(null) + } + + const handleItemClick = (values) => { + if (values === undefined || values === null || values.length === 0) { + return + } + + var toComplete = selectedActionParameters[count].value.trim().endsWith("$") ? values[0].autocomplete : "$"+values[0].autocomplete + for (var key in values) { + if (key == 0 || values[key].autocomplete.length === 0) { + continue + } + + toComplete += values[key].autocomplete + } + + selectedActionParameters[count].value += toComplete + selectedAction.parameters[count].value = selectedActionParameters[count].value + console.log("TARGET: ", selectedActionParameters) + setSelectedAction(selectedAction) + setUpdate(Math.random()) + + setShowDropdown(false) + setMenuPosition(null) + } + + const iconStyle = { + marginRight: 15, + } + + return ( +
+ { + handleMenuClose() + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {actionlist.map(innerdata => { + const icon = innerdata.type === "action" ? : innerdata.type === "workflow_variable" || innerdata.type === "execution_variable" ? : + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById("execution_argument_input_field") + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e" + } else { + exec_text_field.style.border = "" + } + } + + // Also doing arguments + if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0) { + for (var key in workflow.triggers) { + const item = workflow.triggers[key] + + var node = cy.getElementById(item.id) + if (node.length > 0) { + if (inside) { + node.addClass('shuffle-hover-highlight') + } else { + node.removeClass('shuffle-hover-highlight') + } + } + + } + } + } + + const handleActionHover = (inside, actionId) => { + var node = cy.getElementById(actionId) + if (node.length > 0) { + if (inside) { + node.addClass('shuffle-hover-highlight') + } else { + node.removeClass('shuffle-hover-highlight') + } + } + } + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true) + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id) + } + } + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false) + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id) + } + } + + var parsedPaths = [] + if (typeof(innerdata.example) === "object") { + parsedPaths = GetParsedPaths(innerdata.example, "") + } + + return ( + parsedPaths.length > 0 ? + + {icon} {innerdata.name} +
+ } + parentMenuOpen={!!menuPosition} + style={{backgroundColor: inputColor, color: "white"}} + onClick={() => { + handleItemClick([innerdata]) + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = pathdata.type === "value" ? : pathdata.type === "list" ? : + return ( + {}} + onClick={() => { + handleItemClick([innerdata, pathdata]) + }} + > + +
+ {icon} {pathdata.name} +
+
+
+ ) + + })} + + : + handleMouseover()} onMouseOut={() => {handleMouseOut()}} + onClick={() => { + handleItemClick([innerdata]) + }} + > + +
+ {icon} {innerdata.name} +
+
+
+ + ) + })} + +
+ ) } var itemColor = "#f85a3e" @@ -2942,7 +3158,6 @@ const AngularWorkflow = (props) => { selectedActionParameters[count].value += e.target.value.autocomplete selectedAction.parameters[count].value = selectedActionParameters[count].value - console.log("TARGET: ", selectedActionParameters) setSelectedAction(selectedAction) setUpdate(Math.random()) @@ -2969,106 +3184,7 @@ const AngularWorkflow = (props) => { : null} {showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length === 0 ? - - Autocomplete - - + : null} diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index f6ae675d..6fc4f596 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -45,6 +45,10 @@ const inputColor = "#383B40" export const GetParsedPaths = (inputdata, basekey) => { const splitkey = " => " var parsedValues = [] + if (typeof(inputdata) !== "object") { + return parsedValues + } + for (const [key, value] of Object.entries(inputdata)) { // Check if loop or JSON From f6edb2aeb71e339e2d39e4d71b50dc79b547830c Mon Sep 17 00:00:00 2001 From: frikky Date: Sun, 27 Sep 2020 20:44:53 +0200 Subject: [PATCH 11/31] Fixed bugs in appcreator --- backend/go-app/codegen.go | 4 ++-- backend/go-app/walkoff.go | 2 +- frontend/src/views/AngularWorkflow.jsx | 12 +++++++----- frontend/src/views/AppCreator.jsx | 2 ++ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/backend/go-app/codegen.go b/backend/go-app/codegen.go index 5ac9340a..c23070f3 100644 --- a/backend/go-app/codegen.go +++ b/backend/go-app/codegen.go @@ -553,7 +553,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, }) } else if securitySchemes["BasicAuth"] != nil { api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{ - Name: "username_auth", + Name: "username_basic", Value: "", Example: "username", Description: securitySchemes["BasicAuth"].Value.Description, @@ -565,7 +565,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, }) api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{ - Name: "password_auth", + Name: "password_basic", Value: "", Example: "*****", Description: securitySchemes["BasicAuth"].Value.Description, diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 5a3156f2..73982dac 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1843,7 +1843,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { if !found && param.Required { log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) return } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 81ccc578..353d36c9 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -5476,7 +5476,7 @@ const AngularWorkflow = (props) => { {executionData.results === undefined || executionData.results === null || executionData.results.length === 0 && executionData.status === "EXECUTING" ? : - executionData.results.map(data => { + executionData.results.map((data, index) => { if (executionData.results.length !== 1 && !showSkippedActions && (data.status === "SKIPPED" || data.status === "FAILURE")) { return null } @@ -5504,7 +5504,7 @@ const AngularWorkflow = (props) => { {data.action.app_name} return ( -
+
{actionimg} {data.action.label} @@ -5806,6 +5806,7 @@ const AngularWorkflow = (props) => { console.log(authenticationOption) if (authenticationOption.label.length === 0) { alert.info("Label can't be empty") + return } for (var key in selectedApp.authentication.parameters) { @@ -5819,7 +5820,6 @@ const AngularWorkflow = (props) => { selectedAction.selectedAuthentication = authenticationOption selectedAction.authentication.push(authenticationOption) setSelectedAction(selectedAction) - setUpdate(authenticationOption.id) var newAuthOption = JSON.parse(JSON.stringify(authenticationOption)) var newFields = [] @@ -5834,6 +5834,7 @@ const AngularWorkflow = (props) => { console.log("FIELDS: ", newFields) newAuthOption.fields = newFields setNewAppAuth(newAuthOption) + setUpdate(authenticationOption.id) } return ( @@ -5842,7 +5843,6 @@ const AngularWorkflow = (props) => { What is this?
These are required fields for authenticating with {selectedApp.name}
- {selectedApp.link.length > 0 ? : null} Name - what is this used for? { authenticationOption.label = event.target.value }} /> + {selectedApp.link.length > 0 ?
: null}
{selectedApp.authentication.parameters.map((data, index) => { @@ -5912,9 +5913,10 @@ const AngularWorkflow = (props) => { const EndpointData = () => { const [tmpVar, setTmpVar] = React.useState("") + return (
- The API endpoint to use (URL) - leave this if you're unsure + The API endpoint to use (URL) - predefined in the app { } } + console.log("SCHEMES: ", securitySchemes) + setActions(newActions) setIsAppLoaded(true) } From 62c01b2df8dbfcd5b05432f4ad5b9550b7f0ec7f Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 28 Sep 2020 07:20:14 +0200 Subject: [PATCH 12/31] Removed indexing for value fields --- backend/go-app/main.go | 8 ++++---- backend/go-app/walkoff.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 54cd24bd..14add147 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -150,7 +150,7 @@ type UserAuth struct { type UserAuthField struct { Key string `json:"key" datastore:"key"` - Value string `json:"value" datastore:"value"` + Value string `json:"value" datastore:"value,noindex"` } // Not environment, but execution environment @@ -209,7 +209,7 @@ type Contact struct { type Translator struct { Src struct { Name string `json:"name" datastore:"name"` - Value string `json:"value" datastore:"value"` + Value string `json:"value" datastore:"value,noindex"` Description string `json:"description" datastore:"description,noindex"` Required string `json:"required" datastore:"required"` Type string `json:"type" datastore:"type"` @@ -219,7 +219,7 @@ type Translator struct { } `json:"src" datastore:"src"` Dst struct { Name string `json:"name" datastore:"name"` - Value string `json:"value" datastore:"value"` + Value string `json:"value" datastore:"value,noindex"` Type string `json:"type" datastore:"type"` Description string `json:"description" datastore:"description,noindex"` Required string `json:"required" datastore:"required"` @@ -231,7 +231,7 @@ type Translator struct { type Appconfig struct { Key string `json:"key" datastore:"key"` - Value string `json:"value" datastore:"value"` + Value string `json:"value" datastore:"value,noindex"` } type ScheduleApp struct { diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 73982dac..995c8fc1 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -129,7 +129,7 @@ type WorkflowAppActionParameter struct { ID string `json:"id" datastore:"id" yaml:"id,omitempty"` Name string `json:"name" datastore:"name" yaml:"name"` Example string `json:"example" datastore:"example" yaml:"example"` - Value string `json:"value" datastore:"value" yaml:"value,omitempty"` + Value string `json:"value" datastore:"value,noindex" yaml:"value,omitempty"` Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` Options []string `json:"options" datastore:"options" yaml:"options"` ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"` @@ -163,7 +163,7 @@ type WorkflowAppAction struct { Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id"` Name string `json:"name" datastore:"name"` - Value string `json:"value" datastore:"value"` + Value string `json:"value" datastore:"value,noindex"` } `json:"execution_variable" datastore:"execution_variables"` Returns struct { Description string `json:"description" datastore:"returns" yaml:"description,omitempty"` @@ -308,7 +308,7 @@ type Workflow struct { Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id"` Name string `json:"name" datastore:"name"` - Value string `json:"value" datastore:"value"` + Value string `json:"value" datastore:"value,noindex"` } `json:"workflow_variables" datastore:"workflow_variables"` ExecutionVariables []struct { Description string `json:"description" datastore:"description,noindex"` @@ -338,7 +338,7 @@ type AuthenticationParams struct { ID string `json:"id" datastore:"id" yaml:"id"` Name string `json:"name" datastore:"name" yaml:"name"` Example string `json:"example" datastore:"example" yaml:"example"` - Value string `json:"value,omitempty" datastore:"value" yaml:"value"` + Value string `json:"value,omitempty" datastore:"value,noindex" yaml:"value"` Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` Required bool `json:"required" datastore:"required" yaml:"required"` In string `json:"in" datastore:"in" yaml:"in"` @@ -348,7 +348,7 @@ type AuthenticationParams struct { type AuthenticationStore struct { Key string `json:"key" datastore:"key"` - Value string `json:"value" datastore:"value"` + Value string `json:"value" datastore:"value,noindex"` } type ExecutionRequestWrapper struct { From f371d67f035d9bbde5c4651d7d079e4dad6f39cd Mon Sep 17 00:00:00 2001 From: frikky Date: Tue, 29 Sep 2020 19:01:02 +0200 Subject: [PATCH 13/31] Fixed workflow import/export issues --- backend/app_sdk/app_base.py | 2 +- backend/go-app/main.go | 1 + backend/go-app/walkoff.go | 155 ++++++++++++++++--------- frontend/src/views/AngularWorkflow.jsx | 120 +++++++++++-------- frontend/src/views/AppCreator.jsx | 2 - frontend/src/views/Workflows.jsx | 11 +- functions/onprem/worker/worker.go | 10 +- 7 files changed, 189 insertions(+), 112 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index f9ea4397..7d162334 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1054,7 +1054,7 @@ class AppBase: print(f"Failed to execute: {e}") self.logger.exception(f"Failed to execute {e}-{action['id']}") action_result["status"] = "FAILURE" - action_result["result"] = "General exception: %s" % e + action_result["result"] = f"General exception: {e}" action_result["completed_at"] = int(time.time()) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 14add147..b799a89f 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -2955,6 +2955,7 @@ func getSpecificWebhook(resp http.ResponseWriter, request *http.Request) { } ctx := context.Background() + // FIXME: Schedule = trigger? schedule, err := getSchedule(ctx, workflowId) if err != nil { log.Printf("Failed setting schedule: %s", err) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 995c8fc1..1e891291 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1601,10 +1601,30 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { for _, trigger := range workflow.Triggers { log.Printf("Trigger %s: %s", trigger.TriggerType, trigger.Status) + // Check if it's actually running + // FIXME: Do this for other triggers too + if trigger.TriggerType == "SCHEDULE" && trigger.Status != "uninitialized" { + schedule, err := getSchedule(ctx, trigger.ID) + if err != nil { + trigger.Status = "stopped" + } else if schedule.Id == "" { + trigger.Status = "stopped" + } + } + //log.Println("TRIGGERS") allNodes = append(allNodes, trigger.ID) } + for _, variable := range workflow.WorkflowVariables { + if len(variable.Value) == 0 { + log.Printf("Can't have an empty variable: %s", variable.Name) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Variable %s can't be empty"}`, variable.Name))) + return + } + } + if len(workflow.Actions) == 0 { workflow.Actions = []Action{} } @@ -1787,70 +1807,77 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { // Check to see if the whole app is valid if curapp.Name != action.AppName { - log.Printf("App %s doesn't exist.", action.AppName) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) - return - } + workflow.Errors = append(workflow.Errors, fmt.Sprintf("App %s doesn't exist", action.AppName)) + action.Errors = append(action.Errors, "This app doesn't exist.") + action.IsValid = false + workflow.IsValid = false - // Check tosee if the appaction is valid - curappaction := WorkflowAppAction{} - for _, curAction := range curapp.Actions { - if action.Name == curAction.Name { - curappaction = curAction - break - } - } - - // Check to see if the action is valid - if curappaction.Name != action.Name { - log.Printf("Appaction %s doesn't exist.", action.Name) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // FIXME - check all parameters to see if they're valid - // Includes checking required fields - - newParams := []WorkflowAppActionParameter{} - for _, param := range curappaction.Parameters { - found := false - - // Handles check for parameter exists + value not empty in used fields - for _, actionParam := range action.Parameters { - if actionParam.Name == param.Name { - found = true - - if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true { - log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) - return - - } - - if actionParam.Variant == "" { - actionParam.Variant = "STATIC_VALUE" - } - - newParams = append(newParams, actionParam) + // Append with errors + newActions = append(newActions, action) + log.Printf("App %s doesn't exist. Adding as error.", action.AppName) + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) + //return + } else { + // Check tosee if the appaction is valid + curappaction := WorkflowAppAction{} + for _, curAction := range curapp.Actions { + if action.Name == curAction.Name { + curappaction = curAction break } } - // Handles check for required params - if !found && param.Required { - log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name) + // Check to see if the action is valid + if curappaction.Name != action.Name { + log.Printf("Appaction %s doesn't exist.", action.Name) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) + resp.Write([]byte(`{"success": false}`)) return } - } + // FIXME - check all parameters to see if they're valid + // Includes checking required fields - action.Parameters = newParams - newActions = append(newActions, action) + newParams := []WorkflowAppActionParameter{} + for _, param := range curappaction.Parameters { + found := false + + // Handles check for parameter exists + value not empty in used fields + for _, actionParam := range action.Parameters { + if actionParam.Name == param.Name { + found = true + + if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true { + log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) + return + + } + + if actionParam.Variant == "" { + actionParam.Variant = "STATIC_VALUE" + } + + newParams = append(newParams, actionParam) + break + } + } + + // Handles check for required params + if !found && param.Required { + log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) + return + } + + } + + action.Parameters = newParams + newActions = append(newActions, action) + } } } @@ -1873,9 +1900,25 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { log.Printf("Failed to change total actions data: %s", err) } + type returnData struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + } + + returndata := returnData{ + Success: true, + Errors: workflow.Errors, + } + log.Printf("Saved new version of workflow %s (%s)", workflow.Name, fileId) resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) + newBody, err := json.Marshal(returndata) + if err != nil { + resp.Write([]byte(`{"success": true}`)) + return + } + + resp.Write(newBody) } func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) { @@ -2627,6 +2670,8 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) { err = deleteSchedule(ctx, scheduleId) if err != nil { + log.Printf("Failed deleting schedule: %s", err) + if strings.Contains(err.Error(), "Job not found") { resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 353d36c9..89d28eab 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -576,6 +576,9 @@ const AngularWorkflow = (props) => { useworkflow.triggers = newTriggers useworkflow.branches = newBranches + // Errors are backend defined + useworkflow.errors = [] + setLastSaved(true) fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, { method: 'PUT', @@ -599,6 +602,15 @@ const AngularWorkflow = (props) => { alert.error("Failed to save: "+responseJson.reason) } else { success = true + if (responseJson.errors !== undefined) { + console.log(responseJson) + workflow.errors = responseJson.errors + if (responseJson.errors.length === 0) { + workflow.isValid = true + } + + setWorkflow(workflow) + } alert.success("Successfully saved workflow") } }) @@ -925,7 +937,46 @@ const AngularWorkflow = (props) => { const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version) if (!curapp || curapp === undefined) { alert.error("App "+curaction.app_name+" not found. Did someone delete it?") - return + //return + } else { + setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) + if (curapp.authentication.required) { + // Setup auth here :) + const authenticationOptions = [] + var findAuthId = "" + if (curaction.authentication_id !== null && curaction.authentication_id !== undefined && curaction.authentication_id.length > 0) { + findAuthId = curaction.authentication_id + } + + var tmpAuth = JSON.parse(JSON.stringify(appAuthentication)) + for (var key in tmpAuth) { + var item = tmpAuth[key] + + const newfields = {} + for (var filterkey in item.fields) { + newfields[item.fields[filterkey].key] = item.fields[filterkey].value + } + + item.fields = newfields + if (item.app.name === curapp.name) { + authenticationOptions.push(item) + if (item.id === findAuthId) { + curaction.selectedAuthentication = item + } + } + } + + curaction.authentication = authenticationOptions + if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") { + curaction.selectedAuthentication = {} + } + } else { + curaction.authentication = [] + curaction.authentication_id = "" + curaction.selectedAuthentication = {} + } + + setSelectedApp(curapp) } var env = environments.find(a => a.Name === curaction.environment) @@ -934,48 +985,8 @@ const AngularWorkflow = (props) => { } setSelectedActionEnvironment(env) - setSelectedActionName(curaction.name) + setSelectedActionName(curaction.name) - setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) - - - if (curapp.authentication.required) { - // Setup auth here :) - const authenticationOptions = [] - var findAuthId = "" - if (curaction.authentication_id !== null && curaction.authentication_id !== undefined && curaction.authentication_id.length > 0) { - findAuthId = curaction.authentication_id - } - - var tmpAuth = JSON.parse(JSON.stringify(appAuthentication)) - for (var key in tmpAuth) { - var item = tmpAuth[key] - - const newfields = {} - for (var filterkey in item.fields) { - newfields[item.fields[filterkey].key] = item.fields[filterkey].value - } - - item.fields = newfields - if (item.app.name === curapp.name) { - authenticationOptions.push(item) - if (item.id === findAuthId) { - curaction.selectedAuthentication = item - } - } - } - - curaction.authentication = authenticationOptions - if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") { - curaction.selectedAuthentication = {} - } - } else { - curaction.authentication = [] - curaction.authentication_id = "" - curaction.selectedAuthentication = {} - } - - setSelectedApp(curapp) setSelectedAction(curaction) } else if (data.type === "TRIGGER") { //console.log("Should handle trigger "+data.triggertype) @@ -1513,10 +1524,17 @@ const AngularWorkflow = (props) => { return response.json() }) .then((responseJson) => { + // No matter what, it's being stopped. if (!responseJson.success) { - //alert.error("Failed to delete schedule: " + responseJson.reason) + alert.error("Failed to stop schedule: " + responseJson.reason) + + workflow.triggers[triggerindex].status = "stopped" + trigger.status = "stopped" + setSelectedTrigger(trigger) + setWorkflow(workflow) + saveWorkflow(workflow) } else { - //alert.success("Successfully stopped schedule") + alert.success("Successfully stopped schedule") workflow.triggers[triggerindex].status = "stopped" trigger.status = "stopped" setSelectedTrigger(trigger) @@ -3255,13 +3273,17 @@ const AngularWorkflow = (props) => { } function sortByKey(array, key) { + if (array === undefined) { + return [] + } + return array.sort(function(a, b) { var x = a[key]; var y = b[key] return ((x < y) ? -1 : ((x > y) ? 1 : 0)) }) } - const appApiView = Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0 ? + const appApiView = Object.getOwnPropertyNames(selectedAction).length > 0 ?
@@ -3300,7 +3322,7 @@ const AngularWorkflow = (props) => { placeholder={selectedAction.label} onChange={selectedNameChange} /> - {selectedAction.authentication.length === 0 && requiresAuthentication ? + {selectedApp.name !== undefined && selectedAction.authentication !== undefined && selectedAction.authentication.length === 0 && requiresAuthentication ?
Authenticate {selectedApp.name}: @@ -3312,7 +3334,7 @@ const AngularWorkflow = (props) => {
: null} - {selectedAction.authentication.length > 0 ? + {selectedAction.authentication !== undefined && selectedAction.authentication.length > 0 ?
Authentication
@@ -5136,7 +5158,7 @@ const AngularWorkflow = (props) => { : -
} parentMenuOpen={!!menuPosition} - style={{backgroundColor: inputColor, color: "white"}} + style={{backgroundColor: inputColor, color: "white", minWidth: 250,}} onClick={() => { handleItemClick([innerdata]) }} @@ -3078,7 +3078,7 @@ const AngularWorkflow = (props) => { // FIXME: Should be recursive in here const icon = pathdata.type === "value" ? : pathdata.type === "list" ? : return ( - {}} + {}} onClick={() => { handleItemClick([innerdata, pathdata]) }} diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index b9d6365c..9c4fb0ab 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -67,7 +67,7 @@ export const GetParsedPaths = (inputdata, basekey) => { return parsedValues } - console.log("KEY: ", key, "VALUE: ", value, "BASEKEY: ", basekeyname) + //console.log("KEY: ", key, "VALUE: ", value, "BASEKEY: ", basekeyname) if (typeof(value) === 'object') { if (Array.isArray(value)) { // Check if each item is object From 0f5f97a837906d6a70646df6109ad94753bd6faf Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 5 Oct 2020 11:37:11 +0200 Subject: [PATCH 19/31] Authentication fixes to workflow imports --- backend/go-app/walkoff.go | 21 ++++++++++++++------- frontend/src/views/AngularWorkflow.jsx | 19 ++++++++++++++----- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 786a9b3b..06a23496 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1585,7 +1585,8 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { } // FIXME: Have a good way of tracking errors. ID's or similar. - if !action.IsValid { + if !action.IsValid && len(action.Errors) > 0 { + log.Printf("Node %s is invalid and needs to be remade. Errors: %s", action.Label, strings.Join(action.Errors, "\n")) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Node %s is invalid and needs to be remade."}`, action.Label))) return @@ -1792,10 +1793,16 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { } if !authFound { - log.Printf("App auth %s doesn't exist", action.AuthenticationId) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App auth %s doesn't exist"}`, action.AuthenticationId))) - return + log.Printf("App auth %s doesn't exist. Setting error", action.AuthenticationId) + workflow.Errors = append(workflow.Errors, fmt.Sprintf("App authentication for %s doesn't exist!", action.AppName)) + workflow.IsValid = false + + action.Errors = append(action.Errors, "App authentication doesn't exist") + action.IsValid = false + action.AuthenticationId = "" + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App auth %s doesn't exist"}`, action.AuthenticationId))) + //return } } @@ -2421,9 +2428,9 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf for _, authparam := range curAuth.Fields { if param.Name == authparam.Key { - log.Printf("Name: %s - value: %s", param.Name, param.Value) param.Value = authparam.Value - log.Printf("Name: %s - value: %s\n", param.Name, param.Value) + //log.Printf("Name: %s - value: %s", param.Name, param.Value) + //log.Printf("Name: %s - value: %s\n", param.Name, param.Value) break } } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 6ab6e3f3..117d0878 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -558,6 +558,10 @@ const AngularWorkflow = (props) => { } } + // Override just in this place + curworkflowAction.errors = [] + curworkflowAction.isValid = true + newActions.push(curworkflowAction) } else if (type === "TRIGGER") { //console.log("TRIGGER") @@ -2258,6 +2262,7 @@ const AngularWorkflow = (props) => { const ParsedAppPaper = (props) => { const app = props.app + const [hover, setHover] = React.useState(false) // FIXME - add label to apps, as this might be slow with A LOT of apps var newAppname = app.name @@ -2268,6 +2273,10 @@ const AngularWorkflow = (props) => { } const image = "url("+app.large_image+")" + const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle)) + const pixelSize = !hover ? "2px" : "4px" + newAppStyle.borderLeft = app.is_valid ? `${pixelSize} solid green` : `${pixelSize} solid orange` + return ( { y: 0, }} > - -
-
- + {setHover(true)}} onMouseOut={() => {setHover(false)}}> +
@@ -5838,7 +5845,7 @@ const AngularWorkflow = (props) => { } const handleSubmitCheck = () => { - console.log(authenticationOption) + console.log("NEW AUTH: ", authenticationOption) if (authenticationOption.label.length === 0) { alert.info("Label can't be empty") return @@ -5869,6 +5876,8 @@ const AngularWorkflow = (props) => { console.log("FIELDS: ", newFields) newAuthOption.fields = newFields setNewAppAuth(newAuthOption) + appAuthentication.push(newAuthOption) + setAppAuthentication(appAuthentication) setUpdate(authenticationOption.id) } From 4fe0d3d03573c817e7bac358298a96f0bffac116 Mon Sep 17 00:00:00 2001 From: frikky Date: Wed, 7 Oct 2020 16:50:15 +0200 Subject: [PATCH 20/31] #163: Major fixes to app sdk --- backend/app_sdk/app_base.py | 88 +++++++++++++++++++++++--- backend/app_sdk/build.sh | 2 +- backend/go-app/main.go | 4 +- frontend/src/views/AngularWorkflow.jsx | 1 + 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 794fc9ae..30a0f9f2 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -854,6 +854,7 @@ class AppBase: minlength = 0 multi_parameters = json.loads(json.dumps(params)) multiexecution = False + multi_execution_lists = [] for parameter in action["parameters"]: check, value, is_loop = parse_params(action, fullexecution, parameter) @@ -883,6 +884,8 @@ class AppBase: # Loop WITH variables go in else. if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER": print("Pre replacement: %s" % actualitem[0][2]) + tmpitem = value + replacement = actualitem[0][2] if replacement.startswith("\"") and replacement.endswith("\""): replacement = replacement[1:len(replacement)-1] @@ -899,9 +902,10 @@ class AppBase: if len(json_replacement) > minlength: minlength = len(json_replacement) - #value = parse_wrapper_start(json_replacement) - params[parameter["name"]] = json_replacement - multi_parameters[parameter["name"]] = json_replacement + tmpitem = tmpitem.replace(actualitem[0][0], replacement, 1) + multi_execution_lists.append(tmpitem) + params[parameter["name"]] = tmpitem + multi_parameters[parameter["name"]] = tmpitem print("MULTI finished: %s" % replacement) else: @@ -953,6 +957,28 @@ class AppBase: params[parameter["name"]] = value multi_parameters[parameter["name"]] = value + + # Fix lists here + print("CHECKING multi execution list!") + if len(multi_execution_lists) > 0: + print("Multi execution list has more data: %d" % len(multi_execution_lists)) + filteredlist = [] + for listitem in multi_execution_lists: + if listitem in filteredlist: + continue + + filteredlist.append(listitem) + + #print("New list length: %d" % len(filteredlist)) + if len(filteredlist) > 1: + print("Calculating new multi-loop length") + tmplength = 1 + for innerlist in filteredlist: + tmplength = len(innerlist)*tmplength + + minlength = tmplength + + #print("New multi execution length: %d" % tmplength) # FIXME - this is horrible, but works for now #for i in range(calltimes): @@ -970,21 +996,67 @@ class AppBase: print("Can't handle type %s value from function" % (type(newres))) print("POST NEWRES RESULT: ", result) else: - print("APP_SDK DONE: Starting MULTI execution with", multi_parameters) - # 1. Use number of executions based on longest array + print("APP_SDK DONE: Starting MULTI execution with values %s of length %d" % (multi_parameters, minlength)) + # 1. Use number of executions based on the arrays being similar # 2. Find the right value from the parsed multi_params - results = [] json_object = False for i in range(0, minlength): # To be able to use the results as a list: baseparams = json.loads(json.dumps(multi_parameters)) # {'call': ['GoogleSafebrowsing_2_0', 'VirusTotal_GetReport_3_0']} - + # 1. Check if list length is same as minlength + # 2. If NOT same length, duplicate based on length of array + # arraylength = 3 ["1", "2", "3"] + # arraylength = 4 ["1", "2", "3", "4"] + # minlength = 12 - 12/3 = 4 per item = ["1", "1", "1", "1", "2", "2", ...] + try: + firstlist = True for key, value in baseparams.items(): + if isinstance(value, list): - baseparams[key] = value[i] + try: + newvalue = value[i] + except IndexError: + pass + + if len(value) != minlength and len(value) > 0: + newarray = [] + print("VALUE: ", value) + additiontime = minlength/len(value) + #print("Bad length for value: %d - should be %d. Additiontime: %d" % (len(value), minlength, additiontime)) + if firstlist: + print("Running normal list (FIRST)") + for subvalue in value: + for number in range(int(additiontime)): + newarray.append(subvalue) + else: + #print("Running secondary lists") + ## 1. Set up length of array + ## 2. Put values spread out + # FIXME: This works well, except if lists are same length + newarray = [""] * minlength + + cnt = 0 + for number in range(int(additiontime)): + for subvaluerange in range(len(value)): + # newlocation = number+(additiontime*subvaluerange) + # print("%d+(%d*%d) = %d. VAL: %s" % (number, additiontime, subvaluerange, newlocation, value[subvaluerange])) + # Reverse if same length? + if int(minlength/len(value)) == len(value): + tmp = int(len(value)-subvaluerange-1) + print("NEW: %d" % tmp) + newarray[cnt] = value[tmp] + else: + newarray[cnt] = value[subvaluerange] + cnt += 1 + + print("Newarray =", newarray) + newvalue = newarray[i] + firstlist = False + + baseparams[key] = newvalue except IndexError as e: print("IndexError: %s" % e) baseparams[key] = "IndexError: %s" % e diff --git a/backend/app_sdk/build.sh b/backend/app_sdk/build.sh index 240681aa..b1ac5113 100644 --- a/backend/app_sdk/build.sh +++ b/backend/app_sdk/build.sh @@ -1,6 +1,6 @@ #!/bin/bash NAME=app_sdk -VERSION=0.7.0 +VERSION=0.7.3 docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION diff --git a/backend/go-app/main.go b/backend/go-app/main.go index b799a89f..9ed398f7 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1982,7 +1982,7 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { } if len(users) != 1 { - log.Printf(`Found multiple users with the same username: %s: %d`, t.Username, len(users)) + log.Printf(`Found multiple or no users with the same username: %s: %d`, t.Username, len(users)) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s (%d)"}`, len(users), t.Username))) return @@ -2290,7 +2290,7 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { } if len(users) != 1 { - log.Printf(`Found multiple users with the same username: %s: %d`, data.Username, len(users)) + log.Printf(`Found multiple or no users with the same username: %s: %d`, data.Username, len(users)) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s"}`, len(users), data.Username))) return diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 117d0878..be9cd8d2 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2531,6 +2531,7 @@ const AngularWorkflow = (props) => { var jsonvalid = true try { + console.log(foundResult.result) const tmp = String(JSON.parse(foundResult.result)) if (!tmp.includes("{") && !tmp.includes("[")) { jsonvalid = false From 916683e3e57f0d5255e713a11f2fa76869c5a640 Mon Sep 17 00:00:00 2001 From: frikky Date: Sat, 10 Oct 2020 05:33:08 +0200 Subject: [PATCH 21/31] #171: Added a check for max result size --- backend/go-app/walkoff.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 06a23496..590b016f 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1090,8 +1090,33 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { // log.Printf("Name: %s, Env: %s", action.Name, action.Environment) //} + tmpJson, err := json.Marshal(workflowExecution) + if err == nil { + if len(tmpJson) >= 1048487 { + log.Printf("[ERROR] Result length is too long! Need to reduce result size") + + // Result string `json:"result" datastore:"result,noindex"` + // Arbitrary reduction size + maxSize := 500000 + newResults := []ActionResult{} + for _, item := range workflowExecution.Results { + if len(item.Result) > maxSize { + item.Result = "[ERROR] Result too large to handle (https://github.com/frikky/shuffle/issues/171)" + } + + newResults = append(newResults, item) + } + + workflowExecution.Results = newResults + } + } + err = setWorkflowExecution(ctx, *workflowExecution) if err != nil { + //workflowExecution.Result = "Error setting workflow: result too large" + //workflowExecution.Status = "FINISHED" + //workflowExecution.CompletedAt = int64(time.Now().Unix()) + log.Printf("Error saving workflow execution actionresult setting: %s", err) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err))) From 274ffce9fa059660e9398db13983140dc00fa664 Mon Sep 17 00:00:00 2001 From: frikky Date: Sat, 10 Oct 2020 06:14:38 +0200 Subject: [PATCH 22/31] #170: Fixed double for-loop issues. Now for anything >2 .. :) --- backend/app_sdk/app_base.py | 28 ++++++++++++++-------- frontend/src/views/AngularWorkflow.jsx | 32 ++++++++++++++++++-------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 30a0f9f2..97026b67 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -876,7 +876,6 @@ class AppBase: actionname = action["name"] #print("Multicheck ", actualitem) print("Actual item: %s" % actualitem) - print("LENGTH: %d" % len(actualitem)) if len(actualitem) > 0: multiexecution = True @@ -903,9 +902,12 @@ class AppBase: minlength = len(json_replacement) tmpitem = tmpitem.replace(actualitem[0][0], replacement, 1) - multi_execution_lists.append(tmpitem) params[parameter["name"]] = tmpitem - multi_parameters[parameter["name"]] = tmpitem + multi_execution_lists.append(json_replacement) + multi_parameters[parameter["name"]] = json_replacement + + #print("LENGTH OF ARR: %d" % len(resultarray)) + #print("RESULTARRAY: %s" % resultarray) print("MULTI finished: %s" % replacement) else: @@ -949,6 +951,11 @@ class AppBase: resultarray.append(tmpitem) # With this parameter ready, add it to... a greater list of parameters. Rofl + print("LENGTH OF ARR: %d" % len(resultarray)) + print("RESULTARRAY: %s" % resultarray) + if resultarray not in multi_execution_lists: + multi_execution_lists.append(resultarray) + multi_parameters[parameter["name"]] = resultarray else: # Parses things like int(value) @@ -961,7 +968,7 @@ class AppBase: # Fix lists here print("CHECKING multi execution list!") if len(multi_execution_lists) > 0: - print("Multi execution list has more data: %d" % len(multi_execution_lists)) + print("\n Multi execution list has more data: %d" % len(multi_execution_lists)) filteredlist = [] for listitem in multi_execution_lists: if listitem in filteredlist: @@ -971,14 +978,15 @@ class AppBase: #print("New list length: %d" % len(filteredlist)) if len(filteredlist) > 1: - print("Calculating new multi-loop length") + print("Calculating new multi-loop length with %d lists" % len(filteredlist)) tmplength = 1 for innerlist in filteredlist: + print("List length: %d. %d*%d" % (len(innerlist), len(innerlist), tmplength)) tmplength = len(innerlist)*tmplength minlength = tmplength - #print("New multi execution length: %d" % tmplength) + print("New multi execution length: %d\n" % tmplength) # FIXME - this is horrible, but works for now #for i in range(calltimes): @@ -1025,7 +1033,7 @@ class AppBase: newarray = [] print("VALUE: ", value) additiontime = minlength/len(value) - #print("Bad length for value: %d - should be %d. Additiontime: %d" % (len(value), minlength, additiontime)) + print("Bad length for value: %d - should be %d. Additiontime: %d" % (len(value), minlength, additiontime)) if firstlist: print("Running normal list (FIRST)") for subvalue in value: @@ -1052,7 +1060,7 @@ class AppBase: newarray[cnt] = value[subvaluerange] cnt += 1 - print("Newarray =", newarray) + #print("Newarray =", newarray) newvalue = newarray[i] firstlist = False @@ -1079,10 +1087,10 @@ class AppBase: #print("Json: %s" % e) results.append(ret) - print("Inner ret parsed: %s" % ret) + #print("Inner ret parsed: %s" % ret) # Dump the result as a string of a list - print("RESULTS: %s" % results) + #print("RESULTS: %s" % results) if isinstance(results, list): print("JSON OBJECT? ", json_object) if json_object: diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index be9cd8d2..d18044b9 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2524,14 +2524,17 @@ const AngularWorkflow = (props) => { continue } - const foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id) + var foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id) if (foundResult === undefined) { continue } + foundResult.result = foundResult.result.trim() + foundResult.result = foundResult.result.split(" None").join(" \"None\"") + foundResult.result = foundResult.result.split("\'").join("\"") + var jsonvalid = true try { - console.log(foundResult.result) const tmp = String(JSON.parse(foundResult.result)) if (!tmp.includes("{") && !tmp.includes("[")) { jsonvalid = false @@ -2544,9 +2547,10 @@ const AngularWorkflow = (props) => { if (jsonvalid) { exampledata = JSON.parse(foundResult.result) break - } else { - console.log("Invalid JSON: ", foundResult.result) - } + } + //else { + // console.log("Invalid JSON: ", foundResult.result) + //} } } @@ -2610,10 +2614,14 @@ const AngularWorkflow = (props) => { if (curstring.length > 0) { // Search back in the action list curstring = curstring.split(" ").join("_").toLowerCase() - const actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring) + var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring) if (actionItem !== undefined) { console.log("Found item: ", actionItem) + //actionItem.example = actionItem.example.trim() + //actionItem.example = actionItem.example.split(" None").join(" \"None\"") + //actionItem.example = actionItem.example.split("\'").join("\"") + var jsonvalid = true try { const tmp = String(JSON.parse(actionItem.example)) @@ -2977,7 +2985,6 @@ const AngularWorkflow = (props) => { selectedActionParameters[count].value += toComplete selectedAction.parameters[count].value = selectedActionParameters[count].value - console.log("TARGET: ", selectedActionParameters) setSelectedAction(selectedAction) setUpdate(Math.random()) @@ -5336,7 +5343,8 @@ const AngularWorkflow = (props) => { const parsedExecutionArgument = () => { var showResult = executionData.execution_argument.trim() - showResult.split(" None").join(" \"None\"") + showResult = showResult.split(" None").join(" \"None\"") + showResult = showResult.split("\'").join("\"") var jsonvalid = true try { @@ -5524,11 +5532,15 @@ const AngularWorkflow = (props) => { return null } - var showResult = data.result.trim() - showResult.split(" None").join(" \"None\"") // showResult = replaceAll(showResult, " None", " \"None\"") // Super basic check. + // + // FIXME: The latter replace doens't really work if ' is used in a string + var showResult = data.result.trim() + showResult = showResult.split(" None").join(" \"None\"") + showResult = showResult.split("\'").join("\"") + var jsonvalid = true try { const tmp = String(JSON.parse(showResult)) From afc0117423fb103c5f63d6832f291bd8476dbb54 Mon Sep 17 00:00:00 2001 From: frikky Date: Sat, 10 Oct 2020 06:33:22 +0200 Subject: [PATCH 23/31] Fixed execution view bugs --- frontend/src/views/AngularWorkflow.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index d18044b9..0e7dd0b3 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -5562,7 +5562,10 @@ const AngularWorkflow = (props) => {
{actionimg} - {data.action.label} +
+
{data.action.label}
+
{data.action.name}
+
Status {data.status}
{jsonvalid ? Date: Sat, 10 Oct 2020 11:42:33 +0200 Subject: [PATCH 24/31] Added a default organization with migration --- backend/go-app/main.go | 321 ++++++++++++++++++++++++---- backend/go-app/walkoff.go | 13 +- frontend/src/components/Header.js | 25 +-- frontend/src/views/Admin.jsx | 68 +++++- frontend/src/views/SettingsPage.jsx | 3 +- 5 files changed, 371 insertions(+), 59 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 9ed398f7..db2e91ab 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -181,6 +181,7 @@ type User struct { Id string `datastore:"id" json:"id"` Orgs []string `datastore:"orgs" json:"orgs"` CreationTime int64 `datastore:"creation_time" json:"creation_time"` + ActiveOrg Org `json:"active_org" datastore:"active_org"` Active bool `datastore:"active" json:"active"` } @@ -1013,13 +1014,13 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) { user, err := handleApiAuthentication(resp, request) if err != nil { resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`)) + resp.Write([]byte(`{"success": false, "reason": "Can't handle set env auth"}`)) return } if user.Role != "admin" { resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`)) + resp.Write([]byte(`{"success": false, "reason": "Can't set environment without being admin"}`)) return } @@ -1097,7 +1098,7 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(`{"success": true}`)) } -func createNewUser(username, password, role, apikey string) error { +func createNewUser(username, password, role, apikey string, org Org) error { // Returns false if there is an issue // Use this for register err := checkPasswordStrength(password) @@ -1137,7 +1138,7 @@ func createNewUser(username, password, role, apikey string) error { newUser.Verified = false newUser.CreationTime = time.Now().Unix() newUser.Active = true - newUser.Orgs = []string{"default"} + newUser.Orgs = []string{org.Id} // FIXME - Remove this later if role == "admin" { @@ -1148,6 +1149,8 @@ func createNewUser(username, password, role, apikey string) error { newUser.Roles = []string{"user"} } + newUser.ActiveOrg = org + if len(apikey) > 0 { newUser.ApiKey = apikey } @@ -1182,25 +1185,25 @@ func createNewUser(username, password, role, apikey string) error { log.Printf("Error adding User %s: %s", username, err) return err } - url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String()) - const verifyMessage = ` -Registration URL :) - -%s - ` - addr := newUser.Username - - msg := &mail.Message{ - Sender: "Shuffle ", - To: []string{addr}, - Subject: "Verify your username - Shuffle", - Body: fmt.Sprintf(verifyMessage, url), - } - - log.Println(msg.Body) - if err := mail.Send(ctx, msg); err != nil { - log.Printf("Couldn't send email: %v", err) - } + // url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String()) + // const verifyMessage = ` + //Registration URL :) + // + //%s + // ` + // addr := newUser.Username + // + // msg := &mail.Message{ + // Sender: "Shuffle ", + // To: []string{addr}, + // Subject: "Verify your username - Shuffle", + // Body: fmt.Sprintf(verifyMessage, url), + // } + // + // log.Println(msg.Body) + // if err := mail.Send(ctx, msg); err != nil { + // log.Printf("Couldn't send email: %v", err) + // } err = increaseStatisticsField(ctx, "successful_register", username, 1) if err != nil { @@ -1249,7 +1252,8 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { if count == 0 { role = "admin" } - err = createNewUser(data.Username, data.Password, role, "") + + err = createNewUser(data.Username, data.Password, role, "", user.ActiveOrg) if err != nil { log.Printf("Failed registering user: %s", err) resp.WriteHeader(401) @@ -1623,6 +1627,10 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { // This is a long check to see if an inactive admin can access the site parsedAdmin := "false" + if userInfo.Role == "admin" { + parsedAdmin = "true" + } + if !userInfo.Active { if userInfo.Role == "admin" { parsedAdmin = "true" @@ -1692,14 +1700,55 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { Expires: expiration, }) - // Migrate this to real Org - // Need to create org endpoints (create, delete etc) - currentOrg := `{ - "name": "Shuffle", - "id": "123", - "role": "admin", - "cloud_sync": false - }` + // Updating user info if there's something wrong + if (len(userInfo.ActiveOrg.Name) == 0 || len(userInfo.ActiveOrg.Id) == 0) && len(userInfo.Orgs) > 0 { + _, err := getOrg(ctx, userInfo.Orgs[0]) + if err != nil { + var orgs []Org + q := datastore.NewQuery("Organizations") + _, err = dbclient.GetAll(ctx, q, &orgs) + if err == nil { + newStringOrgs := []string{} + newOrgs := []Org{} + for _, org := range orgs { + if strings.ToLower(org.Name) == strings.ToLower(userInfo.Orgs[0]) { + newOrgs = append(newOrgs, org) + newStringOrgs = append(newStringOrgs, org.Id) + } + } + + if len(newOrgs) > 0 { + userInfo.ActiveOrg = newOrgs[0] + userInfo.Orgs = newStringOrgs + + err = setUser(ctx, &userInfo) + if err != nil { + log.Printf("Error patching User for activeOrg: %s", err) + } else { + log.Printf("Updated the users' org") + } + } + } else { + log.Printf("Failed getting orgs for user. Major issue.: %s", err) + } + + } else { + // 1. Check if the org exists by ID + // 2. if it does, overwrite user + userInfo.ActiveOrg = Org{ + Id: userInfo.Orgs[0], + } + err = setUser(ctx, &userInfo) + if err != nil { + log.Printf("Error patching User for activeOrg: %s", err) + } + } + } + + currentOrg, err := json.Marshal(userInfo.ActiveOrg) + if err != nil { + currentOrg = []byte("{}") + } returnData := fmt.Sprintf(` { @@ -2175,6 +2224,61 @@ func handleGetEnvironments(resp http.ResponseWriter, request *http.Request) { resp.Write(newjson) } +func handleGetOrgs(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Role != "admin" { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Not admin"}`)) + return + } + + ctx := context.Background() + var orgs []Org + q := datastore.NewQuery("Organizations") + _, err = dbclient.GetAll(ctx, q, &orgs) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't get users"}`)) + return + } + + //newUsers := []User{} + //for _, item := range users { + // if len(item.Username) == 0 { + // continue + // } + + // item.Password = "" + // item.Session = "" + // item.VerificationToken = "" + + // newUsers = append(newUsers, item) + //} + + newjson, err := json.Marshal(orgs) + if err != nil { + log.Printf("Failed unmarshal: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) +} + func handleGetUsers(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -2392,6 +2496,28 @@ func getSession(ctx context.Context, thissession string) (*session, error) { return curUser, nil } +// ListBooks returns a list of books, ordered by title. +func getOrg(ctx context.Context, id string) (*Org, error) { + key := datastore.NameKey("Organizations", id, nil) + curOrg := &Org{} + if err := dbclient.Get(ctx, key, curOrg); err != nil { + return &Org{}, err + } + + return curOrg, nil +} + +func setOrg(ctx context.Context, data Org, id string) error { + // clear session_token and API_token for user + k := datastore.NameKey("Organizations", id, nil) + if _, err := dbclient.Put(ctx, k, &data); err != nil { + log.Println(err) + return err + } + + return nil +} + // ListBooks returns a list of books, ordered by title. func getUser(ctx context.Context, id string) (*User, error) { key := datastore.NameKey("Users", id, nil) @@ -4791,8 +4917,8 @@ func setTriggerAuth(ctx context.Context, trigger TriggerAuth) error { func getOutlookClient(ctx context.Context, code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) { conf := &oauth2.Config{ - ClientID: "70e37005-c954-4290-b573-d4b94e484336", - ClientSecret: ".eNw/A[kQFB5zL.agvRputdEJENeJ392", + ClientID: "", + ClientSecret: "", Scopes: []string{ "Mail.Read", "User.Read", @@ -6243,6 +6369,91 @@ func runInit(ctx context.Context) { } */ + setUsers := false + orgQuery := datastore.NewQuery("Organizations") + var activeOrgs []Org + _, err = dbclient.GetAll(ctx, orgQuery, &activeOrgs) + if err != nil { + log.Printf("Error getting organizations!") + } else { + // Add all users to it + if len(activeOrgs) == 1 { + setUsers = true + } + + log.Printf("Organizations exist!") + if len(activeOrgs) == 0 { + log.Printf(`No orgs. Setting org "default"`) + orgSetupName := "default" + orgId := uuid.NewV4().String() + newOrg := Org{ + Name: orgSetupName, + Id: orgId, + Org: orgSetupName, + Users: []User{}, + Roles: []string{"admin", "user"}, + CloudSync: false, + } + + err = setOrg(ctx, newOrg, orgId) + if err != nil { + log.Printf("Failed setting organization: %s", err) + } else { + log.Printf("Successfully created the default org!") + setUsers = true + } + } else { + log.Printf("There are %d org(s).", len(activeOrgs)) + } + } + + // Adding the users to the base organization since only one exists (default) + if setUsers && len(activeOrgs) > 0 { + activeOrg := activeOrgs[0] + + q := datastore.NewQuery("Users") + var users []User + _, err = dbclient.GetAll(ctx, q, &users) + if err == nil { + setOrgBool := false + for _, user := range users { + newUser := User{ + Username: user.Username, + Id: user.Id, + ActiveOrg: Org{ + Id: activeOrg.Id, + }, + Orgs: []string{activeOrg.Id}, + Role: user.Role, + } + + found := false + for _, orgUser := range activeOrg.Users { + if user.Id == orgUser.Id { + found = true + } + } + + if !found && len(user.Username) > 0 { + log.Printf("Adding user %s to org %s", user.Username, activeOrg.Name) + activeOrg.Users = append(activeOrg.Users, newUser) + setOrgBool = true + } + } + + if setOrgBool { + err = setOrg(ctx, activeOrg, activeOrg.Id) + if err != nil { + log.Printf("Failed setting org %s: %s!", activeOrg.Name, err) + } else { + log.Printf("UPDATED org %s!", activeOrg.Name) + } + } + } + + log.Printf("Should add %d users to organization default", len(users)) + } + // Fix active users etc q := datastore.NewQuery("Users").Filter("active =", true) var activeusers []User @@ -6253,6 +6464,7 @@ func runInit(ctx context.Context) { q := datastore.NewQuery("Users") var users []User _, err := dbclient.GetAll(ctx, q, &users) + if len(activeusers) == 0 && len(users) > 0 { log.Printf("No active users found - setting ALL to active") if err == nil { @@ -6268,7 +6480,12 @@ func runInit(ctx context.Context) { } if len(user.Orgs) == 0 { - user.Orgs = []string{"default"} + defaultName := "default" + user.Orgs = []string{defaultName} + user.ActiveOrg = Org{ + Name: defaultName, + Role: "user", + } } err = setUser(ctx, &user) @@ -6291,7 +6508,11 @@ func runInit(ctx context.Context) { log.Printf("SHUFFLE_DEFAULT_USERNAME and SHUFFLE_DEFAULT_PASSWORD not defined as environments. Running without default user.") } else { apikey := os.Getenv("SHUFFLE_DEFAULT_APIKEY") - err = createNewUser(username, password, "admin", apikey) + + tmpOrg := Org{ + Name: "default", + } + err = createNewUser(username, password, "admin", apikey, tmpOrg) if err != nil { log.Printf("Failed to create default user %s: %s", username, err) } else { @@ -6504,6 +6725,31 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { log.Printf("Apidata: %s", tmpData.Apikey) // FIXME: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit + client := &http.Client{} + syncPath := "http://192.168.3.6:5002/api/v1/cloud/sync" + req, err := http.NewRequest( + "POST", + syncPath, + nil, + ) + + newresp, err := client.Do(req) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync: %s"`, err))) + //setBadMemcache(ctx, docPath) + return + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't parse sync data"`))) + return + } + + log.Printf("Respbody: %s", string(respBody)) + resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) } @@ -6625,8 +6871,9 @@ func init() { r.HandleFunc("/api/v1/validate_openapi", validateSwagger).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS") + // NEW for 0.8.0 r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS") - //r.HandleFunc("/api/v1/execution_cleanup", cleanupExecutions).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS") http.Handle("/", r) } diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 590b016f..e1de6414 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -68,12 +68,13 @@ type ExecutionRequest struct { // Role is just used for feedback for a user type Org struct { - Name string `json:"name"` - Org string `json:"org"` - Users []User `json:"users"` - Id string `json:"id"` - Role string `json:"role"` - CloudSync bool `json:"cloud_sync"` + Name string `json:"name" datastore:"name"` + Id string `json:"id" datastore:"id"` + Org string `json:"org" datastore:"org"` + Users []User `json:"users" datastore:"users"` + Role string `json:"role" datastore:"role"` + Roles []string `json:"roles" datastore:"roles"` + CloudSync bool `json:"cloud_sync" datastore:"CloudSync"` } type AppAuthenticationStorage struct { diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 6c37c125..6902eb8f 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -101,7 +101,6 @@ const Header = props => { // Should be based on some path const logoCheck = !homePage ? null : null - // Handle top bar or something const loginTextBrowser = !isLoggedIn ?
@@ -184,17 +183,19 @@ const Header = props => { color="primary"> Settings - - - - - + {userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null : + + + + + + } {userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null :