From 7814eb1e0842c7bdb7f9b3bf445942f2df7c15de Mon Sep 17 00:00:00 2001 From: frikky Date: Sat, 27 Mar 2021 18:57:26 +0100 Subject: [PATCH] Added more hybrid features --- backend/go-app/docker.go | 2 +- backend/go-app/go.mod | 2 +- backend/go-app/go.sum | 2 + backend/go-app/main.go | 325 +++++------------------- backend/go-app/oauth2.go | 4 +- backend/go-app/walkoff.go | 336 +------------------------ frontend/src/views/Admin.jsx | 95 +++++-- frontend/src/views/AngularWorkflow.jsx | 34 ++- frontend/src/views/Apps.jsx | 6 +- frontend/src/views/Workflows.jsx | 93 ++++++- 10 files changed, 263 insertions(+), 636 deletions(-) diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index e11934f8..214f81c9 100644 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -863,7 +863,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { tagFound := "" for _, image := range images { for _, tag := range image.RepoTags { - log.Printf("Image: %s", tag) + log.Printf("[INFO] Docker Image: %s", tag) if strings.ToLower(tag) == strings.ToLower(version.Name) { img = image diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 8a2e19ae..7d7f91f2 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -2,7 +2,7 @@ module shuffle go 1.13 -//replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared +replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi require ( diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index de114e19..a20b5e4c 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -88,6 +88,8 @@ github.com/frikky/kin-openapi v0.38.0 h1:V7ttwIJS8Vks4KL+mZVj1ZSqhIcQtgaG8akeqXE github.com/frikky/kin-openapi v0.38.0/go.mod h1:Fr28TtCHL4K0kIqtqui8HWxN1LG5uAh3z/tDfFyiA1s= github.com/frikky/shuffle-shared v0.0.12 h1:+0EIfThmK47Po+LogPYZR4XjbS4Ds19WNMFu2YUSjhw= github.com/frikky/shuffle-shared v0.0.12/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww= +github.com/frikky/shuffle-shared v0.0.15 h1:508ceeEHfPBMCC8/K4Zve3kwRQqiXNJSw6+BDoq9X4E= +github.com/frikky/shuffle-shared v0.0.15/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww= github.com/getkin/kin-openapi v0.8.0 h1:a6TQjTqwkyscC4/hShJX7WhCVE+4bi9lzw61XHQW5hE= github.com/getkin/kin-openapi v0.8.0/go.mod h1:zZQMFkVgRHCdhgb6ihCTIo9dyDZFvX0k/xAKqw1FhPw= github.com/getkin/kin-openapi v0.52.0 h1:6WqsF5d6PfJ8AscdD+9Rtb2RP2iBWyC7V6GcjssWg7M= diff --git a/backend/go-app/main.go b/backend/go-app/main.go index ef8345b1..7d1de1b2 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -932,7 +932,7 @@ func createNewUser(username, password, role, apikey string, org shuffle.Org) err neworg, err := shuffle.GetOrg(ctx, org.Id) if err == nil { //neworg.Users = append(neworg.Users, *newUser) - err = setOrg(ctx, *neworg, neworg.Id) + err = shuffle.SetOrg(ctx, *neworg, neworg.Id) if err != nil { log.Printf("Failed updating org with user %s", newUser.Username) } else { @@ -1164,7 +1164,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { userInfo, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { - log.Printf("Api authentication failed in handleInfo: %s", err) + log.Printf("[WARNING] Api authentication failed in handleInfo: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -1572,7 +1572,7 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("Handling login of %s", data.Username) + log.Printf("[INFO] Handling login of %s", data.Username) err = checkUsername(data.Username) if err != nil { @@ -1675,39 +1675,6 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(loginData)) } -func setOrg(ctx context.Context, org shuffle.Org, id string) error { - // clear session_token and API_token for user - timeNow := int64(time.Now().Unix()) - if org.Created == 0 { - org.Created = timeNow - } - - org.Edited = timeNow - - k := datastore.NameKey("Organizations", id, nil) - if _, err := dbclient.Put(ctx, k, &org); err != nil { - log.Printf("Failed setting org: %s", err) - return err - } - - // FIXME: Make this update every user to have the correct org data. - //org = fixOrgUser(ctx, &org) - //_ = org - - return nil -} - -// ListBooks returns a list of books, ordered by title. -//func getUser(ctx context.Context, id string) (*User, error) { -// key := datastore.NameKey("Users", id, nil) -// curUser := &User{} -// if err := dbclient.Get(ctx, key, curUser); err != nil { -// return &User{}, err -// } -// -// return curUser, nil -//} - // Index = Username func DeleteKeys(ctx context.Context, entity string, value []string) error { // Non indexed User data @@ -1818,7 +1785,7 @@ func fixOrgUser(ctx context.Context, org *shuffle.Org) *shuffle.Org { // org.Users = append(org.Users, *user) // } - // err = setOrg(ctx, *org, orgId) + // err = shuffle.SetOrg(ctx, *org, orgId) // if err != nil { // log.Printf("Failed setting org %s", orgId) // } @@ -1873,7 +1840,7 @@ func fixUserOrg(ctx context.Context, user *shuffle.User) *shuffle.User { org.Users = append(org.Users, *user) } - err = setOrg(ctx, *org, orgId) + err = shuffle.SetOrg(ctx, *org, orgId) if err != nil { log.Printf("Failed setting org %s", orgId) } @@ -2052,7 +2019,7 @@ func handleSetHook(resp http.ResponseWriter, request *http.Request) { user, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { - log.Printf("Api authentication failed in set new workflowhandler: %s", err) + log.Printf("[INFO] Api authentication failed in set new workflowhandler: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -2362,7 +2329,7 @@ func handleDeleteSchedule(resp http.ResponseWriter, request *http.Request) { user, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { - log.Printf("Api authentication failed in set new workflowhandler: %s", err) + log.Printf("[WARNING] Api authentication failed in set new workflowhandler: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -2616,7 +2583,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } } -func executeCloudAction(action CloudSyncJob, apikey string) error { +func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error { data, err := json.Marshal(action) if err != nil { log.Printf("Failed cloud webhook action marshalling: %s", err) @@ -2661,164 +2628,6 @@ func executeCloudAction(action CloudSyncJob, apikey string) error { return nil } -// Starts a new webhook -func handleNewHook(resp http.ResponseWriter, request *http.Request) { - cors := shuffle.HandleCors(resp, request) - if cors { - return - } - - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("Api authentication failed in set new workflowhandler: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - type requestData struct { - Type string `json:"type"` - Description string `json:"description"` - Id string `json:"id"` - Name string `json:"name"` - Workflow string `json:"workflow"` - Start string `json:"start"` - Environment string `json:"environment"` - } - - 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 - } - - //log.Printf("Data: %s", string(body)) - - ctx := context.Background() - var requestdata requestData - err = yaml.Unmarshal([]byte(body), &requestdata) - if err != nil { - log.Printf("Failed unmarshaling inputdata: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - log.Printf("%#v", requestdata) - - // CBA making a real thing. Already had some code lol - newId := requestdata.Id - if len(newId) != 36 { - log.Printf("Bad ID") - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Invalid ID"}`)) - return - } - - if requestdata.Id == "" || requestdata.Name == "" { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Requires fields id and name can't be empty"}`)) - return - - } - - validTypes := []string{ - "webhook", - } - - isTypeValid := false - for _, thistype := range validTypes { - if requestdata.Type == thistype { - isTypeValid = true - break - } - } - - if !(isTypeValid) { - log.Printf("Type %s is not valid. Try any of these: %s", requestdata.Type, strings.Join(validTypes, ", ")) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // Let remote endpoint handle access checks (shuffler.io) - currentUrl := fmt.Sprintf("https://shuffler.io/api/v1/hooks/webhook_%s", newId) - startNode := requestdata.Start - if requestdata.Environment == "cloud" { - // https://shuffler.io/v1/hooks/webhook_80184973-3e82-4852-842e-0290f7f34d7c - log.Printf("[INFO] Should START a cloud webhook for url %s for startnode %s", currentUrl, startNode) - org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id) - if err != nil { - log.Printf("Failed finding org %s: %s", org.Id, err) - return - } - - action := CloudSyncJob{ - Type: "webhook", - Action: "start", - OrgId: org.Id, - PrimaryItemId: newId, - SecondaryItem: startNode, - ThirdItem: requestdata.Workflow, - } - - err = executeCloudAction(action, org.SyncConfig.Apikey) - if err != nil { - log.Printf("Failed cloud action START execution: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } else { - log.Printf("[INFO] Successfully set up cloud action schedule") - } - } - - hook := Hook{ - Id: newId, - Start: startNode, - Workflows: []string{requestdata.Workflow}, - Info: Info{ - Name: requestdata.Name, - Description: requestdata.Description, - Url: fmt.Sprintf("https://shuffler.io/api/v1/hooks/webhook_%s", newId), - }, - Type: "webhook", - Owner: user.Username, - Status: "uninitialized", - Actions: []HookAction{ - HookAction{ - Type: "workflow", - Name: requestdata.Name, - Id: requestdata.Workflow, - Field: "", - }, - }, - Running: false, - OrgId: user.ActiveOrg.Id, - Environment: requestdata.Environment, - } - - hook.Status = "running" - hook.Running = true - err = setHook(ctx, hook) - if err != nil { - log.Printf("Failed setting hook: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - err = increaseStatisticsField(ctx, "total_workflow_triggers", requestdata.Workflow, 1, user.ActiveOrg.Id) - if err != nil { - log.Printf("[INFO] Failed to increase total workflows: %s", err) - } - - log.Printf("[INFO] Set up a new hook with ID %s and environment %s", newId, hook.Environment) - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) -} - func getSpecificSchedule(resp http.ResponseWriter, request *http.Request) { if request.Method != "GET" { setSpecificSchedule(resp, request) @@ -3462,7 +3271,7 @@ func handleGetallHooks(resp http.ResponseWriter, request *http.Request) { user, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { - log.Printf("Api authentication failed in set new workflowhandler: %s", err) + log.Printf("[WARNING] Api authentication failed in set new workflowhandler: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -3532,7 +3341,7 @@ func findAvailablePorts(startRange int64, endRange int64) string { func handleSendalert(resp http.ResponseWriter, request *http.Request) { user, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { - log.Printf("Api authentication failed in sendalert: %s", err) + log.Printf("[WARNING] Api authentication failed in sendalert: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -4753,22 +4562,6 @@ func handleAppHotload(ctx context.Context, location string, forceUpdate bool) er return nil } -// Primary = usually an outer ID, e.g. workflow ID -// Secondary = something to specify what inside workflow to execute -// Third = Some data to add to it -type CloudSyncJob struct { - Id string `json:"id" datastore:"id"` - Type string `json:"type" datastore:"type"` - Action string `json:"action" datastore:"action"` - OrgId string `json:"org_id" datastore:"org_id"` - PrimaryItemId string `json:"primary_item_id" datastore:"primary_item_id"` - SecondaryItem string `json:"secondary_item" datastore:"secondary_item"` - ThirdItem string `json:"third_item" datastore:"third_item"` - FourthItem string `json:"fourth_item" datastore:"fourth_item"` - FifthItem string `json:"fifth_item" datastore:"fifth_item"` - Created string `json:"created" datastore:"created"` -} - func handleCloudExecutionOnprem(workflowId, startNode, executionSource, executionArgument string) error { ctx := context.Background() // 1. Get the workflow @@ -4826,7 +4619,7 @@ func handleCloudExecutionOnprem(workflowId, startNode, executionSource, executio return err } -func handleCloudJob(job CloudSyncJob) error { +func handleCloudJob(job shuffle.CloudSyncJob) error { // May need authentication in all of these..? log.Printf("[INFO] Handle job with type %s and action %s", job.Type, job.Action) @@ -4932,7 +4725,7 @@ func handleCloudJob(job CloudSyncJob) error { return err } - fullUrl := fmt.Sprintf("https://shuffler.io/api/v1/workflows/%s/execute?authorization=%s&start=%s&reference_execution=%s&answer=true", job.PrimaryItemId, job.FourthItem, job.SecondaryItem, job.ThirdItem) + fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute?authorization=%s&start=%s&reference_execution=%s&answer=true", syncUrl, job.PrimaryItemId, job.FourthItem, job.SecondaryItem, job.ThirdItem) newRequest, err := http.NewRequest( "GET", fullUrl, @@ -4997,9 +4790,9 @@ func handleCloudJob(job CloudSyncJob) error { // Handles jobs from remote (cloud) func remoteOrgJobController(org shuffle.Org, body []byte) error { type retStruct struct { - Success bool `json:"success"` - Reason string `json:"reason"` - Jobs []CloudSyncJob `json:"jobs"` + Success bool `json:"success"` + Reason string `json:"reason"` + Jobs []shuffle.CloudSyncJob `json:"jobs"` } responseData := retStruct{} @@ -5010,9 +4803,9 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error { ctx := context.Background() if !responseData.Success { - log.Printf("Should stop org job controller because no success?") + log.Printf("[WARNING] Should stop org job controller because no success?") - if strings.Contains(responseData.Reason, "Bad apikey") || strings.Contains(responseData.Reason, "Error getting the organization") { + if strings.Contains(responseData.Reason, "Bad apikey") || strings.Contains(responseData.Reason, "Error getting the organization") || strings.Contains(responseData.Reason, "Organization isn't syncing") { log.Printf("[WARNING] Remote error; Bad apikey or org error. Stopping sync for org: %s", responseData.Reason) if value, exists := scheduledOrgs[org.Id]; exists { @@ -5029,7 +4822,20 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error { org.SyncConfig.Interval = 0 org.SyncConfig.Apikey = "" org.CloudSync = false - err = setOrg(ctx, *org, org.Id) + + // Just in case + org, err = handleStopCloudSync(syncUrl, *org) + + startDate := time.Now().Unix() + org.SyncFeatures.Webhook = shuffle.SyncData{Active: false, Type: "trigger", Name: "Webhook", StartDate: startDate} + org.SyncFeatures.UserInput = shuffle.SyncData{Active: false, Type: "trigger", Name: "User Input", StartDate: startDate} + org.SyncFeatures.EmailTrigger = shuffle.SyncData{Active: false, Type: "action", Name: "Email Trigger", StartDate: startDate} + org.SyncFeatures.Schedules = shuffle.SyncData{Active: false, Type: "trigger", Name: "Schedule", StartDate: startDate, Limit: 0} + org.SyncFeatures.SendMail = shuffle.SyncData{Active: false, Type: "action", Name: "Send Email", StartDate: startDate, Limit: 0} + org.SyncFeatures.SendSms = shuffle.SyncData{Active: false, Type: "action", Name: "Send SMS", StartDate: startDate, Limit: 0} + org.CloudSyncActive = false + + err = shuffle.SetOrg(ctx, *org, org.Id) if err != nil { log.Printf("[WARNING] Failed setting organization when stopping sync: %s", err) } else { @@ -5082,8 +4888,7 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { return err } - //log.Printf("Data: %s", respBody) - + //log.Printf("Remote Data: %s", respBody) err = remoteOrgJobController(org, respBody) if err != nil { log.Printf("[ERROR] Failed job controller run for %s: %s", respBody, err) @@ -5126,7 +4931,7 @@ func runInit(ctx context.Context) { }, // 15 second timeout - Timeout: 15 * time.Second, + Timeout: 15 * 15time.Second, // don't follow redirect CheckRedirect: func(req *http.Request, via []*http.Request) error { @@ -5172,7 +4977,7 @@ func runInit(ctx context.Context) { CloudSync: false, } - err = setOrg(ctx, newOrg, orgId) + err = shuffle.SetOrg(ctx, newOrg, orgId) if err != nil { log.Printf("Failed setting organization: %s", err) } else { @@ -5219,7 +5024,7 @@ func runInit(ctx context.Context) { } if setOrgBool { - err = setOrg(ctx, activeOrg, activeOrg.Id) + err = shuffle.SetOrg(ctx, activeOrg, activeOrg.Id) if err != nil { log.Printf("Failed setting org %s: %s!", activeOrg.Name, err) } else { @@ -5741,9 +5546,9 @@ func handleVerifyCloudsync(orgId string) (shuffle.SyncFeatures, error) { // Actually stops syncing with cloud for an org. // Disables potential schedules, removes environments, breaks workflows etc. -func handleStopCloudSync(syncUrl string, org shuffle.Org) error { +func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error) { if len(org.SyncConfig.Apikey) == 0 { - return errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id)) + return &org, errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id)) } log.Printf("Should run cloud sync disable for org %s with URL %s and sync key %s", org.Id, syncUrl, org.SyncConfig.Apikey) @@ -5758,28 +5563,28 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) error { req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) newresp, err := client.Do(req) if err != nil { - return err + return &org, err } respBody, err := ioutil.ReadAll(newresp.Body) if err != nil { - return err + return &org, err } log.Printf("Remote disable ret: %s", string(respBody)) responseData := retStruct{} err = json.Unmarshal(respBody, &responseData) if err != nil { - return err + return &org, err } if newresp.StatusCode != 200 { - return errors.New(fmt.Sprintf("Got status code %d when disabling org remotely. Expected 200. Contact support.", newresp.StatusCode)) + return &org, errors.New(fmt.Sprintf("Got status code %d when disabling org remotely. Expected 200. Contact support.", newresp.StatusCode)) } if !responseData.Success { //log.Printf("Success reason: %s", responseData.Reason) - return errors.New(responseData.Reason) + return &org, errors.New(responseData.Reason) } log.Printf("Everything is success. Should disable org sync for %s", org.Id) @@ -5789,18 +5594,18 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) error { org.SyncFeatures = shuffle.SyncFeatures{} org.SyncConfig = shuffle.SyncConfig{} - err = setOrg(ctx, org, org.Id) + err = shuffle.SetOrg(ctx, org, org.Id) if err != nil { newerror := fmt.Sprintf("ERROR: Failed updating even though there was success: %s", err) log.Printf(newerror) - return errors.New(newerror) + return &org, errors.New(newerror) } var environments []shuffle.Environment q := datastore.NewQuery("Environments").Filter("org_id =", org.Id) _, err = dbclient.GetAll(ctx, q, &environments) if err != nil { - return err + return &org, err } // Don't disable, this will be deleted entirely @@ -5810,9 +5615,9 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) error { environment.Archived = true err = setEnvironment(ctx, &environment) if err == nil { - log.Printf("Updated cloud environment %s", environment.Name) + log.Printf("[INFO] Updated cloud environment %s", environment.Name) } else { - log.Printf("Failed to update cloud environment %s", environment.Name) + log.Printf("[INFO] Failed to update cloud environment %s", environment.Name) } } } @@ -5820,12 +5625,12 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) error { // FIXME: This doesn't work? if value, exists := scheduledOrgs[org.Id]; exists { // Looks like this does the trick? Hurr - log.Printf("STOPPING ORG SCHEDULE for: %s", org.Id) + log.Printf("[WARNING] STOPPING ORG SCHEDULE for: %s", org.Id) value.Lock() } - return nil + return &org, nil } // INFO: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit @@ -5885,13 +5690,13 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { } // FIXME: Check if user is admin of this org - log.Printf("Checking org %s", org.Name) + //log.Printf("Checking org %s", org.Name) userFound := false admin := false for _, inneruser := range org.Users { if inneruser.Id == user.Id { userFound = true - log.Printf("Role: %s", inneruser.Role) + //log.Printf("[INFO] Role: %s", inneruser.Role) if inneruser.Role == "admin" { admin = true } @@ -5923,17 +5728,17 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { apiPath := "/api/v1/cloud/sync/setup" if tmpData.Disable { if !org.CloudSync { - log.Printf("Org %s isn't syncing. Can't stop.", org.Id) + log.Printf("[WARNING] Org %s isn't syncing. Can't stop.", org.Id) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Skipped cloud sync setup. Already syncing."}`))) return } - log.Printf("Should disable sync for org %s", org.Id) + log.Printf("[INFO] Should disable sync for org %s", org.Id) apiPath := "/api/v1/cloud/sync/stop" syncPath := fmt.Sprintf("%s%s", syncUrl, apiPath) - err = handleStopCloudSync(syncPath, *org) + _, err = handleStopCloudSync(syncPath, *org) if err != nil { resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) @@ -6027,7 +5832,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { } interval := int(responseData.IntervalSeconds) - log.Printf("Starting cloud sync on interval %d", interval) + log.Printf("[INFO] Starting cloud sync on interval %d", interval) job := func() { err := remoteOrgJobHandler(*org, interval) if err != nil { @@ -6039,13 +5844,13 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { if err != nil { log.Printf("[CRITICAL] Failed to schedule org: %s", err) } else { - log.Printf("Started sync on interval %d for org %s", interval, org.Name) + log.Printf("[INFO] Started sync on interval %d for org %s", interval, org.Name) scheduledOrgs[org.Id] = jobret } // FIXME: Add this for every feature if org.SyncFeatures.Workflows.Active { - log.Printf("Should activate cloud workflows for org %s!", org.Id) + log.Printf("[INFO] Should activate cloud workflows for org %s!", org.Id) // 1. Find environment // 2. If cloud env found, enable it (un-archive) @@ -6063,9 +5868,9 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { environment.Archived = false err = setEnvironment(ctx, &environment) if err == nil { - log.Printf("Re-added cloud environment %s", environment.Name) + log.Printf("[INFO] Re-added cloud environment %s", environment.Name) } else { - log.Printf("Failed to re-enable cloud environment %s", environment.Name) + log.Printf("[INFO] Failed to re-enable cloud environment %s", environment.Name) } found = true @@ -6074,7 +5879,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { } if !found { - log.Printf("Env for cloud not found. Should add it!") + log.Printf("[INFO] Env for cloud not found. Should add it!") newEnv := shuffle.Environment{ Name: "Cloud", Type: "cloud", @@ -6096,7 +5901,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { } } - err = setOrg(ctx, *org, org.Id) + err = shuffle.SetOrg(ctx, *org, org.Id) if err != nil { log.Printf("ERROR: Failed updating org even though there was success: %s", err) resp.WriteHeader(400) @@ -6106,7 +5911,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { if responseData.IntervalSeconds > 0 { // FIXME: - log.Printf("Should set up interval for %d with session key %s for org %s", responseData.IntervalSeconds, responseData.SessionKey, org.Name) + log.Printf("[INFO] Should set up interval for %d with session key %s for org %s", responseData.IntervalSeconds, responseData.SessionKey, org.Name) } resp.WriteHeader(200) @@ -6220,13 +6025,13 @@ func initHandlers() { r.HandleFunc("/api/v1/workflows/{key}/outlook", createOutlookSub).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/outlook/{triggerId}", handleDeleteOutlookSub).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/executions", getWorkflowExecutions).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/workflows/{key}/executions/{key}/abort", abortExecution).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/executions/{key}/abort", shuffle.AbortExecution).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}", deleteWorkflow).Methods("DELETE", "OPTIONS") // Triggers - r.HandleFunc("/api/v1/hooks/new", handleNewHook).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/hooks/new", shuffle.HandleNewHook).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/hooks/{key}", handleWebhookCallback).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/hooks/{key}/delete", handleDeleteHook).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/hooks/{key}/delete", shuffle.HandleDeleteHook).Methods("DELETE", "OPTIONS") // OpenAPI configuration r.HandleFunc("/api/v1/verify_swagger", verifySwagger).Methods("POST", "OPTIONS") diff --git a/backend/go-app/oauth2.go b/backend/go-app/oauth2.go index 3c03ea13..164c803d 100644 --- a/backend/go-app/oauth2.go +++ b/backend/go-app/oauth2.go @@ -817,7 +817,7 @@ func createOutlookSub(resp http.ResponseWriter, request *http.Request) { } log.Printf("[INFO] Starting cloud configuration TO START trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId) - action := CloudSyncJob{ + action := shuffle.CloudSyncJob{ Type: "outlook", Action: "start", OrgId: org.Id, @@ -1218,7 +1218,7 @@ func handleOutlookSubRemoval(ctx context.Context, user shuffle.User, workflowId, } log.Printf("[INFO] Stopping cloud configuration for trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId) - action := CloudSyncJob{ + action := shuffle.CloudSyncJob{ Type: "outlook", Action: "stop", OrgId: org.Id, diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 621aa0b6..3ff9bdf2 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1894,211 +1894,6 @@ func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) { return body, nil } -func abortExecution(resp http.ResponseWriter, request *http.Request) { - cors := handleCors(resp, request) - if cors { - return - } - - //log.Printf("\n\nINSIDE ABORT\n\n") - - location := strings.Split(request.URL.String(), "/") - var fileId string - if location[1] == "api" { - if len(location) <= 4 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - fileId = location[4] - } - - if len(fileId) != 36 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Workflow ID to abort is not valid"}`)) - return - } - - executionId := location[6] - if len(executionId) != 36 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "ExecutionID not valid"}`)) - return - } - - ctx := context.Background() - workflowExecution, err := shuffle.GetWorkflowExecution(ctx, executionId) - if err != nil { - log.Printf("[ERROR] Failed getting execution (abort) %s: %s", executionId, err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist (abort)."}`, executionId))) - return - } - - apikey := request.Header.Get("Authorization") - parsedKey := "" - if strings.HasPrefix(apikey, "Bearer ") { - apikeyCheck := strings.Split(apikey, " ") - if len(apikeyCheck) == 2 { - parsedKey = apikeyCheck[1] - } - } - - if workflowExecution.Authorization != parsedKey { - // FIXME: Check the execution if this fails. - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("Api authentication failed in abort workflow: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // FIXME - have a check for org etc too.. - if user.Id != workflowExecution.Workflow.Owner { - log.Printf("[INFO] Wrong user (%s) for workflowexecution workflow %s", user.Username, workflowExecution.Workflow.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - } else { - //log.Printf("[INFO] API key to abort/finish execution %s is correct.", executionId) - } - - if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" || workflowExecution.Status == "FINISHED" { - log.Printf("[INFO] Stopped execution of %s with status %s", executionId, workflowExecution.Status) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Status for %s is %s, which can't be aborted."}`, executionId, workflowExecution.Status))) - return - } - - topic := "workflowexecution" - - workflowExecution.CompletedAt = int64(time.Now().Unix()) - workflowExecution.Status = "ABORTED" - log.Printf("[INFO] Running shutdown of %s", workflowExecution.ExecutionId) - - lastResult := "" - newResults := []shuffle.ActionResult{} - // type ActionResult struct { - for _, result := range workflowExecution.Results { - if result.Status == "EXECUTING" { - result.Status = "ABORTED" - result.Result = "Aborted because of error in another node (1)" - } - - if len(result.Result) > 0 { - lastResult = result.Result - } - - newResults = append(newResults, result) - } - - workflowExecution.Results = newResults - if len(workflowExecution.Result) == 0 { - workflowExecution.Result = lastResult - } - - addResult := true - for _, result := range workflowExecution.Results { - if result.Status != "SKIPPED" { - addResult = false - } - } - - extra := 0 - for _, trigger := range workflowExecution.Workflow.Triggers { - //log.Printf("Appname trigger (0): %s", trigger.AppName) - if trigger.AppName == "User Input" || trigger.AppName == "Shuffle Workflow" { - extra += 1 - } - } - - parsedReason := "An error occurred during execution of this node" - reason, reasonok := request.URL.Query()["reason"] - if reasonok { - parsedReason = reason[0] - } - - if len(workflowExecution.Results) == 0 || addResult { - newaction := shuffle.Action{ - ID: workflowExecution.Start, - } - - for _, action := range workflowExecution.Workflow.Actions { - if action.ID == workflowExecution.Start { - newaction = action - break - } - } - - workflowExecution.Results = append(workflowExecution.Results, shuffle.ActionResult{ - Action: newaction, - ExecutionId: workflowExecution.ExecutionId, - Authorization: workflowExecution.Authorization, - Result: parsedReason, - StartedAt: workflowExecution.StartedAt, - CompletedAt: workflowExecution.StartedAt, - Status: "FAILURE", - }) - } else if len(workflowExecution.Results) >= len(workflowExecution.Workflow.Actions)+extra { - log.Printf("[INFO] DONE - Nothing to add during abort!") - } else { - //log.Printf("VALIDATING INPUT!") - node, nodeok := request.URL.Query()["node"] - if nodeok { - nodeId := node[0] - log.Printf("[INFO] Found abort node %s", nodeId) - newaction := shuffle.Action{ - ID: nodeId, - } - - for _, action := range workflowExecution.Workflow.Actions { - if action.ID == nodeId { - newaction = action - break - } - } - - workflowExecution.Results = append(workflowExecution.Results, shuffle.ActionResult{ - Action: newaction, - ExecutionId: workflowExecution.ExecutionId, - Authorization: workflowExecution.Authorization, - Result: parsedReason, - StartedAt: workflowExecution.StartedAt, - CompletedAt: workflowExecution.StartedAt, - Status: "FAILURE", - }) - } - } - - err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) - if err != nil { - log.Printf("Error saving workflow execution for updates when aborting %s: %s", topic, err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution status to abort"}`))) - return - } - - err = increaseStatisticsField(ctx, "workflow_executions_aborted", workflowExecution.Workflow.ID, 1, workflowExecution.ExecutionOrg) - if err != nil { - log.Printf("Failed to increase aborted execution stats: %s", err) - } - - // FIXME - allowed to edit it? idk - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) - - // Not sure what's up here - //if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { - // log.Printf("Workflowexecution is already aborted. No further action can be taken") - // resp.WriteHeader(401) - // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status))) - // return - //} -} - //// New execution with firestore func cleanupExecutions(resp http.ResponseWriter, request *http.Request) { @@ -3002,11 +2797,10 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("Schedule: %#v", schedule) + //log.Printf("Schedule: %#v", schedule) if schedule.Environment == "cloud" { log.Printf("[INFO] Should STOP a cloud schedule for workflow %s with schedule ID %s", fileId, scheduleId) - // https://shuffler.io/v1/hooks/webhook_80184973-3e82-4852-842e-0290f7f34d7c org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id) if err != nil { log.Printf("Failed finding org %s: %s", org.Id, err) @@ -3015,7 +2809,7 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) { // 1. Send request to cloud // 2. Remove schedule if success - action := CloudSyncJob{ + action := shuffle.CloudSyncJob{ Type: "schedule", Action: "stop", OrgId: org.Id, @@ -3328,7 +3122,6 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { if schedule.Environment == "cloud" { log.Printf("[INFO] Should START a cloud schedule for workflow %s with schedule ID %s", workflow.ID, schedule.Id) - // https://shuffler.io/v1/hooks/webhook_80184973-3e82-4852-842e-0290f7f34d7c org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id) if err != nil { log.Printf("Failed finding org %s: %s", org.Id, err) @@ -3339,7 +3132,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { // 2 = schedule (cron, frequency) // 3 = workflowId // 4 = execution argument - action := CloudSyncJob{ + action := shuffle.CloudSyncJob{ Type: "schedule", Action: "start", OrgId: org.Id, @@ -5592,123 +5385,6 @@ func handleStopHook(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(`{"success": true, "reason": "Stopped webhook"}`)) } -func handleDeleteHook(resp http.ResponseWriter, request *http.Request) { - cors := handleCors(resp, request) - if cors { - return - } - - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("Api authentication failed in set new workflowhandler: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - location := strings.Split(request.URL.String(), "/") - - var fileId string - if location[1] == "api" { - if len(location) <= 4 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - fileId = location[4] - } - - if len(fileId) != 36 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Workflow ID when deleting hook is not valid"}`)) - return - } - - ctx := context.Background() - hook, err := getHook(ctx, fileId) - if err != nil { - log.Printf("Failed getting hook %s (delete): %s", fileId, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - if user.Id != hook.Owner && user.ActiveOrg.Id != hook.OrgId { - log.Printf("Wrong user (%s) for workflow %s", user.Username, hook.Id) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - if len(hook.Workflows) > 0 { - //err = increaseStatisticsField(ctx, "total_workflow_triggers", hook.Workflows[0], -1, user.ActiveOrg.Id) - //if err != nil { - // log.Printf("Failed to increase total workflows: %s", err) - //} - } - - hook.Status = "stopped" - err = setHook(ctx, *hook) - if err != nil { - log.Printf("Failed setting hook: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - log.Printf("Hook: %#v", hook) - if hook.Environment == "cloud" { - log.Printf("[INFO] Should STOP cloud webhook https://shuffler.io/api/v1/hooks/webhook_%s", hook.Id) - org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id) - if err != nil { - log.Printf("Failed finding org %s: %s", org.Id, err) - return - } - - action := CloudSyncJob{ - Type: "webhook", - Action: "stop", - OrgId: org.Id, - PrimaryItemId: hook.Id, - } - - if len(hook.Workflows) > 0 { - action.SecondaryItem = hook.Workflows[0] - } - - err = executeCloudAction(action, org.SyncConfig.Apikey) - if err != nil { - log.Printf("Failed cloud action STOP execution: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - // https://shuffler.io/v1/hooks/webhook_80184973-3e82-4852-842e-0290f7f34d7c - } - - // This is here to force stop and remove the old webhook - //image := "webhook" - //err = removeWebhookFunction(ctx, fileId) - //if err != nil { - // log.Printf("Function removal issue for %s-%s: %s", image, fileId, err) - // if strings.Contains(err.Error(), "does not exist") { - // resp.WriteHeader(200) - // resp.Write([]byte(`{"success": true, "reason": "Stopped webhook"}`)) - - // } else { - // resp.WriteHeader(401) - // resp.Write([]byte(`{"success": false, "reason": "Couldn't stop webhook, please try again later"}`)) - // } - - // return - //} - - log.Printf("Successfully deleted webhook %s", fileId) - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true, "reason": "Stopped webhook"}`)) -} - func removeWebhookFunction(ctx context.Context, hookid string) error { service, err := cloudfunctions.NewService(ctx) if err != nil { @@ -5794,7 +5470,7 @@ func handleStartHook(resp http.ResponseWriter, request *http.Request) { environmentVariables := map[string]string{ "FUNCTION_APIKEY": user.ApiKey, - "CALLBACKURL": "https://shuffler.io", + "CALLBACKURL": syncUrl, "HOOKID": fileId, } @@ -5874,7 +5550,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId ctx := context.Background() startNode := trigger.ID if strings.Contains(triggerType, "email") { - action := CloudSyncJob{ + action := shuffle.CloudSyncJob{ Type: "user_input", Action: "send_email", OrgId: organizationId, @@ -5900,7 +5576,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId log.Printf("Should send email to %s during execution.", email) } if strings.Contains(triggerType, "sms") { - action := CloudSyncJob{ + action := shuffle.CloudSyncJob{ Type: "user_input", Action: "send_sms", OrgId: organizationId, diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 6334cc4a..dfefcd5f 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -191,6 +191,45 @@ const Admin = (props) => { }); } + const handleStopOrgSync = (org_id) => { + if (org_id === undefined || org_id === null) { + alert.error("Couldn't get org "+org_id) + return + } + + const data = {} + + const url = globalUrl + '/api/v1/orgs/' + org_id + "/stop_sync"; + fetch(url, { + mode: 'cors', + method: 'POST', + body: JSON.stringify(data), + credentials: 'include', + crossDomain: true, + withCredentials: true, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => { + if (response.status === 200) { + console.log("Cloud sync success?") + alert.success("Successfully stopped cloud sync") + } else { + console.log("Cloud sync fail?") + alert.error("Failed stopping sync. Try again, and contact support if this persists.") + } + + return response.json() + }) + .then((responseJson) => { + handleGetOrg(org_id) + }) + .catch(error => { + alert.error("Err: " + error.toString()) + }) + } + const enableCloudSync = (apikey, organization, disableSync) => { setOrgSyncResponse("") @@ -1475,27 +1514,41 @@ const Admin = (props) => { Your Apikey - - +
+ + {selectedOrganization.cloud_sync_active ? + + : null} +
+ :
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 845b5c8c..dbe016f6 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -785,6 +785,10 @@ const AngularWorkflow = (props) => { console.log(responseJson) alert.error("Failed to save: "+responseJson.reason) } else { + if (responseJson.new_id !== undefined && responseJson.new_id !== null) { + window.location.pathname = "/workflows/"+responseJson.new_id + } + success = true if (responseJson.errors !== undefined) { //console.log(responseJson) @@ -800,6 +804,7 @@ const AngularWorkflow = (props) => { setWorkflow(workflow) } + //alert.success("Successfully saved workflow") setSavingState(1) setTimeout(() => { @@ -833,6 +838,11 @@ const AngularWorkflow = (props) => { console.log("FIXME: Might have forgotten to save before executing.") } + if (workflow.public) { + alert.info("Save it to get a new version") + } + + var returncheck = monitorUpdates() if (!returncheck) { alert.error("No startnode set.") @@ -1041,7 +1051,7 @@ const AngularWorkflow = (props) => { }) .catch(error => { setAuthLoaded(true) - alert.error("Auth loading error: ", error.toString()) + alert.error("Auth loading error: "+error.toString()) }) } @@ -1110,6 +1120,11 @@ const AngularWorkflow = (props) => { responseJson.errors = [] } + if (responseJson.public) { + alert.info("This workflow is public. You'll have to save it to make it your own!") + setLastSaved(false) + } + setWorkflow(responseJson) setWorkflowDone(true) }) @@ -6256,6 +6271,14 @@ const AngularWorkflow = (props) => {

{workflow.name}

+ + {workflow.public ? +

+ Public Workflow PREVIEW +

+ : + null + }
@@ -6380,7 +6403,7 @@ const AngularWorkflow = (props) => { - + {importLoading ? + + : + + } upload = ref} onChange={importFiles} /> {workflows.length > 0 ?