diff --git a/backend/go-app/main.go b/backend/go-app/main.go index edd8e863..e775ebf5 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1506,49 +1506,6 @@ func getUserCount() (int, error) { return count, nil } -func handleGetSchedules(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 - } - - if user.Role != "admin" { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Admin required"}`)) - return - } - - ctx := context.Background() - schedules, err := getAllSchedules(ctx, user.ActiveOrg.Id) - if err != nil { - log.Printf("Failed getting schedules: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Couldn't get schedules"}`)) - return - } - - newjson, err := json.Marshal(schedules) - if err != nil { - log.Printf("Failed unmarshal: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking environments"}`))) - return - } - - //log.Printf("Existing environments: %s", string(newjson)) - - resp.WriteHeader(200) - resp.Write(newjson) -} - func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -5379,7 +5336,7 @@ func runInit(ctx context.Context) { // Gets schedules and starts them log.Printf("Relaunching schedules") - schedules, err := getAllSchedules(ctx, "ALL") + schedules, err := shuffle.GetAllSchedules(ctx, "ALL") if err != nil { log.Printf("Failed getting schedules during service init: %s", err) } else { @@ -6214,18 +6171,18 @@ func initHandlers() { /* Everything below here increases the counters*/ r.HandleFunc("/api/v1/workflows", shuffle.GetWorkflows).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/workflows", shuffle.SetNewWorkflow).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/workflows/{key}", shuffle.GetSpecificWorkflow).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/workflows/{key}", shuffle.SaveWorkflow).Methods("PUT", "OPTIONS") - r.HandleFunc("/api/v1/workflows/schedules", handleGetSchedules).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/schedules", shuffle.HandleGetSchedules).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/executions", shuffle.GetWorkflowExecutions).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/executions/{key}/abort", shuffle.AbortExecution).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/schedule", scheduleWorkflow).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/download_remote", loadSpecificWorkflows).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/schedule/{schedule}", stopSchedule).Methods("DELETE", "OPTIONS") 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", shuffle.GetWorkflowExecutions).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") + r.HandleFunc("/api/v1/workflows/{key}", shuffle.SaveWorkflow).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}", shuffle.GetSpecificWorkflow).Methods("GET", "OPTIONS") // Triggers r.HandleFunc("/api/v1/hooks/new", shuffle.HandleNewHook).Methods("POST", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 3b780cdf..20ab9791 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1079,7 +1079,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { } if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" { - log.Printf("SHOULD WAIT A BIT AND RUN CLOUD STUFF WITH USER INPUT! WAITING!") + log.Printf("[INFO] SHOULD WAIT A BIT AND RUN CLOUD STUFF WITH USER INPUT! WAITING!") var trigger shuffle.Trigger err = json.Unmarshal([]byte(actionResult.Result), &trigger) @@ -4919,187 +4919,6 @@ func getWorkflowExecutions(resp http.ResponseWriter, request *http.Request) { resp.Write(newjson) } -func getAllSchedules(ctx context.Context, orgId string) ([]ScheduleOld, error) { - var schedules []ScheduleOld - - q := datastore.NewQuery("schedules").Filter("org = ", orgId) - if orgId == "ALL" { - q = datastore.NewQuery("schedules") - } - - _, err := dbclient.GetAll(ctx, q, &schedules) - if err != nil { - return []ScheduleOld{}, err - } - - return schedules, nil -} - -//FIXME: Add cursor -//func shuffle.GetAllWorkflowApps(ctx context.Context, maxLen int) ([]shuffle.WorkflowApp, error) { -// var apps []WorkflowApp -// query := datastore.NewQuery("workflowapp").Order("-edited").Limit(10) -// //query := datastore.NewQuery("workflowapp").Order("-edited").Limit(40) -// -// cacheKey := fmt.Sprintf("workflowapps-sorted-%d", maxLen) -// if value, found := requestCache.Get(cacheKey); found { -// parsedValue := value.(*[]WorkflowApp) -// log.Printf("[INFO] Returning %d apps from cache", len(*parsedValue)) -// return *parsedValue, nil -// } -// -// cursorStr := "" -// -// // NOT BEING UPDATED -// // FIXME: Update the app with the correct actions. HOW DOES THIS WORK?? -// // Seems like only actions are wrong. Could get the app individually. -// // Guessing it's a memory issue. -// //Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions,noindex"` -// //errors.New(nil) -// var err error -// for { -// it := dbclient.Run(ctx, query) -// //_, err = it.Next(&app) -// for { -// var app WorkflowApp -// _, err := it.Next(&app) -// if err != nil { -// break -// } -// -// if app.Name == "Shuffle Subflow" { -// continue -// } -// -// found := false -// //log.Printf("ACTIONS: %d - %s", len(app.Actions), app.Name) -// for _, innerapp := range apps { -// if innerapp.Name == app.Name { -// found = true -// break -// } -// } -// -// if !found { -// apps = append(apps, app) -// } -// } -// -// if err != iterator.Done { -// //log.Printf("[INFO] Failed fetching results: %v", err) -// //break -// } -// -// // Get the cursor for the next page of results. -// nextCursor, err := it.Cursor() -// if err != nil { -// log.Printf("Cursorerror: %s", err) -// break -// } else { -// //log.Printf("NEXTCURSOR: %s", nextCursor) -// nextStr := fmt.Sprintf("%s", nextCursor) -// if cursorStr == nextStr { -// break -// } -// -// cursorStr = nextStr -// query = query.Start(nextCursor) -// //cursorStr = nextCursor -// //break -// } -// -// if len(apps) > maxLen { -// break -// } -// } -// -// if len(apps) > 20 { -// log.Printf("[INFO] Setting %d apps in cache", len(apps)) -// requestCache.Set(cacheKey, &apps, cache.DefaultExpiration) -// } -// -// //var allworkflowapps []WorkflowApp -// //_, err := dbclient.GetAll(ctx, query, &allworkflowapps) -// //if err != nil { -// // if strings.Contains(fmt.Sprintf("%s", err), "ResourceExhausted") { -// // //datastore.NewQuery("workflowapp").Limit(30).Order("-edited") -// // query = datastore.NewQuery("workflowapp").Order("-edited").Limit(25) -// // //q := q.Limit(25) -// // _, err := dbclient.GetAll(ctx, query, &allworkflowapps) -// // if err != nil { -// // return []WorkflowApp{}, err -// // } -// // } else { -// // return []WorkflowApp{}, err -// // } -// //} -// -// return apps, nil -//} - -//func shuffle.GetAllWorkflowAppAuth(ctx context.Context, OrgId string) ([]shuffle.AppAuthenticationStorage, error) { -// var allworkflowapps []AppAuthenticationStorage -// q := datastore.NewQuery("workflowappauth").Filter("org_id = ", OrgId) -// -// _, err := dbclient.GetAll(ctx, q, &allworkflowapps) -// if err != nil { -// return []AppAuthenticationStorage{}, err -// } -// -// return allworkflowapps, nil -//} -// -//func getWorkflowAppAuthDatastore(ctx context.Context, id string) (*AppAuthenticationStorage, error) { -// -// key := datastore.NameKey("workflowappauth", id, nil) -// appAuth := &AppAuthenticationStorage{} -// // New struct, to not add body, author etc -// if err := dbclient.Get(ctx, key, appAuth); err != nil { -// return &AppAuthenticationStorage{}, err -// } -// -// return appAuth, nil -//} -// -//func shuffle.SetWorkflowAppAuthDatastore(ctx context.Context, workflowappauth AppAuthenticationStorage, id string) error { -// timeNow := int64(time.Now().Unix()) -// if workflowappauth.Created == 0 { -// workflowappauth.Created = timeNow -// } -// -// workflowappauth.Edited = timeNow -// -// key := datastore.NameKey("workflowappauth", id, nil) -// -// // New struct, to not add body, author etc -// if _, err := dbclient.Put(ctx, key, &workflowappauth); err != nil { -// log.Printf("Error adding workflow app auth: %s", err) -// return err -// } -// -// return nil -//} -// -//// Hmm, so I guess this should use uuid :( -//// Consistency PLX -//func SetWorkflowAppDatastore(ctx context.Context, workflowapp WorkflowApp, id string) error { -// timeNow := int64(time.Now().Unix()) -// if workflowapp.Created == 0 { -// workflowapp.Created = timeNow -// } -// -// workflowapp.Edited = timeNow -// key := datastore.NameKey("workflowapp", id, nil) -// -// // New struct, to not add body, author etc -// if _, err := dbclient.Put(ctx, key, &workflowapp); err != nil { -// log.Printf("Error adding workflow app: %s", err) -// return err -// } -// -// return nil -//} - // Starts a new webhook func handleStopHook(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) @@ -5372,7 +5191,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId return err } - log.Printf("Should send email to %s during execution.", email) + log.Printf("[INFO] Should send email to %s during execution.", email) } if strings.Contains(triggerType, "sms") { action := shuffle.CloudSyncJob{ diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 0e83f81b..b311dc95 100644 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -1,13 +1,24 @@ import React, {useState} from 'react'; -import {Typography, } from '@material-ui/core'; +import { InputAdornment, Tooltip, TextField, CircularProgress, ButtonGroup, Button, Avatar, ListItemAvatar, Typography, List, ListItem, ListItemText} from '@material-ui/core'; +import {FavoriteBorder as FavoriteBorderIcon} from '@material-ui/icons'; +// Handles workflow updates on first open to highlight the issues of the workflow +// Variables +// Action (exists, missing fields) +// Action auth +// Triggers +// +// Specifically used for UNSAVED workflows only? const Workflow = (props) => { - const { workflow, appAuthentication, apps } = props + const { globalUrl, theme, workflow, appAuthentication, setSelectedAction, setAuthenticationModalOpen, setSelectedApp, apps, selectedAction,setConfigureWorkflowModalOpen, saveWorkflow, newWebhook, submitSchedule, referenceUrl, isCloud, } = props const [requiredActions, setRequiredActions] = React.useState([]) + const [requiredVariables, setRequiredVariables] = React.useState([]) + const [requiredTriggers, setRequiredTriggers] = React.useState([]) + const [previousAuth, setPreviousAuth] = React.useState(appAuthentication) const [firstLoad, setFirstLoad] = React.useState("") - - // Rofl + var finished = false + if (workflow === undefined || workflow === null) { return null } @@ -20,63 +31,349 @@ const Workflow = (props) => { return null } + const getApp = (actionId, appId) => { + fetch(globalUrl+"/api/v1/apps/"+appId+"/config?openapi=false", { + headers: { + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + //alert.success("Successfully GOT app "+appId) + } else { + alert.error("Failed getting app") + } + + return response.json() + }) + .then((responseJson) => { + console.log("ACTION: ", responseJson) + if (responseJson.actions !== undefined && responseJson.actions !== null) { + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + if (firstLoad.length === 0 || firstLoad !== workflow.id) { + if (finished) { + setConfigureWorkflowModalOpen(false) + return null + } + setFirstLoad(workflow.id) const newactions = [] for (var key in workflow.actions) { + const action = workflow.actions[key] var newaction = { - "large_image": "", - "app_name": "", - "app_version": "", + "large_image": action.large_image, + "app_name": action.app_name, + "app_version": action.app_version, + "activation_done": false, "must_activate": false, "must_authenticate": false, + "auth_done": false, "action_ids": [], + "action": action, + "app": {}, } - const action = workflow.actions[key] - console.log(action) - const app = apps.find(app => app.name === action.app_name && app.app_version === action.app_version) + const app = apps.find(app => app.name === action.app_name && (app.app_version === action.app_version || app.loop_versions.includes(action.app_version))) if (app === undefined || app === null) { - console.log("COULDNT FIND APP - SEARCH BACKEND") + console.log("App not found!") - newaction.app_name = action.app_name - newaction.app_version = action.app_version + newaction.must_activate = true } else { - newaction.app_name = app.name - newaction.app_version = app.app_version - - console.log("APP: ", app) if (action.authentication_id === "" && app.authentication.required === true) { - console.log("Requires auth!") newaction.must_authenticate = true newaction.action_ids.push(action.id) } - //newaction.app_name = action.app_name - //newaction.app_name = action.app_version + newaction.app = app } if (action.errors !== undefined && action.errors !== null && action.errors.length > 0) { - console.log("Has errors!") + console.log("Node has errors!: ", action.errors) } - console.log("NEWACTION: ", newaction) - if (newaction.must_authenticate || newaction.must_activate) { - newactions.push(newaction) + if (newaction.must_authenticate) { + var authenticationOptions = [] + for (var key in appAuthentication) { + const auth = appAuthentication[key] + if (auth.app.name === app.name && auth.active) { + console.log("Found auth: ", auth) + authenticationOptions.push(auth) + newaction.authenticationId = auth.id + break + } + } + + console.log("APPAUTH: ", app.authentication, action) + if (newaction.authenticationId === null || newaction.authenticationId === undefined || newaction.authenticationId.length === "") { + console.log("FAILED to authentication node!") + newactions.push(newaction) + } else { + console.log("Skipping node as it's already authenticated.") + newaction.authentication = authenticationOptions + workflow.actions[key] = newaction + } + } else if (newaction.must_activate) { + + if (newactions.find(tmpaction => tmpaction.app_id === newaction.app_id && tmpaction.app_name === newaction.app_name) !== undefined) { + console.log("Action already found.") + } else { + newactions.push(newaction) + } } } + for (var key in workflow.workflow_variables) { + const variable = workflow.workflow_variables[key] + if (variable.value === undefined || variable.value === undefined || variable.value.length < 2) { + variable.value = "" + variable.index = key + requiredVariables.push(variable) + } + } + + for (var key in workflow.triggers) { + var trigger = workflow.triggers[key] + trigger.index = key + + if (trigger.status === "running") { + continue + } + + if (trigger.trigger_type === "SUBFLOW" || trigger.trigger_type === "USERINPUT") { + continue + } + + requiredTriggers.push(trigger) + } + + if (requiredTriggers.length === 0 && requiredVariables.length === 0 && newactions.length === 0) { + setConfigureWorkflowModalOpen(false) + } + + console.log("VARIABLES: ", requiredVariables) console.log("ACTIONS: ", newactions) + setRequiredTriggers(requiredTriggers) + setRequiredVariables(requiredVariables) setRequiredActions(newactions) } + console.log("AUTH: ", appAuthentication) + if (appAuthentication.length !== previousAuth.length) { + console.log("APP AUTH CHANGED!") + var newactions = [] + for (var actionkey in requiredActions) { + var newaction = requiredActions[actionkey] + const app = newaction.app + + for (var key in appAuthentication) { + const auth = appAuthentication[key] + if (auth.app.name === app.name && auth.active) { + console.log("FOUND AUTH FOR: ", auth.app.name) + newaction.auth_done = true + break + } + } + + newactions.push(newaction) + } + + setRequiredActions(newactions) + setPreviousAuth(appAuthentication) + // Set auth done to true + //"auth_done": false + } + + const TriggerSection = (props) => { + const {trigger} = props + + console.log(trigger) + + return ( +