From 4b784dfc11d6445d205341c90374dbecf09fd0fc Mon Sep 17 00:00:00 2001 From: frikky Date: Sun, 25 Oct 2020 12:31:36 +0100 Subject: [PATCH] Almost finished with cloud executions --- backend/go-app/main.go | 278 +++++++++++++++++++++++-- backend/go-app/walkoff.go | 188 +++++++++++++++-- frontend/src/views/Admin.jsx | 46 +++- frontend/src/views/AngularWorkflow.jsx | 2 +- frontend/src/views/Workflows.jsx | 3 + 5 files changed, 472 insertions(+), 45 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 5e8c84a2..4ef276a6 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -72,6 +72,7 @@ var gceProject = "shuffle" var bucketName = "shuffler.appspot.com" var baseAppPath = "/home/frikky/git/shaffuru/tmp/apps" var baseDockerName = "frikky/shuffle" +var syncUrl = "http://192.168.3.6:5002" var dbclient *datastore.Client @@ -139,6 +140,14 @@ type UserLimits struct { MaxWorkflows int64 `json:"max_workflows" datastore:"max_workflows"` } +type retStruct struct { + Success bool `json:"success"` + SyncFeatures SyncFeatures `json:"sync_features"` + SessionKey string `json:"session_key"` + IntervalSeconds int64 `json:"interval_seconds"` + Reason string `json:"reason"` +} + // Saves some data, not sure what to have here lol type UserAuth struct { Description string `json:"description" datastore:"description,noindex" yaml:"description"` @@ -1082,6 +1091,10 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) { } for _, item := range newEnvironments { + if item.OrgId == "" { + item.OrgId = user.ActiveOrg.Id + } + err = setEnvironment(ctx, &item) if err != nil { resp.WriteHeader(401) @@ -3327,8 +3340,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)), } + // OrgId: activeOrgs[0].Id, workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest) - if err == nil { err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1) if err != nil { @@ -6559,11 +6572,13 @@ func runInit(ctx context.Context) { log.Printf("Found %d users.", len(users)) if len(activeOrgs) == 1 && len(users) > 0 { for _, user := range users { - if user.ActiveOrg.Id == "" { + if user.ActiveOrg.Id == "" && len(user.Username) > 0 { user.ActiveOrg = activeOrgs[0] err = setUser(ctx, &user) if err != nil { - log.Printf("Failed updating user %s", user.Username) + log.Printf("Failed updating user %s with org", user.Username) + } else { + log.Printf("Updated user %s to have org", user.Username) } } } @@ -6606,6 +6621,35 @@ func runInit(ctx context.Context) { } } + // Fixing workflows to have real activeorg IDs + if len(activeOrgs) == 1 { + q := datastore.NewQuery("workflow") + var workflows []Workflow + _, err = dbclient.GetAll(ctx, q, &workflows) + if err != nil { + log.Printf("Error getting workflows in runinit: %s", err) + } else { + updated := 0 + for _, workflow := range workflows { + if workflow.ExecutingOrg.Id == "" { + workflow.ExecutingOrg = activeOrgs[0] + + err = setWorkflow(ctx, workflow, workflow.ID) + if err != nil { + log.Printf("Failed setting workflow in init: %s", err) + } else { + log.Printf("Fixed workflow %s to have the right org.", workflow.ID) + updated += 1 + } + } + } + + if updated > 0 { + log.Printf("Set workflow orgs for %d workflows", updated) + } + } + } + // Gets schedules and starts them log.Printf("Relaunching schedules") schedules, err := getAllSchedules(ctx) @@ -6746,7 +6790,136 @@ func runInit(ctx context.Context) { log.Printf("Finished INIT") } +func handleVerifyCloudsync(orgId string) (SyncFeatures, error) { + ctx := context.Background() + org, err := getOrg(ctx, orgId) + if err != nil { + return SyncFeatures{}, err + } + + //r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS") + + syncURL := fmt.Sprintf("%s/api/v1/cloud/sync/get_access", syncUrl) + client := &http.Client{} + req, err := http.NewRequest( + "GET", + syncURL, + nil, + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) + newresp, err := client.Do(req) + if err != nil { + return SyncFeatures{}, err + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + return SyncFeatures{}, err + } + + responseData := retStruct{} + err = json.Unmarshal(respBody, &responseData) + if err != nil { + return SyncFeatures{}, err + } + + if newresp.StatusCode != 200 { + return SyncFeatures{}, errors.New(fmt.Sprintf("Got status code %d when getting org remotely. Expected 200. Contact support.", newresp.StatusCode)) + } + + if !responseData.Success { + return SyncFeatures{}, errors.New(responseData.Reason) + } + + return responseData.SyncFeatures, nil +} + +// Actually stops syncing with cloud for an org. +// Disables potential schedules, removes environments, breaks workflows etc. +func handleStopCloudSync(syncUrl string, org 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)) + } + + log.Printf("Should run cloud sync disable for org %s with URL %s and sync key %s", org.Id, syncUrl, org.SyncConfig.Apikey) + + client := &http.Client{} + req, err := http.NewRequest( + "DELETE", + syncUrl, + nil, + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) + newresp, err := client.Do(req) + if err != nil { + return err + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + return err + } + log.Printf("Remote disable ret: %s", string(respBody)) + + responseData := retStruct{} + err = json.Unmarshal(respBody, &responseData) + if err != nil { + return err + } + + if newresp.StatusCode != 200 { + return 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) + } + + log.Printf("Everything is success. Should disable org sync for %s", org.Id) + + ctx := context.Background() + org.CloudSync = false + org.SyncFeatures = SyncFeatures{} + org.SyncConfig = SyncConfig{} + + err = 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) + } + + var environments []Environment + q := datastore.NewQuery("Environments").Filter("org_id =", org.Id) + _, err = dbclient.GetAll(ctx, q, &environments) + if err != nil { + return err + } + + // Don't disable, this will be deleted entirely + for _, environment := range environments { + if environment.Type == "cloud" { + environment.Name = "Cloud" + environment.Archived = true + err = setEnvironment(ctx, &environment) + if err == nil { + log.Printf("Updated cloud environment %s", environment.Name) + } else { + log.Printf("Failed to update cloud environment %s", environment.Name) + } + } + } + + return nil +} + // INFO: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit +/* + This is here to both enable and disable cloud sync features for an organization +*/ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -6778,6 +6951,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { type ReturnData struct { Apikey string `datastore:"apikey"` Organization Org `datastore:"organization"` + Disable bool `datastore:"disable"` } var tmpData ReturnData @@ -6833,7 +7007,42 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { // FIXME: Path client := &http.Client{} - syncPath := "http://192.168.3.6:5002/api/v1/cloud/sync" + apiPath := "/api/v1/cloud/sync" + if tmpData.Disable { + if !org.CloudSync { + log.Printf("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) + apiPath := "/api/v1/cloud/sync/stop" + syncUrl = fmt.Sprintf("%s%s", syncUrl, apiPath) + + err = handleStopCloudSync(syncUrl, *org) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + } else { + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Successfully disabled cloud sync for org."}`))) + } + + return + } + + // Everything below here is to SET UP CLOUD SYNC. + // If you want to disable cloud sync, see previous section. + if org.CloudSync { + log.Printf("Org %s is already syncing. Skip", org.Id) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Org is already syncing. Nothing to set up."}`))) + return + } + + syncPath := fmt.Sprintf("%s%s", syncUrl, apiPath) + type requestStruct struct { ApiKey string `json:"api_key"` } @@ -6871,14 +7080,6 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { return } - type retStruct struct { - Success bool `json:"success"` - SyncFeatures SyncFeatures `json:"sync_features"` - SessionKey string `json:"session_key"` - IntervalSeconds int64 `json:"interval_seconds"` - Reason string `json:"reason"` - } - log.Printf("Respbody: %s", string(respBody)) responseData := retStruct{} err = json.Unmarshal(respBody, &responseData) @@ -6912,6 +7113,59 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { Interval: responseData.IntervalSeconds, } + // FIXME: Add this for every feature + if org.SyncFeatures.Workflows.Active { + log.Printf("Should activate cloud workflows for org %s!", org.Id) + + // 1. Find environment + // 2. If cloud env found, enable it (un-archive) + // 3. If it doesn't create it + var environments []Environment + q := datastore.NewQuery("Environments").Filter("org_id =", org.Id) + _, err = dbclient.GetAll(ctx, q, &environments) + if err == nil { + + // Don't disable, this will be deleted entirely + found := false + for _, environment := range environments { + if environment.Type == "cloud" { + environment.Name = "Cloud" + environment.Archived = false + err = setEnvironment(ctx, &environment) + if err == nil { + log.Printf("Re-added cloud environment %s", environment.Name) + } else { + log.Printf("Failed to re-enable cloud environment %s", environment.Name) + } + + found = true + break + } + } + + if !found { + log.Printf("Env for cloud not found. Should add it!") + newEnv := Environment{ + Name: "Cloud", + Type: "cloud", + Archived: false, + Registered: true, + Default: false, + OrgId: org.Id, + } + + err = setEnvironment(ctx, &newEnv) + if err != nil { + log.Printf("Failed setting up NEW org environment for org %s: %s", org.Id, err) + } else { + log.Printf("Successfully added new environment for org %s", org.Id) + } + } + } else { + log.Printf("Failed setting org environment, because none were found: %s", err) + } + } + err = setOrg(ctx, *org, org.Id) if err != nil { log.Printf("ERROR: Failed updating org even though there was success: %s", err) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index e2f5a3a3..e0eb0eda 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -204,6 +204,7 @@ type WorkflowExecution struct { ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"` ExecutionId string `json:"execution_id" datastore:"execution_id"` ExecutionSource string `json:"execution_source" datastore:"execution_source"` + ExecutionOrg string `json:"execution_org" datastore:"execution_org"` WorkflowId string `json:"workflow_id" datastore:"workflow_id"` LastNode string `json:"last_node" datastore:"last_node"` Authorization string `json:"authorization" datastore:"authorization"` @@ -481,7 +482,7 @@ func getWorkflowQueue(ctx context.Context, id string) (ExecutionRequestWrapper, //} // Frequency = cronjob OR minutes between execution -func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency string, body []byte) error { +func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency, orgId string, body []byte) error { var err error testSplit := strings.Split(frequency, "*") cronJob := "" @@ -525,7 +526,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)), } - _, _, err := handleExecution(workflowId, Workflow{}, request) + _, _, err := handleExecution(workflowId, Workflow{ExecutingOrg: Org{Id: orgId}}, request) if err != nil { log.Printf("Failed to execute %s: %s", workflowId, err) } @@ -1355,6 +1356,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.ID = uuid.NewV4().String() workflow.Owner = user.Id workflow.Sharing = "private" + workflow.ExecutingOrg = user.ActiveOrg ctx := context.Background() log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name) @@ -1395,7 +1397,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { if err == nil { // FIXME: Add real env envName := "Shuffle" - environments, err := getEnvironments(ctx) + environments, err := getEnvironments(ctx, user.ActiveOrg.Id) if err == nil { for _, env := range environments { if env.Default { @@ -1679,8 +1681,6 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { return } - //Actions []Action `json:"actions" datastore:"actions,noindex"` - body, err := ioutil.ReadAll(request.Body) if err != nil { log.Printf("Failed hook unmarshaling: %s", err) @@ -1712,6 +1712,10 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.Owner = user.Id } + if len(workflow.ExecutingOrg.Id) == 0 { + workflow.ExecutingOrg = user.ActiveOrg + } + // FIXME - this shouldn't be necessary with proper API checks newActions := []Action{} allNodes := []string{} @@ -2305,6 +2309,11 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf workflow = *tmpworkflow } + if len(workflow.ExecutingOrg.Id) == 0 { + log.Printf("Stopped execution because there is no executing org for workflow %s", workflow.ID) + return WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined") + } + if len(workflow.Actions) == 0 { workflow.Actions = []Action{} } @@ -2397,6 +2406,8 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf } } else { // Check for parameters of start and ExecutionId + // This is mostly used for user input trigger + start, startok := request.URL.Query()["start"] answer, answerok := request.URL.Query()["answer"] referenceId, referenceok := request.URL.Query()["reference_execution"] @@ -2642,31 +2653,83 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf // Verification for execution environments workflowExecution.Results = defaultResults workflowExecution.Workflow.Actions = newActions - onpremExecution := false + onpremExecution := true environments := []string{} + if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 { + workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id + } + + var allEnvs []Environment + if len(workflowExecution.ExecutionOrg) > 0 { + log.Printf("Executing ORG: %s", workflowExecution.ExecutionOrg) + + allEnvironments, err := getEnvironments(ctx, workflowExecution.ExecutionOrg) + if err != nil { + log.Printf("Failed finding environments: %s", err) + return WorkflowExecution{}, fmt.Sprintf("Workflow environments not found for this org"), errors.New(fmt.Sprintf("Workflow environments not found for this org")) + } + + for _, curenv := range allEnvironments { + if curenv.Archived { + continue + } + + allEnvs = append(allEnvs, curenv) + } + } else { + log.Printf("[ERROR] No org identified for execution of %s. Returning", workflowExecution.Workflow.ID) + return WorkflowExecution{}, "No org identified for execution", errors.New("No org identified for execution") + } + + if len(allEnvs) == 0 { + log.Printf("[ERROR] No active environments found for org", workflowExecution.ExecutionOrg) + return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No active env found for org %s", workflowExecution.ExecutionOrg)) + } + // Check if the actions are children of the startnode? imageNames := []string{} + cloudExec := false for _, action := range workflowExecution.Workflow.Actions { - if action.Environment != cloudname { - found := false - for _, env := range environments { - if env == action.Environment { - found = true - break + // Verify if the action environment exists and append + found := false + for _, env := range allEnvs { + if env.Name == action.Environment { + found = true + + if env.Type == "cloud" { + cloudExec = true + } else if env.Type == "onprem" { + onpremExecution = true + } else { + log.Printf("[ERROR] No handler for environment type %s", env.Type) + return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No handler for environment type %s", env.Type)) } + break } + } - // Check if the app exists? - newName := action.AppName - newName = strings.ReplaceAll(newName, " ", "-") - imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion)) + if !found { + log.Printf("[ERROR] Couldn't find environment %s. Maybe it's inactive?", action.Environment) + return WorkflowExecution{}, "Couldn't find the environment", errors.New(fmt.Sprintf("Couldn't find env %s in org %s", action.Environment, workflowExecution.ExecutionOrg)) + } - if !found { - environments = append(environments, action.Environment) + found = false + for _, env := range environments { + if env == action.Environment { + + found = true + break } + } - onpremExecution = true + // Check if the app exists? + newName := action.AppName + newName = strings.ReplaceAll(newName, " ", "-") + imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion)) + + if !found { + environments = append(environments, action.Environment) } } @@ -2711,8 +2774,22 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf log.Printf("Failed adding to db: %s", err) } } - } else { - log.Printf("[ERROR] Cloud not implemented yet") + } + + // Verifies and runs cloud executions + if cloudExec { + featuresList, err := handleVerifyCloudsync(workflowExecution.ExecutionOrg) + if !featuresList.Workflows.Active || err != nil { + log.Printf("Error: %s", err) + log.Printf("[ERROR] Cloud not implemented yet. May need to work on app checking and such") + return WorkflowExecution{}, "Cloud not implemented yet", errors.New("Cloud not implemented yet") + } + + if len(workflowExecution.Workflow.Actions) == 1 { + log.Printf("Should execute directly with cloud instead of worker because only one action") + cloudExecuteAction(workflowExecution.ExecutionId, workflowExecution.Workflow.Actions[0], workflowExecution.ExecutionOrg) + return WorkflowExecution{}, "Cloud not implemented yet", errors.New("Cloud not implemented yet") + } } err = increaseStatisticsField(ctx, "workflow_executions", workflow.ID, 1) @@ -2723,6 +2800,69 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf return workflowExecution, "", nil } +// This updates stuff locally from remote executions +func cloudExecuteAction(workflowExecutionId string, action Action, orgId string) error { + log.Printf("Executing action: %#v in execution ID %s", action, workflowExecutionId) + ctx := context.Background() + org, err := getOrg(ctx, orgId) + if err != nil { + return err + } + + type ExecutionStruct struct { + ID string `json:"id"` + Action Action `json:"action"` + } + data := ExecutionStruct{ + ID: workflowExecutionId, + Action: action, + } + + b, err := json.Marshal(data) + if err != nil { + log.Printf("Failed marshaling api key data: %s", err) + return err + } + + syncURL := fmt.Sprintf("%s/api/v1/cloud/sync/execute_node", syncUrl) + client := &http.Client{} + req, err := http.NewRequest( + "POST", + syncURL, + bytes.NewBuffer(b), + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) + newresp, err := client.Do(req) + if err != nil { + return err + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + return err + } + + log.Printf("Finished request. Data: %s", string(respBody)) + log.Printf("Status code: %d", newresp.StatusCode) + + responseData := retStruct{} + err = json.Unmarshal(respBody, &responseData) + if err != nil { + return err + } + + if newresp.StatusCode != 200 { + return errors.New(fmt.Sprintf("Got status code %d when executing remotely. Expected 200. Contact support.", newresp.StatusCode)) + } + + if !responseData.Success { + return errors.New(responseData.Reason) + } + + return nil +} + func executeWorkflow(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -2776,6 +2916,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { } log.Printf("[INFO] Starting execution of %s!", fileId) + workflow.ExecutingOrg = user.ActiveOrg workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request) if err == nil { @@ -3137,6 +3278,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { schedule.Name, startNode, schedule.Frequency, + user.ActiveOrg.Id, []byte(parsedBody), ) @@ -3336,9 +3478,9 @@ func getWorkflow(ctx context.Context, id string) (*Workflow, error) { return workflow, nil } -func getEnvironments(ctx context.Context) ([]Environment, error) { +func getEnvironments(ctx context.Context, OrgId string) ([]Environment, error) { var environments []Environment - q := datastore.NewQuery("Environments") + q := datastore.NewQuery("Environments").Filter("org_id =", OrgId) _, err := dbclient.GetAll(ctx, q, &environments) if err != nil { diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 0eb76a20..298080f8 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -196,12 +196,13 @@ const Admin = (props) => { }); } - const enableCloudSync = (apikey, organization) => { + const enableCloudSync = (apikey, organization, disableSync) => { setOrgSyncResponse("") const data = { apikey: apikey, organization: organization, + disable: disableSync, } const url = globalUrl + '/api/v1/cloud/setup'; @@ -229,13 +230,20 @@ const Admin = (props) => { .then((responseJson) => { if (!responseJson.success && responseJson.reason !== undefined) { setOrgSyncResponse(responseJson.reason) - alert.error("Failed to sync: "+responseJson.reason) + alert.error("Failed to handle sync: "+responseJson.reason) } else if (!responseJson.success) { - alert.error("Failed to sync.") + alert.error("Failed to handle sync.") } else { - alert.success("Sync set up!") getOrgs() - //setCloudSyncModalOpen(false) + if (disableSync) { + alert.success("Successfully disabled sync!") + } else { + alert.success("Sync successfully set up!") + } + + selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync + setSelectedOrganization(selectedOrganization) + setCloudSyncApikey("") } }) .catch(error => { @@ -389,9 +397,15 @@ const Admin = (props) => { for (var key in environments) { if (environments[key].Name == name) { if (environments[key].default) { - alert.info("Can't delete the default environment") + alert.error("Can't delete the default environment") return } + + if (environments[key].type === "cloud") { + alert.error("Can't delete the cloud environments") + return + } + environments[key].archived = true } @@ -881,6 +895,7 @@ const Admin = (props) => { }} required fullWidth={true} + disabled={selectedOrganization.cloud_sync} autoComplete="cloud apikey" id="apikey_field" margin="normal" @@ -890,14 +905,19 @@ const Admin = (props) => { setCloudSyncApikey(event.target.value) }} /> - {orgSyncResponse.length > 0 ? @@ -910,7 +930,7 @@ const Admin = (props) => { {syncList.map((data, index) => { return ( - + ) })} @@ -1369,6 +1389,10 @@ const Admin = (props) => { primary="Orborus running (TBD)" style={{minWidth: 200, maxWidth: 200}} /> + { primary={"TBD"} style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}} /> + { jsonvalid = false } } catch (e) { - console.log("Error: ", e) + //console.log("Error: ", e) jsonvalid = false } diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index ad27220d..94a86e06 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -337,6 +337,9 @@ const Workflows = (props) => { } } + data.execution_org = {"id": ""} + console.log(data) + let linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', exportFileDefaultName);