diff --git a/.env b/.env index 2312df7d..ee13a292 100644 --- a/.env +++ b/.env @@ -4,8 +4,13 @@ ENVIRONMENT_NAME=Shuffle # Remote github config for first load APP_DOWNLOAD_LOCATION=https://github.com/frikky/shuffle-apps -APP_DOWNLOAD_AUTH_USERNAME="" -APP_DOWNLOAD_AUTH_PASSWORD="" +APP_DOWNLOAD_AUTH_USERNAME= +APP_DOWNLOAD_AUTH_PASSWORD= + +# User config for first load. Username & PW: min length 3 +SHUFFLE_DEFAULT_USERNAME= +SHUFFLE_DEFAULT_PASSWORD= +SHUFFLE_DEFAULT_APIKEY= # Local location of your app directory. Can't use ~/ APP_HOTLOAD_LOCATION=./shuffle-apps diff --git a/.gitignore b/.gitignore index e0d774db..5bebe49f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,3 @@ functions/generated_apps backend/onprem/app_sdk/apps *test.py -shuffle-apps/* diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 3f7a788f..89f1f0a7 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -173,7 +173,16 @@ class AppBase: if "split" in thistype: return data.split() if "len" in thistype or "length" in thistype: - return len(data) + tmp = "" + try: + tmp = json.loads(data) + except: + pass + + if isinstance(tmp, list): + return str(len(tmp)) + + return str(len(data)) if "parse" in thistype: splitvalues = [] default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)""" @@ -331,7 +340,7 @@ class AppBase: #print("WF Variables: %s" % execution_data["workflow"]["workflow_variables"]) for variable in execution_data["workflow"]["workflow_variables"]: variablename = variable["name"].replace(" ", "_", -1).lower() - + if variablename.lower() == actionname_lower: baseresult = variable["value"] break @@ -341,7 +350,7 @@ class AppBase: except TypeError as e: print("TypeError wf variables: %s" % e) pass - + print("BEFORE EXECUTION VAR") if len(baseresult) == 0: try: @@ -377,23 +386,44 @@ 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 ") + print("HANDLE RECURSIVE LOOP OF %s" % basejson) returnlist = [] - for innervalue in basejson: - #print("Value: %s" % value[parsersplit[cnt+1]]) - returnlist.append(innervalue[parsersplit[cnt+1]]) + 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: ${[]}$ - return "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist)) + 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: @@ -405,17 +435,19 @@ class AppBase: basejson = basejson[value] except KeyError as e: - return "KeyError: %s" % e - except IndexError as e: - return "IndexError: %s" % e - + print("Lower keyerror: %s" % e) + return "KeyError: Couldn't find key: %s" % e + return basejson - + # Parses parameters sent to it and returns whether it did it successfully with the values found def parse_params(action, fullexecution, parameter): # Skip if it starts with $? jsonparsevalue = "$." - match = ".*([$]{1}([a-zA-Z0-9# _-]+\.?){1,})" + + # Matches with space in the first part, but not in subsequent parts. + # JSON / yaml etc shouldn't have spaces in their fields anyway. + match = ".*?([$]{1}([a-zA-Z0-9 _-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,})" # Regex to find all the things if parameter["variant"] == "STATIC_VALUE": @@ -424,7 +456,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: - print("ACTUAL: %s", actualitem) + print("ACTUAL: ", actualitem) for replace in actualitem: try: to_be_replaced = replace[0] @@ -488,8 +520,10 @@ class AppBase: else: fullname += parameter["action_field"] + self.logger.info("PRE Fullname: %s" % fullname) + if parameter["value"].startswith(jsonparsevalue): - fullname += parameter["value"][2:] + fullname += parameter["value"][1:] #else: # fullname = "$%s" % parameter["action_field"] @@ -539,6 +573,22 @@ class AppBase: elif check.lower() == "contains": if destinationvalue.lower() in sourcevalue.lower(): return True + elif check.lower() == "larger than": + try: + if sourcevalue.isdigit() and destinationvalue.isdigit(): + if int(sourcevalue) > int(destinationvalue): + return True + except AttributeError as e: + self.logger.error("Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) + return False + elif check.lower() == "smaller than": + try: + if sourcevalue.isdigit() and destinationvalue.isdigit(): + if int(sourcevalue) < int(destinationvalue): + return True + except AttributeError as e: + self.logger.error("Condition smaller than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) + return False else: self.logger.info("Condition: can't handle %s yet. Setting to true" % check) @@ -574,24 +624,18 @@ class AppBase: # Parse all values first here sourcevalue = condition["source"]["value"] - if condition["source"]["variant"] == "" or condition["source"]["variant"]== "STATIC_VALUE": - condition["source"]["variant"]= "STATIC_VALUE" - else: - check, sourcevalue = parse_params(action, fullexecution, condition["source"]) - if check: - return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check) + check, sourcevalue = parse_params(action, fullexecution, condition["source"]) + if check: + return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check) #sourcevalue = sourcevalue.encode("utf-8") sourcevalue = parse_wrapper_start(sourcevalue) destinationvalue = condition["destination"]["value"] - if condition["destination"]["variant"]== "" or condition["destination"]["variant"]== "STATIC_VALUE": - condition["destination"]["variant"] = "STATIC_VALUE" - else: - check, destinationvalue = parse_params(action, fullexecution, condition["destination"]) - if check: - return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check) + check, destinationvalue = parse_params(action, fullexecution, condition["destination"]) + if check: + return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check) #destinationvalue = destinationvalue.encode("utf-8") destinationvalue = parse_wrapper_start(destinationvalue) @@ -634,7 +678,7 @@ class AppBase: if not branchcheck: self.logger.info("Failed one or more branch conditions.") action_result["result"] = tmpresult - action_result["status"] = "SKIPPED" + action_result["status"] = "FAILURE" try: ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result) self.logger.info("Result: %d" % ret.status_code) @@ -643,6 +687,7 @@ class AppBase: except requests.exceptions.ConnectionError as e: self.logger.exception(e) + print("\n\nRETURNING BECAUSE A BRANCH FAILED\n\n") return # Replace name cus there might be issues @@ -699,8 +744,10 @@ class AppBase: raise "Value check error: %s" % Exception(check) # Custom format for ${name[0,1,2,...]}$ + #submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})" submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})" actualitem = re.findall(submatch, value, re.MULTILINE) + print("Multicheck ", actualitem) if len(actualitem) > 0: multiexecution = True @@ -717,9 +764,12 @@ class AppBase: except IndexError: continue - itemlist = json.loads(actualitem) - if len(itemlist) > minlength: - minlength = len(itemlist) + try: + itemlist = json.loads(actualitem) + if len(itemlist) > minlength: + minlength = len(itemlist) + except json.decoder.JSONDecodeError as e: + print("JSON Error: %s in %s" % (e, actualitem)) replacements[to_be_replaced] = actualitem @@ -729,7 +779,13 @@ class AppBase: for i in range(0, minlength): tmpitem = json.loads(json.dumps(parameter["value"])) for key, value in replacements.items(): - replacement = json.loads(value)[i] + replacement = json.dumps(json.loads(value)[i]) + if replacement.startswith("\"") and replacement.endswith("\""): + replacement = replacement[1:len(replacement)-1] + #except json.decoder.JSONDecodeError as e: + + print("REPLACING %s with %s" % (key, replacement)) + #replacement = parse_wrapper_start(replacement) tmpitem = tmpitem.replace(key, replacement, -1) resultarray.append(tmpitem) @@ -738,7 +794,7 @@ class AppBase: multi_parameters[parameter["name"]] = resultarray else: # Parses things like int(value) - self.logger.info("Parsing wrapper data") + self.logger.info("Parsing wrapper data for %s" % value) value = parse_wrapper_start(value) params[parameter["name"]] = value @@ -758,8 +814,9 @@ class AppBase: except ValueError: result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres) print("Can't handle type %s value from function" % (type(newres))) + print("POST NEWRES: ", newres) else: - print("APP_SDK DONE: Starting multi execution with", multi_parameters) + print("APP_SDK DONE: Starting MULTI execution with", multi_parameters) # 1. Use number of executions based on longest array # 2. Find the right value from the parsed multi_params @@ -782,7 +839,8 @@ class AppBase: #print("Running with params %s" % baseparams) ret = await func(**baseparams) - print("Inner ret: %s" % ret) + ret = ret.replace("\"", "\\\"", -1) + print("Inner ret parsed: %s" % ret) try: results.append(json.loads(ret)) @@ -802,8 +860,7 @@ class AppBase: print("Normal result?") result = results - print("RESULT: %s" % result) - + print("RESULT: %s" % result) action_result["status"] = "SUCCESS" action_result["result"] = str(result) if action_result["result"] == "": @@ -812,10 +869,11 @@ class AppBase: self.logger.debug(f"Executed {action['label']}-{action['id']} with result: {result}") self.logger.debug(f"Data: %s" % action_result) except TypeError as e: + print("TypeError issue: %s" % e) action_result["status"] = "FAILURE" action_result["result"] = "TypeError: %s" % str(e) else: - print("Not callable?") + print("Function %s doesn't exist?" % action["name"]) self.logger.error(f"App {self.__class__.__name__}.{action['name']} is not callable") action_result["status"] = "FAILURE" action_result["result"] = "Function %s is not callable." % actionname diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 5326e38b..8ff15f1f 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -362,6 +362,7 @@ type HookAction struct { type Hook struct { Id string `json:"id" datastore:"id"` + Start string `json:"start" datastore:"start"` Info Info `json:"info" datastore:"info"` Actions []HookAction `json:"actions" datastore:"actions"` Type string `json:"type" datastore:"type"` @@ -812,8 +813,8 @@ func parseLoginParameters(resp http.ResponseWriter, request *http.Request) (logi // Removed for localhost func checkPasswordStrength(password string) error { // Check password strength here - if len(password) < 10 { - return errors.New("Minimum password length is 10.") + if len(password) < 3 { + return errors.New("Minimum password length is 3.") } //if len(password) > 128 { @@ -838,6 +839,84 @@ func checkPasswordStrength(password string) error { return nil } +func deleteUser(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in edit workflow: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Role != "admin" { + log.Printf("Wrong user (%s) when deleting - must be admin", user.Username) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Must be admin"}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + var userId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + userId = location[4] + } + + if userId == user.Id { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't deactivate yourself"}`)) + return + } + + ctx := context.Background() + q := datastore.NewQuery("Users").Filter("id =", userId) + var users []User + _, err := dbclient.GetAll(ctx, q, &users) + if err != nil { + log.Printf("Error getting users apikey (deleteuser): %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed getting users for verification"}`)) + return + } + + if len(users) != 1 { + log.Printf("Found too many users!") + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Backend error: too many or too few users with id %s: %d"}`, userId, len(users)))) + return + } + + // Invert. No user deletion. + if users[0].Active { + users[0].Active = false + } else { + users[0].Active = true + } + + err = setUser(ctx, &users[0]) + if err != nil { + log.Printf("Failed swapping active for user %s (%s)", users[0].Username, users[0].Id) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": true"}`))) + return + } + + log.Printf("Successfully inverted %s", users[0].Username) + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + // No more emails :) func checkUsername(Username string) error { // Stupid first check of email loool @@ -845,8 +924,8 @@ func checkUsername(Username string) error { // return errors.New("Invalid Username") //} - if len(Username) < 4 { - return errors.New("Minimum Username length is 4") + if len(Username) < 3 { + return errors.New("Minimum Username length is 3") } return nil @@ -995,6 +1074,119 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(`{"success": true}`)) } +func createNewUser(username, password, role, apikey string) error { + // Returns false if there is an issue + // Use this for register + err := checkPasswordStrength(password) + if err != nil { + log.Printf("Bad password strength: %s", err) + return err + } + + err = checkUsername(username) + if err != nil { + log.Printf("Bad Username strength: %s", err) + return err + } + + ctx := context.Background() + q := datastore.NewQuery("Users").Filter("Username =", username) + var users []User + _, err = dbclient.GetAll(ctx, q, &users) + if err != nil { + log.Printf("Failed getting user for registration: %s", err) + return err + } + + if len(users) > 0 { + return errors.New(fmt.Sprintf("Username %s already exists", username)) + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8) + if err != nil { + log.Printf("Wrong password for %s: %s", username, err) + return err + } + + newUser := new(User) + newUser.Username = username + newUser.Password = string(hashedPassword) + newUser.Verified = false + newUser.CreationTime = time.Now().Unix() + newUser.Active = true + newUser.Orgs = []string{"default"} + + // FIXME - Remove this later + if role == "admin" { + newUser.Role = "admin" + newUser.Roles = []string{"admin"} + } else { + newUser.Role = "user" + newUser.Roles = []string{"user"} + } + + if len(apikey) > 0 { + newUser.ApiKey = apikey + } + + // set limits + newUser.Limits.DailyApiUsage = 100 + newUser.Limits.DailyWorkflowExecutions = 1000 + newUser.Limits.DailyCloudExecutions = 100 + newUser.Limits.DailyTriggers = 20 + newUser.Limits.DailyMailUsage = 100 + newUser.Limits.MaxTriggers = 10 + newUser.Limits.MaxWorkflows = 10 + + // Set base info for the user + newUser.Executions.TotalApiUsage = 0 + newUser.Executions.TotalWorkflowExecutions = 0 + newUser.Executions.TotalAppExecutions = 0 + newUser.Executions.TotalCloudExecutions = 0 + newUser.Executions.TotalOnpremExecutions = 0 + newUser.Executions.DailyApiUsage = 0 + newUser.Executions.DailyWorkflowExecutions = 0 + newUser.Executions.DailyAppExecutions = 0 + newUser.Executions.DailyCloudExecutions = 0 + newUser.Executions.DailyOnpremExecutions = 0 + + verifyToken := uuid.NewV4() + ID := uuid.NewV4() + newUser.Id = ID.String() + newUser.VerificationToken = verifyToken.String() + err = setUser(ctx, newUser) + if err != nil { + 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) + } + + err = increaseStatisticsField(ctx, "successful_register", username, 1) + if err != nil { + log.Printf("Failed to increase total apps loaded stats: %s", err) + } + + return nil +} + func handleRegister(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -1030,128 +1222,21 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { return } - // Returns false if there is an issue - // Use this for register - err = checkPasswordStrength(data.Password) - if err != nil { - log.Printf("Bad password strength: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - err = checkUsername(data.Username) - if err != nil { - log.Printf("Bad Username strength: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - ctx := context.Background() - q := datastore.NewQuery("Users").Filter("Username =", data.Username) - var users []User - _, err = dbclient.GetAll(ctx, q, &users) - if err != nil { - log.Printf("Failed getting user for registration: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting username"}`))) - return - } - - if len(users) > 0 { - log.Printf("Username %s exists and can't register", data.Username) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) - return - } - - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(data.Password), 8) - if err != nil { - log.Printf("Wrong password for %s: %s", data.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) - return - } - - newUser := new(User) - newUser.Username = data.Username - newUser.Password = string(hashedPassword) - newUser.Verified = false - newUser.CreationTime = time.Now().Unix() - newUser.Active = true - newUser.Orgs = []string{"default"} - - // FIXME - Remove this later + role := "user" if count == 0 { - newUser.Role = "admin" - newUser.Roles = []string{"admin"} - } else { - newUser.Role = "user" - newUser.Roles = []string{"user"} + role = "admin" } - - // set limits - newUser.Limits.DailyApiUsage = 100 - newUser.Limits.DailyWorkflowExecutions = 1000 - newUser.Limits.DailyCloudExecutions = 100 - newUser.Limits.DailyTriggers = 20 - newUser.Limits.DailyMailUsage = 100 - newUser.Limits.MaxTriggers = 10 - newUser.Limits.MaxWorkflows = 10 - - // Set base info for the user - newUser.Executions.TotalApiUsage = 0 - newUser.Executions.TotalWorkflowExecutions = 0 - newUser.Executions.TotalAppExecutions = 0 - newUser.Executions.TotalCloudExecutions = 0 - newUser.Executions.TotalOnpremExecutions = 0 - newUser.Executions.DailyApiUsage = 0 - newUser.Executions.DailyWorkflowExecutions = 0 - newUser.Executions.DailyAppExecutions = 0 - newUser.Executions.DailyCloudExecutions = 0 - newUser.Executions.DailyOnpremExecutions = 0 - - addr := newUser.Username - - verifyToken := uuid.NewV4() - ID := uuid.NewV4() - newUser.Id = ID.String() - newUser.VerificationToken = verifyToken.String() - err = setUser(ctx, newUser) + err = createNewUser(data.Username, data.Password, role, "") if err != nil { - log.Printf("Error adding User %s: %s", data.Username, err) + log.Printf("Failed registering user: %s", err) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } - url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String()) - const verifyMessage = ` -Registration URL :) - -%s - ` - - 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) - } resp.WriteHeader(200) resp.Write([]byte(`{"success": true}`)) log.Printf("%s Successfully registered.", data.Username) - - err = increaseStatisticsField(ctx, "successful_register", data.Username, 1) - if err != nil { - log.Printf("Failed to increase total apps loaded stats: %s", err) - } } func handleCookie(request *http.Request) bool { @@ -1279,62 +1364,195 @@ func generateApikey(ctx context.Context, userInfo User) (User, error) { return userInfo, nil } -func handleApiGeneration(resp http.ResponseWriter, request *http.Request) { +func handleUpdateUser(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { return } - c, err := request.Cookie("session_token") + userInfo, err := handleApiAuthentication(resp, request) if err != nil { - log.Printf("User doesn't have sessiontoken, on apigen: %s", err) + log.Printf("Api authentication failed in apigen: %s", err) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + resp.Write([]byte(`{"success": false}`)) return } + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Missing field: user_id"}`))) + return + } + + type newUserStruct struct { + Role string `json:"role"` + Username string `json:"username"` + UserId string `json:"user_id"` + } + ctx := context.Background() - sessionToken := c.Value - session, err := getSession(ctx, sessionToken) + var t newUserStruct + err = json.Unmarshal(body, &t) if err != nil { - log.Printf("Session %#v doesn't exist (api gen): %s", session, err) + log.Printf("Failed unmarshaling userId: %s", err) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unmarshaling. Missing field: user_id"}`))) return } - // Get session first - // Should basically never happen - userInfo, err := getUser(ctx, session.Id) + if userInfo.Role != "admin" { + log.Printf("%s tried to update user %s", userInfo.Username, t.UserId) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You need to be admin to change other users"}`))) + return + } + + foundUser, err := getUser(ctx, t.UserId) if err != nil { - log.Printf("Username %s doesn't exist (apigen): %s", session.Username, err) + log.Printf("Can't find user %s (update user): %s", t.UserId, err) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) return } - // Delete old apikey from cache - //memcache.Delete(ctx, userInfo.ApiKey) - - if session.Session != userInfo.Session { - log.Printf("Session %s is not the latest. %s", session.Username, err) + if t.Role != "admin" && t.Role != "user" { + log.Printf("%s tried and failed to update user %s", userInfo.Username, t.UserId) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can only change to role user and admin"}`))) return + } else { + // Same user - can't edit yourself + if userInfo.Id == t.UserId { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't update the role of your own user"}`))) + return + } + + log.Printf("Updated user %s from %s to %s", foundUser.Username, foundUser.Role, t.Role) + foundUser.Role = t.Role + foundUser.Roles = []string{t.Role} } - newUserInfo, err := generateApikey(ctx, *userInfo) + if len(t.Username) > 0 { + q := datastore.NewQuery("Users").Filter("username =", t.Username) + var users []User + _, err = dbclient.GetAll(ctx, q, &users) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed getting users when updating user"}`)) + return + } + + found := false + for _, item := range users { + if item.Username == t.Username { + found = true + break + } + } + + if found { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "User with username %s already exists"}`, t.Username))) + return + } + + foundUser.Username = t.Username + } + + err = setUser(ctx, foundUser) if err != nil { - log.Printf("Failed to generate apikey for user %s: %s", session.Username, err) + log.Printf("Error patching user %s: %s", foundUser.Username, err) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) return } - userInfo = &newUserInfo - //memcache.Delete(request.Context(), sessionToken) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +func handleApiGeneration(resp http.ResponseWriter, request *http.Request) { + log.Printf("APIGEN!") + cors := handleCors(resp, request) + if cors { + return + } + + userInfo, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in apigen: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + log.Printf("APIKEY") + + ctx := context.Background() + if request.Method == "GET" { + newUserInfo, err := generateApikey(ctx, userInfo) + if err != nil { + log.Printf("Failed to generate apikey for user %s: %s", userInfo.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + userInfo = newUserInfo + log.Printf("Updated apikey for user %s", userInfo.Username) + } else if request.Method == "POST" { + log.Printf("Handling post!") + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Missing field: user_id"}`))) + return + } + + type userId struct { + UserId string `json:"user_id"` + } + + var t userId + err = json.Unmarshal(body, &t) + if err != nil { + log.Printf("Failed unmarshaling userId: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unmarshaling. Missing field: user_id"}`))) + return + } + + if userInfo.Role != "admin" { + log.Printf("%s tried and failed to change apikey for %s", userInfo.Username, t.UserId) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You need to be admin to change others' apikey"}`))) + return + } + + foundUser, err := getUser(ctx, t.UserId) + if err != nil { + log.Printf("Can't find user %s (apikey gen): %s", t.UserId, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + newUserInfo, err := generateApikey(ctx, *foundUser) + if err != nil { + log.Printf("Failed to generate apikey for user %s: %s", foundUser.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + foundUser = &newUserInfo + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, foundUser.Username, foundUser.Verified, foundUser.ApiKey))) + return + } - log.Printf("Updated apikey for user %s", userInfo.Username) resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, userInfo.Username, userInfo.Verified, userInfo.ApiKey))) } @@ -1345,44 +1563,16 @@ func handleSettings(resp http.ResponseWriter, request *http.Request) { return } - c, err := request.Cookie("session_token") + userInfo, err := handleApiAuthentication(resp, request) if err != nil { - log.Printf("User doesn't have sessiontoken, on getsettings: %s", err) + log.Printf("Api authentication failed in apigen: %s", err) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - ctx := context.Background() - sessionToken := c.Value - session, err := getSession(ctx, sessionToken) - if err != nil { - log.Printf("Session %#v doesn't exist (settings): %s", session, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) - return - } - - // Get session first - // Should basically never happen - UserInfo, err := getUser(ctx, session.Id) - if err != nil { - log.Printf("Username %s doesn't exist (settings): %s", session.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) - return - } - - //log.Printf("%s %s", session.Session, UserInfo.Session) - if session.Session != UserInfo.Session { - log.Printf("Session %s is not the latest. %s", session.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) + resp.Write([]byte(`{"success": false}`)) return } resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, UserInfo.Username, UserInfo.Verified, UserInfo.ApiKey))) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, userInfo.Username, userInfo.Verified, userInfo.ApiKey))) } func handleInfo(resp http.ResponseWriter, request *http.Request) { @@ -1391,21 +1581,16 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { return } - // Should compare with local storage first - c, err := request.Cookie("session_token") + userInfo, err := handleApiAuthentication(resp, request) if err != nil { + log.Printf("Api authentication failed in handleInfo: %s", err) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + resp.Write([]byte(`{"success": false}`)) return } - // FIXME - check memcache here - // Get the item from the memcache ctx := context.Background() - - sessionToken := c.Value - //log.Printf("Found session %s", sessionToken) - session, err := getSession(ctx, sessionToken) + session, err := getSession(ctx, userInfo.Session) if err != nil { log.Printf("Session %#v doesn't exist: %s", session, err) resp.WriteHeader(401) @@ -1413,17 +1598,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { return } - // Get session first - // Should basically never happen - userInfo, err := getUser(ctx, session.Id) - if err != nil { - log.Printf("Username %s doesn't exist (info): %s", session.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) - return - } - - // This is a long check to see if an inactive admin can access the portal + // This is a long check to see if an inactive admin can access the site if !userInfo.Active { if userInfo.Role == "admin" { ctx := context.Background() @@ -1478,7 +1653,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { //log.Printf("%s %s", session.Session, UserInfo.Session) if session.Session != userInfo.Session { - log.Printf("Session %s is not the latest. %s", session.Username, err) + log.Printf("Session %s is not the same as %s for %s. %s", userInfo.Session, session.Session, userInfo.Username, err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": ""}`)) return @@ -1765,7 +1940,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)) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found multiple users with the same username: %s"}`, t.Username))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s (%d)"}`, len(users), t.Username))) return } @@ -2059,7 +2234,8 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { } ctx := context.Background() - q := datastore.NewQuery("Users").Filter("Username =", strings.ToLower(data.Username)) + log.Printf("Username: %s", data.Username) + q := datastore.NewQuery("Users").Filter("Username =", data.Username) var users []User _, err = dbclient.GetAll(ctx, q, &users) if err != nil { @@ -2072,7 +2248,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)) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found multiple users with the same username: %s"}`, data.Username))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s"}`, len(users), data.Username))) return } @@ -2889,7 +3065,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { hookId = hookId[8:len(hookId)] - log.Printf("HookID: %s", hookId) + //log.Printf("HookID: %s", hookId) hook, err := getHook(ctx, hookId) if err != nil { log.Printf("Failed getting hook: %s", err) @@ -2898,31 +3074,60 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("HOOK FOUND: %#v", hook) + //log.Printf("HOOK FOUND: %#v", hook) // Execute the workflow //executeWorkflow(resp, request) //resp.WriteHeader(200) //resp.Write([]byte(`{"success": true}`)) if hook.Status == "stopped" { + log.Printf("Not running because hook status is stopped") resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Click start to start it"}`))) return } if len(hook.Workflows) == 0 { + log.Printf("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 } for _, item := range hook.Workflows { - log.Printf("Running for workflow: %s", item) + log.Printf("Running for workflow %s with startnode %s", item, hook.Start) workflow := Workflow{ ID: "", } - workflowExecution, executionResp, err := handleExecution(item, workflow, request) + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Body data error: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + parsedBody := string(body) + parsedBody = strings.Replace(parsedBody, "\"", "\\\"", -1) + if len(parsedBody) > 0 { + if string(parsedBody[0]) == `"` && string(parsedBody[len(parsedBody)-1]) == "\"" { + parsedBody = parsedBody[1 : len(parsedBody)-1] + } + } + + bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_source": "webhook", "execution_argument": "%s"}`, hook.Start, string(parsedBody)) + if len(hook.Start) == 0 { + log.Printf("No start node for hook %s - running with workflow default.", hook.Id) + bodyWrapper = string(parsedBody) + } + + newRequest := &http.Request{ + Method: "POST", + Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)), + } + + workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest) if err == nil { err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1) @@ -2961,6 +3166,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) { Id string `json:"id"` Name string `json:"name"` Workflow string `json:"workflow"` + Start string `json:"start"` } body, err := ioutil.ReadAll(request.Body) @@ -2971,7 +3177,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) { return } - log.Println("Data: %s", string(body)) + log.Printf("Data: %s", string(body)) ctx := context.Background() var requestdata requestData @@ -3021,6 +3227,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) { hook := Hook{ Id: newId, + Start: requestdata.Start, Workflows: []string{requestdata.Workflow}, Info: Info{ Name: requestdata.Name, @@ -3041,33 +3248,6 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) { Running: false, } - log.Printf("Hello") - - // FIXME: Add cloud function execution? - //b, err := json.Marshal(hook) - //if err != nil { - // log.Printf("Failed marshalling: %s", err) - // resp.WriteHeader(401) - // resp.Write([]byte(`{"success": false}`)) - // return - //} - - //environmentVariables := map[string]string{ - // "FUNCTION_APIKEY": user.ApiKey, - // "CALLBACKURL": "https://shuffler.io", - // "HOOKID": hook.Id, - //} - - //applocation := fmt.Sprintf("gs://%s/triggers/webhook.zip", bucketName) - //hookname := fmt.Sprintf("webhook_%s", hook.Id) - //err = deployWebhookFunction(ctx, hookname, defaultLocation, applocation, environmentVariables) - //if err != nil { - // log.Printf("Error deploying hook: %s", err) - // resp.WriteHeader(401) - // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Issue with starting hook. Please wait a second and try again"}`))) - // return - //} - hook.Status = "running" hook.Running = true err = setHook(ctx, hook) @@ -3083,7 +3263,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) { log.Printf("Failed to increase total workflows: %s", err) } - log.Println("Generating new hook") + log.Println("Set up a new hook") resp.WriteHeader(200) resp.Write([]byte(`{"success": true}`)) } @@ -5966,18 +6146,17 @@ func runInit(ctx context.Context) { } // Fix active users etc - log.Printf("Reformatting users") q := datastore.NewQuery("Users").Filter("active =", true) - var users []User - _, err = dbclient.GetAll(ctx, q, &users) + var activeusers []User + _, err = dbclient.GetAll(ctx, q, &activeusers) if err != nil { - log.Printf("Error getting users apikey (runinit): %s", err) + log.Printf("Error getting users during init: %s", err) } else { - if len(users) == 0 { + 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") - q := datastore.NewQuery("Users") - var users []User - _, err := dbclient.GetAll(ctx, q, &users) if err == nil { for _, user := range users { user.Active = true @@ -6006,6 +6185,24 @@ func runInit(ctx context.Context) { } } } + } else if len(users) == 0 { + log.Printf("Trying to set up user based on environments SHUFFLE_DEFAULT_USERNAME & SHUFFLE_DEFAULT_PASSWORD") + username := os.Getenv("SHUFFLE_DEFAULT_USERNAME") + password := os.Getenv("SHUFFLE_DEFAULT_PASSWORD") + if len(username) == 0 || len(password) == 0 { + 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) + if err != nil { + log.Printf("Failed to create default user %s: %s", username, err) + } else { + log.Printf("Successfully created user %s", username) + } + } + } else { + //log.Printf("Found %d users.", len(users)) + //log.Printf(users[0].Username) } } @@ -6119,7 +6316,7 @@ func runInit(ctx context.Context) { } _, err = git.Clone(storer, fs, cloneOptions) if err != nil { - log.Printf("Failed loading repo %s into memory: %s", err) + log.Printf("Failed loading repo %s into memory: %s", apis, err) } else { log.Printf("Finished git clone. Looking for updates to the repo.") dir, err := fs.ReadDir("") @@ -6157,6 +6354,7 @@ func init() { 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") r.HandleFunc("/api/v1/users/logout", handleLogout).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/users/register", handleRegister).Methods("POST", "OPTIONS") @@ -6164,8 +6362,10 @@ func init() { r.HandleFunc("/api/v1/users/getusers", handleGetUsers).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/getsettings", handleSettings).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/users/generateapikey", handleApiGeneration).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/users/updateuser", handleUpdateUser).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/users/{user}", deleteUser).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/users/passwordchange", handlePasswordChange).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/users", handleGetUsers).Methods("GET", "OPTIONS") // General - duplicates and old. r.HandleFunc("/api/v1/login", handleLogin).Methods("POST", "OPTIONS") @@ -6175,7 +6375,7 @@ func init() { r.HandleFunc("/api/v1/getusers", handleGetUsers).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/getinfo", handleInfo).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/getsettings", handleSettings).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/generateapikey", handleApiGeneration).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/generateapikey", handleApiGeneration).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/getenvironments", handleGetEnvironments).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/setenvironments", handleSetEnvironments).Methods("PUT", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 36ab5d2a..36b78bd4 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -61,6 +61,7 @@ var shuffleTestPath = "./shuffle-test-258209-5a2e8d7e508a.json" type ExecutionRequest struct { ExecutionId string `json:"execution_id"` ExecutionArgument string `json:"execution_argument"` + ExecutionSource string `json:"execution_source"` WorkflowId string `json:"workflow_id"` Environments []string `json:"environments"` Authorization string `json:"authorization"` @@ -153,6 +154,7 @@ type WorkflowExecution struct { Start string `json:"start" datastore:"start"` ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"` ExecutionId string `json:"execution_id" datastore:"execution_id"` + ExecutionSource string `json:"execution_source" datastore:"execution_source"` WorkflowId string `json:"workflow_id" datastore:"workflow_id"` LastNode string `json:"last_node" datastore:"last_node"` Authorization string `json:"authorization" datastore:"authorization"` @@ -449,9 +451,9 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode // FIXME: // This may run multiple places if multiple servers, // but that's a future problem - log.Printf("BODY: %s", string(body)) + //log.Printf("BODY: %s", string(body)) parsedArgument := strings.Replace(string(body), "\"", "\\\"", -1) - bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_argument": "%s"}`, startNode, parsedArgument) + bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_source": "schedule", "execution_argument": "%s"}`, startNode, parsedArgument) log.Printf("WRAPPER BODY: \n%s", bodyWrapper) job := func() { request := &http.Request{ @@ -1205,6 +1207,13 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { // Adds the Testing app if it's a new workflow workflowapps, err := getAllWorkflowApps(ctx) if err == nil { + // FIXME: Add real env + //q := datastore.NewQuery("Environments").Limit(1) + //count, err := dbclient.Get(ctx, q) + //envName := "Shuffle" + //if err == nil { + //} + for _, item := range workflowapps { if item.Name == "Testing" && item.AppVersion == "1.0.0" { nodeId := "40447f30-fa44-4a4f-a133-4ee710368737" @@ -1241,7 +1250,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.Actions = newActions workflow.IsValid = true - workflow.Configuration.ExitOnError = true + workflow.Configuration.ExitOnError = false workflowjson, err := json.Marshal(workflow) if err != nil { @@ -1564,6 +1573,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { // doesn't check sharing=true // Have to do it like this to add the user's apps log.Println("Apps set starting") + //log.Printf("EXIT ON ERROR: %#v", workflow.Configuration.ExitOnError) workflowApps := []WorkflowApp{} //memcacheName = "all_apps" //if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss { @@ -1627,10 +1637,10 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { } // Has to NOT be generated - //if app.Name == action.AppName && app.AppVersion == action.AppVersion { - // curapp = app - // break - //} + if app.Name == action.AppName && app.AppVersion == action.AppVersion { + curapp = app + break + } } // Check to see if the whole app is valid @@ -1976,7 +1986,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf var execution ExecutionRequest err = json.Unmarshal(body, &execution) if err != nil { - //log.Printf("Failed execution POST unmarshaling - still continue: %s", err) + log.Printf("Failed execution POST unmarshaling - continuing anyway: %s", err) //return WorkflowExecution{}, "", err } @@ -1985,10 +1995,15 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf } // FIXME - this should have "execution_argument" from executeWorkflow frontend + //log.Printf("EXEC: %#v", execution) if len(execution.ExecutionArgument) > 0 { workflowExecution.ExecutionArgument = execution.ExecutionArgument } + if len(execution.ExecutionSource) > 0 { + workflowExecution.ExecutionSource = execution.ExecutionSource + } + //log.Printf("Execution data: %#v", execution) if len(execution.Start) == 36 { log.Printf("SHOULD START ON NODE %s", execution.Start) @@ -2007,7 +2022,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf } } else if len(execution.Start) > 0 { - log.Printf("START ACTION %s IS WRONG ID LENGTH %d!", len(execution.Start)) + log.Printf("START ACTION %s IS WRONG ID LENGTH %d!", execution.Start, len(execution.Start)) return WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", execution.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", execution.Start)) } @@ -2136,6 +2151,13 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf workflowExecution.Status = "EXECUTING" } + if len(workflowExecution.ExecutionSource) == 0 { + log.Printf("No execution source specified. Setting to default") + workflowExecution.ExecutionSource = "default" + } else { + log.Printf("Execution source is %s for execution ID %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId) + } + workflowExecution.ExecutionVariables = workflow.ExecutionVariables // Local authorization for this single workflow used in workers. @@ -2158,11 +2180,15 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf childNodes := findChildNodes(workflowExecution, workflowExecution.Start) topic := "workflows" + startFound := false // FIXME - remove this? newActions := []Action{} defaultResults := []ActionResult{} for _, action := range workflowExecution.Workflow.Actions { action.LargeImage = "" + if action.ID == workflowExecution.Start { + startFound = true + } //log.Println(action.Environment) if action.Environment == "" { @@ -2202,6 +2228,11 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf } } + if !startFound { + log.Printf("Startnode %s doesn't exist!", workflowExecution.Start) + return WorkflowExecution{}, fmt.Sprintf("Workflow action %s doesn't exist in workflow", workflowExecution.Start), errors.New(fmt.Sprintf("Workflow start node %s doesn't exist. Exiting!", workflowExecution.Start)) + } + // Verification for execution environments workflowExecution.Results = defaultResults workflowExecution.Workflow.Actions = newActions @@ -2929,84 +2960,6 @@ func setWorkflow(ctx context.Context, workflow Workflow, id string) error { return nil } -func deleteUser(resp http.ResponseWriter, request *http.Request) { - cors := handleCors(resp, request) - if cors { - return - } - - user, userErr := handleApiAuthentication(resp, request) - if userErr != nil { - log.Printf("Api authentication failed in edit workflow: %s", userErr) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - if user.Role != "admin" { - log.Printf("Wrong user (%s) when deleting - must be admin", user.Username) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Must be admin"}`)) - return - } - - location := strings.Split(request.URL.String(), "/") - var userId string - if location[1] == "api" { - if len(location) <= 4 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - userId = location[4] - } - - if userId == user.Id { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Can't deactivate yourself"}`)) - return - } - - ctx := context.Background() - q := datastore.NewQuery("Users").Filter("id =", userId) - var users []User - _, err := dbclient.GetAll(ctx, q, &users) - if err != nil { - log.Printf("Error getting users apikey (deleteuser): %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Failed getting users for verification"}`)) - return - } - - if len(users) != 1 { - log.Printf("Found too many users!") - resp.WriteHeader(500) - resp.Write([]byte(`{"success": false, "reason": "Backend error: too many users"}`)) - return - } - - // Invert. No user deletion. - if users[0].Active { - users[0].Active = false - } else { - users[0].Active = true - } - - err = setUser(ctx, &users[0]) - if err != nil { - log.Printf("Failed swapping active for user %s (%s)", users[0].Username, users[0].Id) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": true"}`))) - return - } - - log.Printf("Successfully inverted %s", users[0].Username) - - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) -} - func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { diff --git a/backend/tests/forparser.py b/backend/tests/forparser.py index 2b9d7100..1b898c6d 100644 --- a/backend/tests/forparser.py +++ b/backend/tests/forparser.py @@ -1,9 +1,7 @@ import re import json -fullexecution = {'type': 'workflow', 'status': 'EXECUTING', 'start': '', 'execution_argument': '', 'execution_id': 'a0a1c723-772e-4569-867e-0228ced98ace', 'workflow_id': '74ab469b-5323-41b9-96c8-4214f49185d2', 'last_node': '', 'authorization': '05205468-6b13-485b-9716-6859e0a5a2a6', 'result': '', 'started_at': 1589450462, 'completed_at': 0, 'project_id': 'shuffle', 'locations': ['europe-west2'], 'workflow': {'actions': [{'app_name': 'testing', 'app_version': '1.0.0', 'app_id': 'ab9549af-d6d5-470a-ae64-1fbe50783f05', 'errors': None, 'id': '6e74deef-4f3b-4ab9-97b1-9370e2040a6f', 'is_valid': True, 'isStartNode': True, 'sharing': True, 'private_id': '', 'label': 'testing_1', 'small_image': '', 'large_image': '', 'environment': 'Shuffle', 'name': 'repeat_back_to_me', 'parameters': [{'description': 'message to repeat', 'id': '', 'name': 'call', 'example': '', 'value': '', 'multiline': False, 'action_field': 'Execution Argument', 'variant': 'ACTION_RESULT', 'required': True, 'schema': {'type': 'string'}}], 'position': {'x': 598, 'y': 559}, 'priority': 0}, {'app_name': 'testing', 'app_version': '1.0.0', 'app_id': 'dd1bd74b-bf84-48a2-99c6-8bca8d7c9ce6', 'errors': None, 'id': '3de7817c-aeda-432f-b780-69f5549c01c7', 'is_valid': True, 'isStartNode': False, 'sharing': True, 'private_id': '', 'label': 'testing_2', 'small_image': '', 'large_image': '', 'environment': 'Shuffle', 'name': 'repeat_back_to_me', 'parameters': [{'description': 'message to repeat', 'id': '', 'name': 'call', 'example': '', 'value': '{"data": "$testing2"}', 'multiline': False, 'action_field': 'testing_2', 'variant': 'STATIC_VALUE', 'required': True, 'schema': {'type': 'string'}}], 'position': {'x': 384.125, 'y': 617.625}, 'priority': 0}, {'app_name': 'testing', 'app_version': '1.0.0', 'app_id': 'dd1bd74b-bf84-48a2-99c6-8bca8d7c9ce6', 'errors': None, 'id': '40066e27-7a9f-4bb6-89cf-cea7575694ba', 'is_valid': True, 'isStartNode': False, 'sharing': True, 'private_id': '', 'label': 'testing_3', 'small_image': '', 'large_image': '', 'environment': 'Shuffle', 'name': 'repeat_back_to_me', 'parameters': [{'description': 'message to repeat', 'id': '', 'name': 'call', 'example': '', 'value': '{"data": "$testing2"}', 'multiline': False, 'action_field': 'testing_2', 'variant': 'STATIC_VALUE', 'required': True, 'schema': {'type': 'string'}}], 'position': {'x': 166.125, 'y': 561.125}, 'priority': 0}], 'branches': [{'destination_id': '6e74deef-4f3b-4ab9-97b1-9370e2040a6f', 'id': '48bbbe8e-e49a-48db-a5ad-16c21e52dd95', 'source_id': '43e4667a-e3c5-472e-8748-80cbd4984ea8', 'label': '', 'has_errors': False, 'conditions': None}, {'destination_id': '3de7817c-aeda-432f-b780-69f5549c01c7', 'id': '8a78af67-bea8-48d1-a52d-84af5fb77d32', 'source_id': '6e74deef-4f3b-4ab9-97b1-9370e2040a6f', 'label': '', 'has_errors': False, 'conditions': None}, {'destination_id': '40066e27-7a9f-4bb6-89cf-cea7575694ba', 'id': 'ddca1983-c591-44ff-932b-617ac419ac6b', 'source_id': '3de7817c-aeda-432f-b780-69f5549c01c7', 'label': '', 'has_errors': False, 'conditions': None}], 'triggers': [{'app_name': 'Webhook', 'description': 'Simple HTTP webhook', 'long_description': 'Execute a workflow with an unauthicated POST request', 'status': 'running', 'app_version': '1.0.0', 'errors': None, 'id': '43e4667a-e3c5-472e-8748-80cbd4984ea8', 'is_valid': True, 'isStartNode': False, 'label': 'Webhook', 'small_image': '', 'large_image': 'data:image/png;base64,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', 'environment': 'Shuffle', 'trigger_type': 'WEBHOOK', 'name': 'Webhook', 'parameters': [{'description': '', 'id': '', 'name': 'url', 'example': '', 'value': 'http://192.168.3.6:5001/api/v1/hooks/webhook_43e4667a-e3c5-472e-8748-80cbd4984ea8', 'multiline': False, 'action_field': '', 'variant': '', 'required': False, 'schema': {'type': ''}}, {'description': '', 'id': '', 'name': 'tmp', 'example': '', 'value': 'webhook_43e4667a-e3c5-472e-8748-80cbd4984ea8', 'multiline': False, 'action_field': '', 'variant': '', 'required': False, 'schema': {'type': ''}}], 'position': {'x': 220.75, 'y': 375.75}, 'priority': 0}], 'schedules': None, 'id': '74ab469b-5323-41b9-96c8-4214f49185d2', 'is_valid': True, 'name': 'Webhook', 'description': '', 'start': '6e74deef-4f3b-4ab9-97b1-9370e2040a6f', 'owner': '16c6e4ff-58c3-49a2-a064-8abf0c37a108', 'sharing': 'private', 'execution_org': {'name': '', 'org': '', 'users': None, 'id': ''}, 'workflow_variables': None}, 'results': [{'action': {'app_name': 'testing', 'app_version': '1.0.0', 'app_id': '', 'errors': None, 'id': '6e74deef-4f3b-4ab9-97b1-9370e2040a6f', 'is_valid': True, 'isStartNode': True, 'sharing': False, 'private_id': '', 'label': 'testing_1', 'small_image': '', 'large_image': '', 'environment': 'Shuffle', 'name': 'repeat_back_to_me', 'parameters': [{'description': 'message to repeat', 'id': '', 'name': 'call', 'example': '', 'value': '', 'multiline': False, 'action_field': 'Execution Argument', 'variant': 'ACTION_RESULT', 'required': True, 'schema': {'type': 'string'}}], 'position': {'x': 598, 'y': 559}, 'priority': 0}, 'execution_id': 'a0a1c723-772e-4569-867e-0228ced98ace', 'authorization': '05205468-6b13-485b-9716-6859e0a5a2a6', 'result': '[ { "severity": 2, "owner": "frikky", "_routing": "6UhYGXIBGUwc2QpmoXZI", "flag": false, "customFields": {}, "_type": "case", "description": "This is a desc", "title": "Aaaaand another case here :)", "tags": [], "createdAt": 1589563137969, "_parent": null, "createdBy": "frikky", "caseId": 6, "tlp": 2, "metrics": {}, "_id": "6UhYGXIBGUwc2QpmoXZI", "id": "6UhYGXIBGUwc2QpmoXZI", "_version": 1, "pap": 2, "startDate": 1589563080000, "status": "Open" }, { "severity": 2, "owner": "frikky", "_routing": "60heGXIBGUwc2QpmgXbc", "flag": false, "customFields": {}, "_type": "case", "description": ":)", "title": "The last challenge", "tags": [], "createdAt": 1589563522665, "_parent": null, "createdBy": "frikky", "caseId": 7, "tlp": 2, "metrics": {}, "_id": "60heGXIBGUwc2QpmgXbc", "id": "60heGXIBGUwc2QpmgXbc", "_version": 1, "pap": 2, "startDate": 1589563500000, "status": "Open" }, { "severity": 2, "owner": "frikky", "_routing": "5UhLGXIBGUwc2Qpmj3YR", "flag": false, "customFields": {}, "_type": "case", "description": "Description", "title": "This is a cool title for a case, right?", "tags": [], "createdAt": 1589562281320, "_parent": null, "createdBy": "frikky", "caseId": 4, "tlp": 2, "metrics": {}, "_id": "5UhLGXIBGUwc2Qpmj3YR", "id": "5UhLGXIBGUwc2Qpmj3YR", "_version": 1, "pap": 2, "startDate": 1589562240000, "status": "Open" }, { "severity": 2, "owner": "frikky", "_routing": "50hXGXIBGUwc2QpmDnZC", "flag": false, "customFields": {}, "_type": "case", "description": "Case desc", "title": "Another case!", "tags": [], "createdAt": 1589563034722, "_parent": null, "createdBy": "frikky", "caseId": 5, "tlp": 2, "metrics": {}, "_id": "50hXGXIBGUwc2QpmDnZC", "id": "50hXGXIBGUwc2QpmDnZC", "_version": 1, "pap": 2, "startDate": 1589563020000, "status": "Open" }, { "severity": 2, "owner": "frikky", "_routing": "4UgXGXIBGUwc2QpmBXa3", "flag": false, "customFields": {}, "_type": "case", "description": "This is a webhook test", "title": "TheHive webhook test", "tags": [ "Integration", "Shuffle", "Test", "TheHive" ], "createdAt": 1589558837830, "_parent": null, "createdBy": "frikky", "caseId": 2, "tlp": 0, "metrics": {}, "_id": "4UgXGXIBGUwc2QpmBXa3", "id": "4UgXGXIBGUwc2QpmBXa3", "_version": 1, "pap": 2, "startDate": 1589558820000, "status": "Open" }, { "severity": 2, "owner": "frikky", "_routing": "40g4GXIBGUwc2Qpmy3Zn", "flag": false, "customFields": {}, "_type": "case", "description": "Case for testing webhook 2", "title": "My new case testing webhook", "tags": [], "createdAt": 1589561051232, "_parent": null, "createdBy": "frikky", "caseId": 3, "tlp": 2, "metrics": {}, "_id": "40g4GXIBGUwc2Qpmy3Zn", "id": "40g4GXIBGUwc2Qpmy3Zn", "_version": 1, "pap": 2, "startDate": 1589561040000, "status": "Open" }, { "severity": 2, "owner": "frikky", "_routing": "30gKGXIBGUwc2Qpm1nZs", "flag": false, "customFields": {}, "_type": "case", "description": "asd", "title": "testing", "tags": [], "createdAt": 1589558039989, "_parent": null, "createdBy": "frikky", "caseId": 1, "tlp": 2, "metrics": {}, "_id": "30gKGXIBGUwc2Qpm1nZs", "id": "30gKGXIBGUwc2Qpm1nZs", "_version": 1, "pap": 2, "startDate": 1589557980000, "status": "Open" } ]', 'started_at': 1589450468, 'completed_at': 1589450468, 'status': 'SUCCESS'}, {'action': {'app_name': 'testing', 'app_version': '1.0.0', 'app_id': '', 'errors': None, 'id': '3de7817c-aeda-432f-b780-69f5549c01c7', 'is_valid': True, 'isStartNode': False, 'sharing': False, 'private_id': '', 'label': 'testing_2', 'small_image': '', 'large_image': '', 'environment': 'Shuffle', 'name': 'repeat_back_to_me', 'parameters': [{'description': 'message to repeat', 'id': '', 'name': 'call', 'example': '', 'value': '{"data": "$testing2"}', 'multiline': False, 'action_field': 'testing_2', 'variant': 'STATIC_VALUE', 'required': True, 'schema': {'type': 'string'}}], 'position': {'x': 384.125, 'y': 617.625}, 'priority': 0}, 'execution_id': 'a0a1c723-772e-4569-867e-0228ced98ace', 'authorization': '05205468-6b13-485b-9716-6859e0a5a2a6', 'result': '{"data": "$testing2"}', 'started_at': 1589450479, 'completed_at': 1589450479, 'status': 'SUCCESS'}, {'action': {'app_name': 'testing', 'app_version': '1.0.0', 'app_id': '', 'errors': None, 'id': '40066e27-7a9f-4bb6-89cf-cea7575694ba', 'is_valid': True, 'isStartNode': False, 'sharing': False, 'private_id': '', 'label': 'testing_3', 'small_image': '', 'large_image': '', 'environment': 'Shuffle', 'name': 'repeat_back_to_me', 'parameters': [{'description': 'message to repeat', 'id': '', 'name': 'call', 'example': '', 'value': '{"data": "$testing2"}', 'multiline': False, 'action_field': 'testing_2', 'variant': 'STATIC_VALUE', 'required': True, 'schema': {'type': 'string'}}], 'position': {'x': 166.125, 'y': 561.125}, 'priority': 0}, 'execution_id': 'a0a1c723-772e-4569-867e-0228ced98ace', 'authorization': '05205468-6b13-485b-9716-6859e0a5a2a6', 'result': '', 'started_at': 1589450489, 'completed_at': 0, 'status': 'EXECUTING'}]} - - +fullexecution = {"type":"workflow","status":"FINISHED","start":"40447f30-fa44-4a4f-a133-4ee710368737","execution_argument":"","execution_id":"083eaa87-17ff-4aba-996c-83245051cf3d","execution_source":"default","workflow_id":"d7b73e8a-08fe-460e-987b-971cb6f1857f","last_node":"40447f30-fa44-4a4f-a133-4ee710368737","authorization":"b1264cd6-ed7a-4839-8caf-187eee8804b9","result":"TypeError: list indices must be integers or slices, not str","started_at":1593236788,"completed_at":1593236790,"project_id":"shuffle","locations":["europe-west2"],"workflow":{"actions":[{"app_name":"Testing","app_version":"1.0.0","app_id":"5411f573-9bba-44c4-a8d3-0e2bb704546d","errors":"null","id":"40447f30-fa44-4a4f-a133-4ee710368737","is_valid":True,"isStartNode":True,"sharing":True,"private_id":"","label":"Hello this is a name","small_image":"","large_image":"","environment":"Shuffle","name":"repeat_back_to_me","parameters":[{"description":"The message to repeat","id":"","name":"call","example":"REPEATING: Hello world","value":"$this is a test.name is not the same as $this is a test.name2 \n\n\nNot list $this is a test.loop.# either","multiline":True,"action_field":"","variant":"STATIC_VALUE","required":True,"schema":{"type":"string"}}],"execution_variable":{"description":"","id":"","name":"","value":""},"position":{"x":360.5,"y":454.5},"priority":0}],"branches":"null","triggers":"null","schedules":"null","configuration":{"exit_on_error":False,"start_from_top":False},"id":"d7b73e8a-08fe-460e-987b-971cb6f1857f","is_valid":True,"name":"App sdk parser testing","description":"","start":"40447f30-fa44-4a4f-a133-4ee710368737","owner":"43c36230-0a6e-40fc-aebc-a8ef57c81a88","sharing":"private","execution_org":{"name":"","org":"","users":"null","id":""},"workflow_variables":[{"description":"","id":"a034abee-5a5f-4347-9e58-6d2e58ce70f2","name":"This is a test","value":"[{\"data\": \"1.1.1.1\", \"data_type\": \"ip\"}]"}]},"results":[{"action":{"app_name":"Testing","app_version":"1.0.0","app_id":"5411f573-9bba-44c4-a8d3-0e2bb704546d","errors":"null","id":"40447f30-fa44-4a4f-a133-4ee710368737","is_valid":True,"isStartNode":True,"sharing":True,"private_id":"","label":"Hello this is a name","small_image":"","large_image":"","environment":"Shuffle","name":"repeat_back_to_me","parameters":[{"description":"The message to repeat","id":"","name":"call","example":"","value":"testing is not the same as testing2 \n\n\nNot list $this is a test.loop.# either","multiline":"false","action_field":"","variant":"STATIC_VALUE","required":True,"schema":{"type":"string"}}],"execution_variable":{"description":"","id":"","name":"","value":""},"position":{"x":360.5,"y":454.5},"priority":0},"execution_id":"083eaa87-17ff-4aba-996c-83245051cf3d","authorization":"b1264cd6-ed7a-4839-8caf-187eee8804b9","result":"TypeError: list indices must be integers or slices, not str","started_at":1593236790,"completed_at":1593236790,"status":"FAILURE"}]} # Takes a workflow execution as argument @@ -18,18 +16,52 @@ def get_json_value(execution_data, input_data): # 1. Find the action baseresult = "" + actionname_lower = actionname.lower() try: - if actionname.lower() == "exec": + if actionname_lower == "exec": baseresult = execution_data["execution_argument"] else: for result in execution_data["results"]: resultlabel = result["action"]["label"].replace(" ", "_", -1).lower() - if resultlabel.lower() == actionname.lower(): + if resultlabel.lower() == actionname_lower: baseresult = result["result"] break + + print("BEFORE VARIABLES!") + if len(baseresult) == 0: + try: + #print("WF Variables: %s" % execution_data["workflow"]["workflow_variables"]) + for variable in execution_data["workflow"]["workflow_variables"]: + variablename = variable["name"].replace(" ", "_", -1).lower() + + if variablename.lower() == actionname_lower: + baseresult = variable["value"] + break + except KeyError as e: + print("KeyError wf variables: %s" % e) + pass + except TypeError as e: + print("TypeError wf variables: %s" % e) + pass + + print("BEFORE EXECUTION VAR") + if len(baseresult) == 0: + try: + #print("Execution Variables: %s" % execution_data["execution_variables"]) + for variable in execution_data["execution_variables"]: + variablename = variable["name"].replace(" ", "_", -1).lower() + if variablename.lower() == actionname_lower: + baseresult = variable["value"] + break + except KeyError as e: + print("KeyError exec variables: %s" % e) + pass + except TypeError as e: + print("TypeError exec variables: %s" % e) + pass except KeyError as error: - print(f"Error: {error}") + print(f"KeyError in JSON: {error}") print(f"After first trycatch") @@ -47,23 +79,44 @@ def get_json_value(execution_data, input_data): 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 ") + print("HANDLE RECURSIVE LOOP OF %s" % basejson) returnlist = [] - for innervalue in basejson: - #print("Value: %s" % value[parsersplit[cnt+1]]) - returnlist.append(innervalue[parsersplit[cnt+1]]) + 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: ${[]}$ - return "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist)) + 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: @@ -75,25 +128,20 @@ def get_json_value(execution_data, input_data): basejson = basejson[value] except KeyError as e: - return "KeyError: %s" % e - except IndexError as e: - return "IndexError: %s" % e + print("Lower keyerror: %s" % e) + return "KeyError: Couldn't find key: %s" % e return basejson parameter = { "value": """{ - "data": "$exec.test", - "lul": "helo", - "data2": "asd", - "data3": "helo2j", - "data5": "$testing_1.#.title", - "data7": "$testing 1.#.id" + "data8": "Not list $this is a test.#.data either with the items $this is a test.#.data_type" }""" } -match = ".*([$]{1}([a-zA-Z0-9()# _-]+\.?){1,})" +match = ".*?([$]{1}([a-zA-Z0-9 _-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,})" actualitem = re.findall(match, parameter["value"], re.MULTILINE) +print("ACTUAL: ", actualitem) if len(actualitem) > 0: data = parameter["value"] diff --git a/backend/tests/triggers.sh b/backend/tests/triggers.sh index 75d3d436..393a3f05 100644 --- a/backend/tests/triggers.sh +++ b/backend/tests/triggers.sh @@ -1 +1,3 @@ -curl -H "Content-Type: application/json" localhost:5001/api/v1/triggers/9e845679-5843-4959-a76c-a6d664e9df35 -H "Authorization: Bearer 377469e8-dd5d-4521-8d9e-416d8d2f6fd4" +# curl -H "Content-Type: application/json" localhost:5001/api/v1/triggers/9e845679-5843-4959-a76c-a6d664e9df35 -H "Authorization: Bearer 377469e8-dd5d-4521-8d9e-416d8d2f6fd4" + +curl -XPOST http://localhost:5001/api/v1/hooks/webhook_1b968d49-2d78-4132-bf91-0f3a1f6a79f3 -d '{}' diff --git a/docker-compose.yml b/docker-compose.yml index 288063bb..b74d7a3c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,10 +27,13 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ${APP_HOTLOAD_LOCATION}:/shuffle-apps environment: - - ORG_ID=${ORG_ID} - DATASTORE_EMULATOR_HOST=shuffle-database:8000 - - APP_DOWNLOAD_LOCATION=${APP_DOWNLOAD_LOCATION} - APP_HOTLOAD_FOLDER=/shuffle-apps + - ORG_ID=${ORG_ID} + - APP_DOWNLOAD_LOCATION=${APP_DOWNLOAD_LOCATION} + - SHUFFLE_DEFAULT_USERNAME=${SHUFFLE_DEFAULT_USERNAME} + - SHUFFLE_DEFAULT_PASSWORD=${SHUFFLE_DEFAULT_PASSWORD} + - SHUFFLE_DEFAULT_APIKEY=${SHUFFLE_DEFAULT_APIKEY} restart: unless-stopped depends_on: - database diff --git a/frontend/src/Admin.js b/frontend/src/Admin.js index 06f14f64..a4762fc4 100644 --- a/frontend/src/Admin.js +++ b/frontend/src/Admin.js @@ -2,6 +2,8 @@ import React, { useEffect} from 'react'; import {Link} from 'react-router-dom'; import Paper from '@material-ui/core/Paper'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; import List from '@material-ui/core/List'; import Divider from '@material-ui/core/Divider'; import TextField from '@material-ui/core/TextField'; @@ -70,7 +72,7 @@ const Admin = (props) => { const onPasswordChange = () => { const data = {"username": selectedUser.username, "newpassword": newPassword} - const url = globalUrl+'/api/v1/passwordchange'; + const url = globalUrl+'/api/v1/users/passwordchange'; fetch(url, { mode: 'cors', method: 'POST', @@ -92,7 +94,7 @@ const Admin = (props) => { }), ) .catch(error => { - alert.error("Err: ", error.toString()) + alert.error("Err: "+error.toString()) }); } @@ -133,7 +135,7 @@ const Admin = (props) => { // Just use this one? var data = {"username": data.Username, "password": data.Password} var baseurl = globalUrl - const url = baseurl+'/api/v1/register'; + const url = baseurl+'/api/v1/users/register'; fetch(url, { method: 'POST', credentials: "include", @@ -294,6 +296,7 @@ const Admin = (props) => { return response.json() }) .then((responseJson) => { + console.log(responseJson) setUsers(responseJson) }) .catch(error => { @@ -319,14 +322,54 @@ const Admin = (props) => { modalUser[field] = value } - const generateApikey = () => { - fetch(globalUrl+"/api/v1/generateapikey", { - method: 'GET', + const setUser = (userId, field, value) => { + const data = {"user_id": userId} + data[field] = value + console.log("DATA: ", data) + + fetch(globalUrl+"/api/v1/users/updateuser", { + method: 'PUT', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, - credentials: "include", + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } else { + getUsers() + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success && responseJson.reason !== undefined) { + alert.error("Failed setting user: "+responseJson.reason) + } else { + alert.success("Set the user field "+field+" to "+value) + } + }) + .catch(error => { + console.log(error) + }); + } + + + + const generateApikey = (userId) => { + const data = {"user_id": userId} + + fetch(globalUrl+"/api/v1/generateapikey", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(data), + credentials: "include", }) .then((response) => { if (response.status !== 200) { @@ -407,7 +450,7 @@ const Admin = (props) => { style={{}} variant="outlined" color="primary" - onClick={() => generateApikey(selectedUser)} + onClick={() => generateApikey(selectedUser.id)} > Get new API key @@ -540,10 +583,6 @@ const Admin = (props) => { primary="API key" style={{minWidth: 350, maxWidth: 350, overflow: "hidden"}} /> - { style={{maxWidth: 350, minWidth: 350,}} /> - { + console.log("VALUE: ", e.target.value) + setUser(data.id, "role", e.target.value) + }} + style={{backgroundColor: surfaceColor, color: "white", height: "50px"}} + > + + Admin + + + User + + style={{minWidth: 150, maxWidth: 150}} /> { const [localFirstrequest, setLocalFirstrequest] = React.useState(true) const [requiresAuthentication, setRequiresAuthentication] = React.useState(true) const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false) + const [showSkippedActions, setShowSkippedActions] = React.useState(false) const [variableAnchorEl, setVariableAnchorEl] = React.useState(null) @@ -219,7 +224,7 @@ const AngularWorkflow = (props) => {
{workflowExecutions.slice(0,15).map(data => { return ( -
+
{new Date(data.started_at*1000).toISOString()} , {data.status} {data.result.length > 0 ? ", "+data.result : ", "} @@ -768,12 +773,12 @@ const AngularWorkflow = (props) => { //console.log("APP: ", selectedApp) setSelectedAction({}) setSelectedApp({}) - //setSelectedTrigger({}) + setSelectedTrigger({}) //setSelectedEdge({}) // setSelectedTriggerIndex(-1) //setSelectedActionEnvironment({}) - //setSelectedEdge({}) + setSelectedEdge({}) //setTriggerAuthentication({}) //setSelectedTriggerIndex(-1) //setTriggerFolders([]) @@ -792,17 +797,20 @@ const AngularWorkflow = (props) => { setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"])) setSelectedEdge(event.target.data()) - setSelectedAction({}) - setSelectedTrigger({}) } else { //alert.info("Can't edit branches from triggers") } + + setSelectedAction({}) + setSelectedTrigger({}) } const onNodeSelect = (event) => { const data = event.target.data() setLastSaved(false) - console.log(data) + const branch = workflow.branches.filter(branch => branch.source_id === data.id || branch.destination_id === data.id) + console.log("NODE: ", data) + console.log("BRANCHES: ", branch) if (data.type === "ACTION") { // FIXME - unselect @@ -845,9 +853,11 @@ const AngularWorkflow = (props) => { setRightSideBarOpen(true) } + // Checks for errors in edges when they're added const onEdgeAdded = (event) => { setLastSaved(false) const edge = event.target.data() + //console.log(workflow.branches) // Check if: // dest == source && source == dest @@ -856,18 +866,14 @@ const AngularWorkflow = (props) => { var found = false for (var key in workflow.branches) { if (workflow.branches[key].destination_id === edge.source && workflow.branches[key].source_id === edge.target) { - alert.error("A pointer in the opposite direction already exists") + alert.error("A branch in the opposite direction already exists") event.target.remove() found = true break } else if (workflow.branches[key].destination_id === edge.target && workflow.branches[key].source_id === edge.source) { - // Checks if NOT trigger - //workflow.triggers.find(data => data.id === workflow.branches[key].source_id).length === 0) - const triggercheck = workflow.triggers.find(data => data.id === workflow.branches[key].source_id) - if (workflow.triggers !== undefined && triggercheck !== undefined && triggercheck.length === 0) { - alert.error("That pointer already exists") - event.target.remove() - } + console.log(edge.source) + alert.error("That branch already exists") + event.target.remove() found = true break } else if (edge.target === workflow.start) { @@ -878,16 +884,29 @@ const AngularWorkflow = (props) => { found = true break } + } else if (edge.source === workflow.branches[key].source_id) { + // FIXME: Verify multi-target for triggers + // 1. Check if destination exists + // 2. Check if source is a trigger + // targetnode = workflow.triggers.findIndex(data => data.id === edge.source) + // console.log("Destination: ", edge.target) + // console.log("CHECK SOURCE IF ITS A TRIGGER: ", targetnode) + // if (targetnode !== -1) { + // alert.error("Triggers can only target one target (startnode)") + // event.target.remove() + // found = true + // break + // } } else { // Find the targetnode and check if its a trigger // FIXME - do this for both actions and other types? - //targetnode = workflow.triggers.findIndex(data => data.id === edge.target) - //if (targetnode !== -1) { - // alert.error("Can't have triggers as target") - // event.target.remove() - // found = true - // break - //} + targetnode = workflow.triggers.findIndex(data => data.id === edge.target) + if (targetnode !== -1) { + alert.error("Can't have triggers as target of branch") + event.target.remove() + found = true + break + } } } @@ -901,10 +920,10 @@ const AngularWorkflow = (props) => { if (!found) { newbranch["hasErrors"] = false - } - workflow.branches.push(newbranch) - setWorkflow(workflow) + workflow.branches.push(newbranch) + setWorkflow(workflow) + } } const onNodeAdded = (event) => { @@ -1039,7 +1058,7 @@ const AngularWorkflow = (props) => { event.preventDefault() cy.fit(null, 50) } - break; + break; case 65: // As a poweruser myself, I found myself hitting this a few // too many times to just edit text. Need a better bind @@ -1322,9 +1341,9 @@ const AngularWorkflow = (props) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to delete schedule: " + responseJson.reason) + //alert.error("Failed to delete schedule: " + responseJson.reason) } else { - alert.success("Successfully stopped schedule") + //alert.success("Successfully stopped schedule") workflow.triggers[triggerindex].status = "stopped" trigger.status = "stopped" setSelectedTrigger(trigger) @@ -1505,14 +1524,14 @@ const AngularWorkflow = (props) => { }} > - { + { setOpen(false) setNewVariableName(variable.name) setNewVariableDescription(variable.description) setNewVariableValue(variable.value) setVariablesModalOpen(true) }} key={"Edit"}>{"Edit"} - { + { deleteVariable(variable.name) setOpen(false) }} key={"Delete"}>{"Delete"} @@ -1567,12 +1586,12 @@ const AngularWorkflow = (props) => { }} > - { + { setOpen(false) setNewVariableName(variable.name) setExecutionVariablesModalOpen(true) }} key={"Edit"}>{"Edit"} - { + { deleteExecutionVariable(variable.name) setOpen(false) }} key={"Delete"}>{"Delete"} @@ -1665,16 +1684,7 @@ const AngularWorkflow = (props) => { ) } - const TriggersView = () => { - const triggersViewStyle = { - marginLeft: "10px", - marginRight: "10px", - display: "flex", - flexDirection: "column", - } - - // Predefined hurr - var triggers = [{ + var triggers = [{ "name": "Webhook", "type": "TRIGGER", "status": "uninitialized", @@ -1683,7 +1693,7 @@ const AngularWorkflow = (props) => { "large_image": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4wYNAxEP4A5uKQAAGipJREFUeNrtXHt4lNWZf8853zf3SSZDEgIJJtxCEnLRLSkXhSKgTcEL6yLK1hZWWylVbO1q7SKsSu3TsvVZqF2g4haoT2m9PIU+gJVHtFa5NQRD5FICIUAumBAmc81cvss5Z/845MtkAskEDJRu3r8Y8n3nfc/vvOe9zyDOOQxScoRvtAA3Ew2C1Q8aBKsfNAhWP2gQrH7QIFj9oEGw+kGDYPWDBsHqBw2C1Q+SbrQAPSg+/ULoRkvTjf4uwOKMAeeAEMI4AaBuf7rRhG5kIs05Zxxh1AUQ5yymUkVFgLBFxhZzbw///wGLUyZ2zikLn2oIVJ3o+NtZ5Xyb5u/gmgYAyCTLLqdlRKajaFRqeZFtTA7C+BJk5MZo2Y0Ai3EOHGGshyIX393btnNv5FQjjSoIYyQRRDBgdOkxyriuc8aJzeIozMu4d2rG16YQm4UzhtANULHrDRZnDGHMGW/b9lHzxh3RxlZslrHFjDAG4JxziBcHAUIIAHHGWFRhqmYblZ3z7bmZc24HAM75dTZk1xUsThkiWPn84umVv/btrSF2K7aYOGPA+pYBYQQIs5hCo8qQGRNGP/9vpky3WPAfECyxseCntSef+6Xq8UupDk4Z9Jc7QohgzR+yDMsY9/OlzpIx1xOv6wSW2JJvb03tM78AxrHVzHWaiAJGAMA5F4p2KYzgnHMG3WVEEqGRGDbJhWt+kFpedN3wuh5gCTsVPHzyb9/9L845Nkmcsm5CEMw0yiIxzhg2ycgkAQemqFyniBBiMyOJXOYVRcNmuXjDMntBnmBx84PFOSCktvmOLHxR9fiJ1Ry/bYQxZ0wPhk3pLtek4tQJhda84cRpA8Y0bzBS3xz4tDZYXav5QlKKXTwcjxcNxyy3ZJVufkFKtQtG/whg1f77Lzy7K+U0Z/ztQwTTiIJN0rAFdw97+G5TRlrihjkAAuVzT8tbu1ve3s01SmzdsZaI5g1m/cudY158/Doo18CCJTbg2V158plfXLLocUjpoYhtdE7+T5bYx+WKaJNR2mW8GOMciERESBWuPXdq2brIuRbJYU3QTRqOFq37oWtSyUDjNZBHwQFhzCk9v3knkqV4Iy2QcpaMKfnf5fZxuZxSRhlgREwykSVMCCaEyLJkkhHGlFKuU3tBXvH/LncU5Okd0QRzzjk/v2kngAjKBpAGULPEOXv/8umJ7/+3lGLvUgeMuKKZhrpLNv6nKcPFKeUIYYxVVa2srKyurm5ra+Ocp6enl5WVTZo0yW63M8YQ40giyueeo4+u1HwhJEtG2IEwohFl/Gv/kTqhcECVa8CrDhd3HUhw/MCBUzbquYXxSFVVVb322mv19fWcc0IIAFBKt2/fnp2dvWjRorvuuosBA52ah6ePfPYbtc++KpnkrmNGiGm65739qRMKYSAt8ICBxTnCWPd3hGrqsNXEO2N0RLAeDKffNTHtjjLOmEBq+/bta9askSQpJSUFRKjVeafa29tXrlx57ty5b3/72wwYMDZkZrl76q3eTw5LDptwjpxxYjEFDp2gkRixWQbOLQ6UxooNh+sa1Yu++CvDOUeEDJ03AwAYYxjjysrKNWvW2Gw2i8VCKaWUMsYYY+LfJpPJ7Xa/8cYbW7duxRgzygAga96M7k6TI5OstHgi9ecN1jcTWOI6hOuamKp12V2EWEyz5g1LuTUfAIgkqaq6YcMGSZIwxoyxnssI4FJSUjZv3nz+/HkiSxwg5bYCS3YGUzUDMoQRjamR+maD9U0FFgAAKOfb4j8ijLiq2gvzsNlENR0A/vrXv545c8ZqtV4WqUuwcy5JUiAQePfddwGA6TpxWO1jb2GKGuf+EHDeye6m0ywEAKB6At19E+KM20ZmCwwA4NChQ8ncGsaY2WyuqakBAIwwAFhGZHLGoOsuckBIC3R08b6ZwAIEACyqAEJxJ80BITnNCSJPBmhpaSGEJIMXIcTr9YZCIRFkSSkO4Im4cI32uc7fJ1gCMdTjUvD4/I5Smnwk2R3TnvhybJYHdDcDBxYHAOKwivwu/r/VNp+xc5fL1Yu1iidKqdvtdjgcwBgA6MEIksilAjQAAAIO8pDUK+D4dw4WBwAwZ6bF6xFwQBIJ1zaI3QFAfn5+Msol4vuioiKEEKUMAMKnGvVAB4sqAIAIRhghgq25WWAsfTOBBQAA1lHZ8QER54xYzKEjdbHzF4kkAcC0adNSU1P7xIsxJsvytGnToNPYDX/kazmP3WcdORwY1/whzRfEZpM9PxcGMkMcqAheSOwoHNmtSMAByUT1Bi5s+yj3yflU1YYPH37fffdt3rw5IyND07TLLoUxjkQixcXFZWVlAIAJBoC020vTbi/llEbPtgQ/O+X75DAgZM0bBgBxd/MmAUtIbBuTYxuT03H8LLaZRbGYMyY5bK1v7U6/a5J93C3A2KJFi86ePfvxxx+73W6EEO8kYyURZ/l8Pr/f73K5OOcIIc4YcECECBZZ/zIjsU49AERefPHFAVpatFH1QNi3t4ZYLV1FAkJoROk4Vp9RMRmbTRjgjqlTFUU5fvx4OBxmjBFCJEmKx0uW5ZaWFoTQhAkTRJKEuspeHBgDQNehDD+AYCEEgJBleMbFXQeYonRFp5xjsxxrbus4cTZ9ZjkyyRjQpMmTJk2a5HA4CCHBYDAYDJrNXb17zrnZbD516tTkyZPdbrdQrk4uCGE80JWsAQdLtOYlp412RPz7jxK7pas/yDmxWiKnmwNVf3NNKpZTHUyn6RkZEyZMqKiomDlzJqX02LFjstwVNxFCOjo6gsHgV77ylXiwricNJFidymUvHOn96FPNF8Iy6YqBOCdWS6y5zbPrgGVYhn3sCM454xwB2Gy2iRMnyrJ84MABi8Ui7iPn3GKxnD59urCwMCcnh4kO/j8SWIAQZ4xYTObsjIvv7sMmU7euKufEYqJR5eJ7+yOnmx35t5jcKUh08TkvLS2tra09d+6c2Ww2Klyapn3++ecVFRX4RkwgDXyvDWPOmHvabTmL7lG9ASSR+L9yypAkSU57+wdVNQ8tC59sAIRwZ1S5cOFCWe6qiDLG7Hb70aNH33vvPQCgdMDd3/UGS+AFnOc+9VBGxRTN40+skXMOCBBBriml9nG5wAEwEuWtwsLCmTNnhkIhUWgWeFkslt///vfBYDDJDPwmA6sTMzT25e+4Z5TTmJI43kcZtphzn3jwEnaXHkcAsGDBApfLpeu6+CjcYlNT0zvvvCOwS2DSM0z7uwNLVIF7ExEhTimxmrMeuJPTbrYZEaIHw8Mevts2dgSnzIi/EUKU0pycnHvuuaejo8MwUowxh8Oxffv2pqYmQohRiTbsmiDOuZAqyUR9wMHinAuMMMaEkN7dEyKEa3rz5h0ovm6DEI0ptlHZ2YvuATFXFC8cxgDw4IMPZmdnK4piKJckScFg8Le//a1AhxAiwlRKaSwWi8ViQhOFVBhjQ85rBOvq0x3hvAkhuq7X1tZ++OGHxcXFM2fOFBF2IqyUIYJb3v4gWH1STnMa2SLCiCvaiMUPSE5bz2EYsf/U1NSHHnpo9erVZrNZGHVKqcPh+Oijj2bNmjV06NCqqqqzZ8+2trYGAgFFUcRVTU1NzcrKys/PLykpycvLEwbusrIlT1fTZBVGAWOsKMr777+/Y8eOc+fOeb3emTNnrlq16jICcQ4IKa3tRx75T70jiiQiDJPoS6fdXlr0Pz/svX+l6/rSpUtPnz4dX60XKqZpWjgcRgiJrodgLVRJ3EGbzTZ69OhZs2bNmjXL4XCI168Osn6DZSB18ODB1157ra6uzmw2WywW4dc3bNhg5LpdrzCGMD790uutf/hIdjl5nMvnjJX8eoWjaOSlSeTLkUB///79K1asEN3pS6IjZGi3IVjXxhASMlBKFUVRVTU7O3vBggWzZ88mhFydivXvBYECY2z9+vXPPfdcY2Ojy+Uym82Ct8fjOXnyJHSv/wqkAlV/a9uxV0qxG0hdsuvzZjqKRl6aXL6SiBhzzqdMmTJ58uR4S28cSbyNN8joPAKA1Wp1uVxer/fnP//5s88+29zcjDG+ijCtH2AJ4SKRyIoVK7Zs2eJwOERb1HBDlFLRgOl2whhzxhrXvgPxCQpCLKZYc4flPHYf9LDrl2UNAN/85jeFCvd3kwI4WZbdbndNTc3SpUurqqqEJx0QsARSsVhs+fLl+/btGzJkiGh/xj9gsVg+++wzADBiSGHIW9/5MFBdSzq77QIdqqgjFv+z5HJyyvrstosYNT8/v6KioqOjw1i/60jifJ+4gD0dNOdc13Wn0xmLxZ5//vmPP/64v3j17xquWrWqqqoqLS1N1/WEzXDOI5GI1+v1+/1CMuAcEay2+Zp/vZ3YrF1ICbs+pSzz3qnimWRYGzGqqKkaKAhQdF0PhUJ+vz8cDiuKEovFxEdVVRMgEyomy/JPfvKTQ4cOCfuV5PaTMvDCJG3ZsuVXv/qV2+1OQIoQEo1GAWDOnDmPPPJIenr6pWImZYjg+pc3try9W3aldLPrlJX8erlj/Khe7HpPopQSQt56661169aJthBCKBqNapqWlZVVXFxcVFSUk5PjcDgope3t7bW1tZWVlWfPnrVarbIsx4MiOiB2u/2Xv/zl8OHDk6z59A2WQOrUqVPf+973hJLHvyJqdaNGjXr66adLSkqEQgHnopETrD55bPFPsVmOL5NqvmD2wntGPvP1/k4Ziy0pivLEE080NzcDgKIo48ePv/fee6dMmeJ0Onu+oqrqn//8502bNnk8HgFivOShUKi8vHzVqlVJgtW3rGKVTZs2xWKxhNwVYxwIBO666661a9eWlJRQTeeMI4wRJqK60LD2HR7fuUGIKYr1lqycbyVl13tKIvr43/jGN/x+//Dhw1944YVXX331q1/9ak+kdF3XNE2W5YqKivXr1992220i9zYeoJQ6nc7Kyspdu3aJlfsWoHfNEmpVXV397LPP2my2BE0OBoMPP/zwkiVLOOeMUiJJwHnH8TO+/UeiDa1Ki6fj+JluI3oEa/6O/Je/k3nftGsZXldVdffu3VOnTk1JSaGUCn1vbW09d+5cOBx2OBy5ublZWVnQOYQjYteXXnpp37594hUDfVVVc3Jy1q9fbzKZ+tSvpNKdnTt3JgBPCAkEAnPnzl2yZAljDBgnktRx4lzDq28Gqk4wRUUYIUnCVnPcMCPWO6JpU0oy75uWvF2/LMmyPGfOHM650J36+vrNmzfX1NSIfgfG2OFwFBUVPfzww7feeit0th2XL1/+gx/8oK6uzkgDhAc/c+bM/v37p0+f3idYvUksIvW2trbDhw8b5V2hU+FwuLS0dOmTS4WRwhK5+O7eo4te8h84hq0mOS1FSnUQmxl6qO2wf60A0ZK5NtJ1Xdd1WZb37Nnz5JNP7tmzR6QQTqfTbrfrun7w4MGnn3769ddfN3Jsi8Xy/e9/32QyJUQ8CKEPP/wwGaa9gSUWPXLkiNfrja9YiqTs8ccfl2SJ6RQT4v3o01PPr0cESyl2YJxTyinrhghCTNflNKejIA/6b60SSKQ4Aqkf//jHACDmK1knIYQcDofD4di0adPatWtF2EUp7RmpCeWqra31er0iALpKsAQdO3as2wsYh8Ph8vLykpISRimRJc0XPLPqDWySESH8ijEeRwhxnTJNv/yfe6WEhzVNa2xsXLt27cqVKyVJkiSpZ2wpUov09PS33nrrk08+MZz47Nmz7Xa78bw4eK/XW1dXB32NWPZms0QW1tDQEN/yFI7jjjvuABGgE3Lhjx/Hmi/IQ1J76wlzQBLR/aHQkTpLdgZw0HTtpZdeunDhgqGz8ZoLcSMLRj3PCFxCodCFCxcikYhwgldyZAJok8n05ptvTps2Texi9OjRY8eOPXbsmOGvEEK6rp85c2bixIlXCZYR1Hi93viIgVJqs9ny8/MBQMQH/n2fYbOczHcGOQf/viMZX5sCCBBAQ0NDY2OjyMMNXC4rSYJUGGNZlsVESe8cRc3+9OnTtbW1BQUFlFJJkvLz82tqarpVaxFqaWnpU/4+vGEsFotGo0aiLyyl3W53uVwAgDGm4ZjS6kXdu+1XQh+bpGhDi1iIcS5JktVqFT4brnwFeiIoVCbJtE7U3c6ePVtQUCBOJTs7O1EwjEWWdk2hA6XUaBYYS4uzvfSRMZ5kbsUBEKLRGNN0LEuqoookySif94JyUuv3SqFQyPh3zwhWdCT7BKsPA08Iib+D4hCi0WhHR4f4KDltl8rEffo3BMA5sVmQLAFAIBgIh8M9HRBOmvrVkbbZbMa/e842iX31uUgfmmWxWGw2WyAQiIcvHA6fP38+JyeH6TqRZcf40aGj9cRm4dDbvUAIMVW3jc4RW2xtbY1EIglZAQBEo9FkWvPC5ffp7MWTsizn5nbNuXk8noS3OOd2ux3iCor9A0v4HbPZ7Ha7m5ubDdcrzNaRI0cmTpwoBhIzKiZf+MOfk/i2M0IYDZn5ZfHh5MmT8ZUWQ+hx48bFB8BXIoxxXV2dqMD08rDwUSNGjCgoKIDOQlt9fX38W8K/Z2RkwLWEDmJUatSoUdXV1cauGGMmk+ngwYOLFi2SZZkzlvJP49Lvnti2Y4+c7uJXCKOQLGntAff0L6XdUSbKMtXV1fGBrrAamZmZr7zyitVq7f2ERU6za9eun/70p737REJIJBKZO3euLMuiwhMMBk+cOGHMTxjcher1cUJ9PlFaWhpflhH6X19ff+DAAQBgjAPAyB9+016Qp3mDSJa6RecIxE9baN6AbXTOmBWPcgCEUV1d3fHjx+NrxMJnFRUVWa1WsfleYlShCxUVFXPnzvV4PKKv01OnJElqb2+/884777//fuP/9+3b19raGn9OIhgaM2YMXIuBFxKUlZVlZmYmXBlRC1RVlUiEMyanOYvW/tBVXqRe9NGoIhwfIMQp18NRzRtMu71s/Gv/Ycp0i2+hbNu2LRqNxhdMBARixBbiAtFeiHP+1FNPPfDAA+3t7bFYTJRMBWGMNU3zeDzTp09ftmyZWJ8QEovFtm7dmqDRqqrm5uaOHDkS+mqR9TZyJA7QarU2NzcfO3ZM3A7oHDg4f/68pmnl5eWMMQQgOW0Zs6eYh6Vr7UE9GGaKyhmT7NaU0rF5Tz2Uu/QhyWnTNU2S5YMHD77++uvxpt0olSxevFiSJKOL1QsZKnb77bePGDGiqanJ4/FEIhHRkWaMDRs27Fvf+tbixYvFRJy4uZs2bfrLX/5idA+h01/df//9ZWVlotrTC9Ok6lmnT59eunRpwkIY446OjieeeGLevHmMMc4YxgRhxClTWjxaewAINg8dYspwAQCjjDEqyXJTU9Mzzzzj9/vjj5cQ4vf7n3zyyfnz5wvL0jtSRtdPhKaiXFVbW1tfX9/R0WGz2UaOHFlYWGhcc1HS2r1796pVq4wjN0CXJGn9+vXJFJeTLSuvXr1627ZtLpcrvnIGAOFweMGCBY899pjwL1TTESGks1bFAZiuIwBECELoxIkTK1eu9Hg88dbKMO3r1693OBy9SywagoSQ999/v7m5+ZFHHjFKLglnKXA0ksrdu3e/8sorwrolHNK8efOWLl2aTNu1b7CE9F6v97vf/a7P54uvBwlRgsFgUVHRwoULy8vLeyqFeP3ixYtbt27dtm2bqAvHx1aijvjCCy/MmDGjd4kNULZs2bJx40Zd10ePHj1//vzp06dbLBYDSsFRHJ5o3/3mN795++23E+IykT87nc5169YZTZZrBctQrn379okGekLZRLhnSunYsWPLy8sLCgoyMzOtVquu636/v6Gh4bPPPqupqfH5fA6HI+FLmGLAfc6cOc8991zvSInNqKq6Zs2anTt3ulwukUsoipKXlzdt2rSJEyfm5eWJ2FLI3NLScuDAgR07djQ0NIgUJ0HsQCCwbNmyu+++O8lufrKzDmK53/3ud+vWrXO73QkJneAUi8UURcEYm81mSZIYY6qqappGCLFarT2rTuIrl+PHj+8zthJ/8vl8K1eurK6uNqyBuGLCqJvN5vT09IyMDNHF8Xq9LS0twWDQYrGIznkC6/b29vnz5yd5AfsHloHXhg0b3njjjbS0tJ5lOSF6fMXOqED1fFiSJL/fn5+fv2rVqoTR9ssiFYlEHn/88aampiFDhqiq2pMvY0zTNF3XjWkRWZbFmfVk3d7ePmvWrBUrViTjeQ3qx7SyiCQmTJhgNpsPHDggiko9k6wEX9MTJoGg1+udMGHCyy+/LPS0l7MVcJtMJrvdXl1dLZQoIaMULAghpk4yRmsSWAOAz+ebPXv2j370I/FM8mD1e+RIuPa9e/euXr3a4/E4nU5xqsmsI2AKh8MA8OCDDz722GOiUZzMLRD6dfz48Z/97GeNjY0pKSn9moQRrCORCAAsWrTo61//ekI9dkDAMvDyeDwbN2784IMPFEWx2Wwi9rvs3RQC6bouClhlZWWPPvpoaWmpuC/JiytgDYVCGzdu/NOf/iT678LrXbZUD3GWQdjT4uLiJUuWFBcX95f11YMFnTOSCKFTp0798Y9/rKysbG9vFwGeMcoCnbM+uq5zzlNSUkpLS++9994vf/nLQhmvQlzjrZMnT7755ptVVVWhUEiWZXHv4hcUcZamaaqqSpI0ZsyYBx54YMaMGcKKXafJP4PEYRpW4PDhw0ePHhXzkiKSEG4xLS1txIgR48eP/9KXvjRs2LCEF6+Rb1NT0549ew4dOtTY2BgMBjVNM7YjSZLNZhs6dGhxcfHUqVNLS0tFw+JaWF/rD/f0jJ5VVY1EIrquY4ytVqvVau3l4S+Kr8/na2lpuXjxomhKWywWt9udlZU1dOhQw9KL0P9amH4xv3IkRIFOO9pzY+I8v/CvJiWzskh6vpAT+uJ/Eqqngf9i178S0wQbb1RyvkAuN/S3lW82uvE/hX0T0SBY/aBBsPpBg2D1gwbB6gcNgtUPGgSrHzQIVj9oEKx+0CBY/aD/A/ORNiwv2PAfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTA2LTEzVDAzOjE3OjE2LTA0OjAwj3mANAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wNi0xM1QwMzoxNzoxNS0wNDowMM/MIhUAAAAASUVORK5CYII=', "is_valid": true, "label": "Webhook", - "environment": environments[0].Name, + "environment": environments[0] === undefined ? {} : environments[0].Name, "description": "Simple HTTP webhook", "long_description": "Execute a workflow with an unauthicated POST request", }, @@ -1697,7 +1707,7 @@ const AngularWorkflow = (props) => { "errors": null, "is_valid": false, "label": "User input", - "environment": environments[0].Name, + "environment": environments[0] === undefined ? {} : environments[0].Name, "long_description": "Take user input to continue execution", }, { @@ -1710,7 +1720,7 @@ const AngularWorkflow = (props) => { "large_image": "data:image/png;base64,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", "is_valid": true, "label": "Schedule", - "environment": environments[0].Name, + "environment": environments[0] === undefined ? {} : environments[0].Name, "long_description": "Create a schedule based on cron", }, { @@ -1722,11 +1732,21 @@ const AngularWorkflow = (props) => { "errors": null, "is_valid": false, "label": "Email", - "environment": environments[0].Name, + "environment": environments[0] === undefined ? {} : environments[0].Name, "large_image": 'data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoM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', "long_description": "Execute a workflow when you get an email", }] + const TriggersView = () => { + const triggersViewStyle = { + marginLeft: "10px", + marginRight: "10px", + display: "flex", + flexDirection: "column", + } + + // Predefined hurr + return (
@@ -1796,7 +1816,7 @@ const AngularWorkflow = (props) => { } if (data.is_valid === false) { - alert.error("Sorry, "+data.name+" is not ready yet") + alert.error(data.name+" is only available on https://shuffler.io") return } @@ -1828,9 +1848,9 @@ const AngularWorkflow = (props) => { position: newposition, } - if (data.trigger_type === "WEBHOOK") { - newAppData.status = "running" - } + //if (data.trigger_type === "WEBHOOK") { + // newAppData.status = "running" + //} // Can all the data be in here? hmm const nodeToBeAdded = { @@ -1870,14 +1890,14 @@ const AngularWorkflow = (props) => { } if (data.name !== "User Input") { - workflow.branches.push(newbranch) + //workflow.branches.push(newbranch) cy.add(edgeToBeAdded) } setWorkflow(workflow) if (data.trigger_type === "WEBHOOK") { - newWebhook(newAppData) - saveWorkflow(workflow) + //newWebhook(newAppData) + //saveWorkflow(workflow) } } } @@ -2171,6 +2191,7 @@ const AngularWorkflow = (props) => { var allkeys = [action.id] var handled = [] var results = [] + console.log("BEFORE PARENTS!") while(true) { for (var key in allkeys) { @@ -2398,12 +2419,7 @@ const AngularWorkflow = (props) => { datafield =
- { }} style={{backgroundColor: inputColor, color: "white", height: "50px"}} > - {workflow.workflow_variables !== null ? workflow.workflow_variables.map(data => ( + {workflow.workflow_variables !== undefined && workflow.workflow_variables !== null ? workflow.workflow_variables.map(data => ( {data.name} )) : null} - {workflow.execution_variables !== null ? workflow.execution_variables.map(data => ( + {workflow.execution_variables !== undefined && workflow.execution_variables !== null ? workflow.execution_variables.map(data => ( {data.name} @@ -2497,7 +2510,7 @@ const AngularWorkflow = (props) => { itemColor = "#ffeb3b" } return ( -
+
@@ -2548,6 +2561,7 @@ const AngularWorkflow = (props) => { paddingRight: 10, paddingLeft: 10, minHeight: "100%", + zIndex: 1000, } const defineStartnode = () => { @@ -2640,16 +2654,11 @@ const AngularWorkflow = (props) => { placeholder={selectedAction.label} onChange={selectedNameChange} /> - {environments !== undefined && environments !== null && environments.length > 1 ? + {environments !== undefined && environments !== null && environments.length > 0 ?
Environment { }} /> - console.log(data) - - // Remap data based on variant - if (data.variant === "STATIC_VALUE") { - staticcolor = "#f85a3e" - } else if (data.variant === "ACTION_RESULT") { - // Gets the parents of the current node - var parents = getParents(workflow.actions.find(a => a.id === selectedEdge["target"])) - actioncolor = "#f85a3e" - // set the datafield - //var datafieldvalue = "Error: No parents. Action not eligible" - //if (parents.length > 0) { - // datafieldvalue = parents[0].label - //} - - datafield = -
- - { - changeActionVariable(data.action_field, e.target.value) - }} - />
- - } else if (data.variant === "WORKFLOW_VARIABLE") { - varcolor = "#f85a3e" - if (workflow.workflow_variables === null || workflow.workflow_variables === undefined || workflow.workflow_variables.length === 0) { - setCurrentView(2) - datafield = -
-
- Looks like you don't have any variables yet. -
-
- -
-
- } else { - // FIXME - this is a shitty solution that needs re-renders all the time - datafield = - - } - - } - const changeActionVariable = (variable, value) => { // set the name data.value = value @@ -3111,47 +3015,30 @@ const AngularWorkflow = (props) => { } return ( -
-
-
-
- {data.name} -
- -
{ - e.preventDefault() - changeActionParameterVariant("STATIC_VALUE") - }}> - +
+
+
+
+ {data.name}
- -  |  - -
{ - e.preventDefault() - changeActionParameterVariant("ACTION_RESULT") - }}> - -
-
-  |  - -
{ - e.preventDefault() - changeActionParameterVariant("WORKFLOW_VARIABLE") - }}> - -
-
-
- {datafield} -
+ +
{ + e.preventDefault() + changeActionParameterVariant("STATIC_VALUE") + }}> + +
+
+
+ {datafield} +
) } const menuItemStyle = { color: "white", + backgroundColor: inputColor } const conditionsModal = @@ -3231,6 +3118,16 @@ const AngularWorkflow = (props) => { setConditionValue(conditionValue) setVariableAnchorEl(null) }} key={"matches regex"}>matches regex + { + conditionValue.value = "larger than" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"larger than"}>larger than + { + conditionValue.value = "less than" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"less than"}>less than
@@ -3291,8 +3188,8 @@ const AngularWorkflow = (props) => { } const paperVariableStyle = { - minHeight: "50px", - maxHeight: "50px", + minHeight: 75, + maxHeight: 75, minWidth: "100%", maxWidth: "100%", marginTop: "5px", @@ -3318,15 +3215,15 @@ const AngularWorkflow = (props) => { setDestinationValue(condition.destination) setConditionsModalOpen(true) }}> -
+
{condition.source.value}
-
{}}> +
{}}> {condition.condition.value}
-
+
{condition.destination.value}
@@ -3336,6 +3233,7 @@ const AngularWorkflow = (props) => { aria-controls="long-menu" aria-haspopup="true" onClick={menuClick} + style={{color: "white",}} > @@ -3354,7 +3252,7 @@ const AngularWorkflow = (props) => { setAnchorEl(null) }} > - { + { setOpen(false) deleteCondition(index) }} key={"Delete"}>{"Delete"} @@ -3545,12 +3443,6 @@ const AngularWorkflow = (props) => { rows="10" value={selectedTrigger.parameters[0].value.split(splitter)} style={{backgroundColor: inputColor, color: "white"}} - PaperProps={{ - style: { - height: "200px", - backgroundColor: inputColor, - } - }} SelectDisplayProps={{ style: { marginLeft: 10, @@ -3954,13 +3846,24 @@ const AngularWorkflow = (props) => { return } + // Check the node it's connected to + var startNode = workflow.start + const branch = workflow.branches.find(branch => branch.source_id === trigger.id) + if (branch === undefined && (workflow.start === undefined || workflow.start === null || workflow.start.length === 0)) { + alert.error("No webhook node defined") + } + alert.info("Starting webhook") + if (branch !== undefined) { + startNode = branch.destination_id + } const data = { "name": hookname, "type": "webhook", "id": trigger.id, "workflow": workflow.id, + "start": startNode, } fetch(globalUrl+"/api/v1/hooks/new", { @@ -3974,10 +3877,10 @@ const AngularWorkflow = (props) => { if (responseJson.success) { // Set the status alert.success("Successfully started webhook") - workflow.triggers[selectedTriggerIndex].status = "running" trigger.status = "running" - setWorkflow(workflow) setSelectedTrigger(trigger) + workflow.triggers[selectedTriggerIndex].status = "running" + setWorkflow(workflow) saveWorkflow(workflow) } else { alert.error("Failed starting webhook: "+responseJson.reason) @@ -4011,7 +3914,7 @@ const AngularWorkflow = (props) => { }) .then((responseJson) => { if (responseJson.success) { - alert.success("Successfully stopped webhook") + //alert.success("Successfully stopped webhook") // Set the status workflow.triggers[triggerindex].status = "stopped" trigger.status = "stopped" @@ -4301,7 +4204,7 @@ const AngularWorkflow = (props) => { return null } - const cytoscapeViewWidths = 650 + const cytoscapeViewWidths = 700 const bottomBarStyle = { position: "fixed", right: 20, @@ -4359,8 +4262,65 @@ const AngularWorkflow = (props) => { return null } + const WorkflowMenu = () => { + const [newAnchor, setNewAnchor] = React.useState(null); + const [showShuffleMenu, setShowShuffleMenu] = React.useState(false) + + { /*const [showShuffleMenu, setShowShuffleMenu] = React.useState(true) */} + return ( +
+ { + setShowShuffleMenu(false) + }} + > +
+

This menu is used to control the workflow itself.

+ + Exit on Error
+ control={ + { + workflow.configuration.exit_on_error = !workflow.configuration.exit_on_error + setWorkflow(workflow) + setUpdate("exit_on_error_"+workflow.configuration.exit_on_error ? "true" : "false") + setShowShuffleMenu(false) + }} /> + } + /> + Start from top
+ control={ + { + workflow.configuration.start_from_top = !workflow.configuration.start_from_top + setWorkflow(workflow) + setUpdate("start_from_top_"+workflow.configuration.start_from_top ? "true" : "false") + setShowShuffleMenu(false) + }} /> + } + /> +
+ + + + +
+ ) + } const BottomCytoscapeBar = () => { + const [anchorEl, setAnchorEl] = React.useState(null); + const boxSize = 100 const executionButton = executionRunning ? @@ -4404,7 +4364,7 @@ const AngularWorkflow = (props) => { /> - @@ -4427,6 +4387,7 @@ const AngularWorkflow = (props) => { +
) @@ -4524,6 +4485,67 @@ const AngularWorkflow = (props) => { maxHeight: 40, } + const parsedExecutionArgument = () => { + var showResult = executionData.execution_argument.trim() + showResult.split(" None").join(" \"None\"") + + var jsonvalid = true + try { + const tmp = String(JSON.parse(showResult)) + if (!tmp.includes("{") && !tmp.includes("[")) { + jsonvalid = false + } + } catch (e) { + jsonvalid = false + } + + if (jsonvalid) { + return ( + + ) + } + + return ( +
+

Execution Argument:

+ {executionData.execution_argument} +
+ ) + } + + + const getExecutionSourceImage = (execution) => { + // This is the playbutton at 150x150 + const defaultImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg==" + const size = 40 + if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) { + return default + } + + if (execution.execution_source === "webhook") { + return {"webhook"} trigger.trigger_type === "WEBHOOK").large_image} style={{width: size, height: size}} /> + } + + if (execution.execution_source === "schedule") { + return {"schedule"} trigger.trigger_type === "SCHEDULE").large_image} style={{width: size, height: size}} /> + } + + if (execution.execution_source === "EMAIL") { + return {"email"} trigger.trigger_type === "EMAIL").large_image} style={{width: size, height: size}} /> + } + + + return ( + {execution.execution_source} + ) + } + const executionModal = setExecutionModalOpen(false)} PaperProps={{style: {minWidth: 375, maxWidth: 375, backgroundColor: "#1F2023", color: "white", fontSize: 18}}}> {executionModalView === 0 ? @@ -4539,6 +4561,7 @@ const AngularWorkflow = (props) => { onClick={() => { getWorkflowExecution(props.match.params.key) }} color="primary"> + Refresh executions @@ -4552,13 +4575,16 @@ const AngularWorkflow = (props) => { const timestamp = new Date(data.started_at*1000).toISOString().split('.')[0].split("T").join(" ") return ( - {}} onMouseOut={() => {}} onClick={() => { + {}} onMouseOut={() => {}} onClick={() => { setExecutionModalView(1) setExecutionData(data) }}>
-
-
+
+
+ {getExecutionSourceImage(data)} +
+
{timestamp}
{data.workflow.actions !== null ? @@ -4587,17 +4613,19 @@ const AngularWorkflow = (props) => {

{setExecutionModalView(0)}}> - - Other Executions + + See other Executions

Executing Workflow

- {executionData.execution_argument !== undefined && executionData.execution_argument.length > 0 ? -
-

Execution Argument:

{executionData.execution_argument} -
- : null } + Show failed / skipped actions
+ control={ + {setShowSkippedActions(!showSkippedActions)}} /> + } + /> {executionData.status !== undefined && executionData.status.length > 0 ?
Status: {executionData.status} @@ -4610,12 +4638,21 @@ const AngularWorkflow = (props) => {
: null } - {executionData.execution_id !== undefined && executionData.execution_id.length > 0 ? + {executionData.completed_at !== undefined ?
- ID: {executionData.execution_id} + Finished: {new Date(executionData.completed_at*1000).toISOString()}
: null } + {executionData.execution_source !== undefined && executionData.execution_source.length > 0 ? +
+ Source: {executionData.execution_source} +
+ : null + } + {executionData.execution_argument !== undefined && executionData.execution_argument.length > 0 ? + parsedExecutionArgument() + : null }
Actions @@ -4627,6 +4664,10 @@ const AngularWorkflow = (props) => { : executionData.results.map(data => { + if (executionData.results.length !== 1 && !showSkippedActions && (data.status === "SKIPPED" || data.status === "FAILURE")) { + return null + } + var showResult = data.result.trim() showResult.split(" None").join(" \"None\"") @@ -4644,9 +4685,10 @@ const AngularWorkflow = (props) => { const curapp = apps.find(a => a.name === data.action.app_name && a.app_version === data.action.app_version) const imgsize = 50 + const statusColor = data.status === "FINISHED" || data.status === "SUCCESS" ? "green" : data.status === "ABORTED" || data.status === "FAILURE" ? "red" : "orange" const actionimg = curapp === null ? null : - {data.action.app_name} + {data.action.app_name} return (
diff --git a/frontend/src/App.js b/frontend/src/App.js index fd8d20ab..c162b774 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -59,7 +59,14 @@ const theme = createMuiTheme({ }, typography: { useNextVariants: true - } + }, + overrides: { + MuiMenu: { + list: { + backgroundColor: inputColor, + }, + }, + }, }); @@ -84,7 +91,7 @@ const App = (message, props) => { const checkLogin = () => { var baseurl = globalUrl - fetch(baseurl+"/api/v1/getinfo", { + fetch(baseurl+"/api/v1/users/getinfo", { credentials: "include", headers: { 'Content-Type': 'application/json', diff --git a/frontend/src/Apps.js b/frontend/src/Apps.js index 8bdb587f..3e48b8e6 100644 --- a/frontend/src/Apps.js +++ b/frontend/src/Apps.js @@ -448,6 +448,12 @@ const Apps = (props) => { })}
: null} + {selectedAction.description !== undefined && selectedAction.description !== null ? +
+ Action Description
+ {selectedAction.description} +
+ : null}
: null diff --git a/frontend/src/LoginPage.js b/frontend/src/LoginPage.js index 7ff22e80..49e22f55 100644 --- a/frontend/src/LoginPage.js +++ b/frontend/src/LoginPage.js @@ -92,7 +92,7 @@ const LoginDialog = props => { var data = {"username": username, "password": password} var baseurl = globalUrl if (register) { - var url = baseurl+'/api/v1/login'; + var url = baseurl+'/api/v1/users/login'; fetch(url, { mode: 'cors', method: 'POST', @@ -123,7 +123,7 @@ const LoginDialog = props => { setLoginInfo("Error in userdata: " + error) }); } else { - url = baseurl+'/api/v1/register'; + url = baseurl+'/api/v1/users/register'; fetch(url, { method: 'POST', body: JSON.stringify(data), diff --git a/frontend/src/Workflows.js b/frontend/src/Workflows.js index 04ea2dd9..95c2e2ba 100644 --- a/frontend/src/Workflows.js +++ b/frontend/src/Workflows.js @@ -89,7 +89,6 @@ const Workflows = (props) => { return response.json() }) .then((responseJson) => { - console.log(responseJson) setSelectedExecution({}) setWorkflowExecutions([]) @@ -384,21 +383,21 @@ const Workflows = (props) => { }} > - { + { setModalOpen(true) setEditingWorkflow(data) setNewWorkflowName(data.name) setNewWorkflowDescription(data.description) }} key={"change"}>{"Change name"} - { + { copyWorkflow(data) setOpen(false) }} key={"copy"}>{"Copy"} - { + { exportWorkflow(data) setOpen(false) }} key={"export"}>{"Export"} - { + { deleteWorkflow(data.id) setOpen(false) }} key={"delete"}>{"Delete"} @@ -807,7 +806,7 @@ const Workflows = (props) => { const ret = setNewWorkflow(data.name, data.description, data, false) .then((response) => { if (response !== undefined) { - alert.success("Successfully created "+data.name) + alert.success("Successfully imported "+data.name) } }) } @@ -1031,20 +1030,20 @@ const Workflows = (props) => { }) .then((response) => { if (response.status === 200) { - response.text().then(function (text) { - console.log("RETURN: ", text) - alert.success("Loaded existing apps!") - }) + alert.success("Successfully loaded workflows from "+downloadUrl) + getAvailableWorkflows() } return response.json() }) .then((responseJson) => { console.log("DATA: ", responseJson) - if (responseJson.reason !== undefined) { - alert.error("Failed loading: "+responseJson.reason) - } else { - alert.error("Failed loading") + if (!responseJson.success) { + if (responseJson.reason !== undefined) { + alert.error("Failed loading: "+responseJson.reason) + } else { + alert.error("Failed loading") + } } }) .catch(error => { diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js index 2b57403c..d101c1cc 100644 --- a/frontend/src/defaultCytoscapeStyle.js +++ b/frontend/src/defaultCytoscapeStyle.js @@ -24,6 +24,7 @@ const data = [{ 'curve-style': 'unbundled-bezier', 'label': 'data(label)', 'text-margin-y': '-15px', + "color": "white", "line-fill": "linear-gradient", "line-gradient-stop-colors": ["cyan", "yellow"], "line-gradient-stop-positions": ["0.0", "100"], @@ -111,6 +112,8 @@ const data = [{ selector: 'node:selected', css: { 'background-color': '#77b0d0', + 'border-color': '#77b0d0', + 'border-width': '20px', }, }, { diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 7585090b..aed440b6 100644 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -432,7 +432,6 @@ func zombiecheck() error { stopContainers := []string{} removeContainers := []string{} for _, container := range containers { - // Skip random containers. Only handle things related to Shuffle. if !strings.Contains(container.Image, baseimagename) { shuffleFound := false @@ -447,6 +446,8 @@ func zombiecheck() error { if !shuffleFound { continue } + //} else { + // log.Printf("NAME: %s", container.Image) } for _, name := range container.Names { @@ -455,13 +456,16 @@ func zombiecheck() error { continue } - if container.State != "running" { + log.Printf("NAME: %s", name) + + // Need to check time here too because a container can be removed the same instant as its created + currenttime := time.Now().Unix() + if container.State != "running" && currenttime-container.Created > int64(workerTimeout) { removeContainers = append(removeContainers, container.ID) containerNames[container.ID] = name } // stopcontainer & removecontainer - currenttime := time.Now().Unix() //log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout)) if container.State == "running" && currenttime-container.Created > int64(workerTimeout) { stopContainers = append(stopContainers, container.ID) diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index 4696b58b..198a780e 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -423,6 +423,11 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W onpremApps := []string{} startAction := workflowExecution.Start + if len(startAction) == 0 { + log.Printf("Didn't find execution start action. Setting it to workflow start action.") + startAction = workflowExecution.Workflow.Start + } + log.Printf("Startaction: %s", startAction) toExecuteOnprem := []string{} parents := map[string][]string{} @@ -660,7 +665,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W // FIXME // Execute, as we don't really care if env is not set? IDK if action.Environment != environment { //&& action.Environment != "" { - log.Printf("Bad environment: %s", action.Environment) + log.Printf("Bad environment for node: %s. Want %s", action.Environment, environment) continue } @@ -995,6 +1000,7 @@ func main() { } else { authorization = os.Getenv("AUTHORIZATION") executionId = os.Getenv("EXECUTIONID") + log.Printf("Running normal execution with auth %s and ID %s", authorization, executionId) } if len(authorization) == 0 { diff --git a/shuffle-apps/README.md b/shuffle-apps/README.md new file mode 100644 index 00000000..f30690cd --- /dev/null +++ b/shuffle-apps/README.md @@ -0,0 +1,5 @@ +# Shuffle Apps +* This folder is by default meant to be empty +* This folder is meant for quick development of apps (single button hot-loading) +* Some shuffle apps can be found at https://github.com/frikky/shuffle-apps +