diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index c2a3f72e..b907f5ed 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -2782,7 +2782,7 @@ class AppBase: "exception": f"TypeError: {e}", }) - # Forcing async wait in case of old apps that use async + # Forcing async wait in case of old apps that use async (backwards compatibility) try: if asyncio.iscoroutine(newres): self.logger.info("[DEBUG] In coroutine (1)") @@ -2802,7 +2802,7 @@ class AppBase: self.logger.info("\n[INFO] Returned from execution with types %s" % type(newres)) #self.logger.info("\n[INFO] Returned from execution with %s of types %s" % (newres, type(newres)))#, newres) if isinstance(newres, tuple): - self.logger.info("[INFO] Handling return as tuple") + self.logger.info(f"[INFO] Handling return as tuple: {newres}") # Handles files. filedata = "" file_ids = [] @@ -2859,111 +2859,6 @@ class AppBase: if isinstance(results, dict) or isinstance(results, list): json_object = True - #for i in range(0, minlength): - # # To be able to use the results as a list: - # self.logger.info("1: %s" % multi_parameters) - # #baseparams = json.loads(json.dumps(multi_parameters)) - # baseparams = copy.deepcopy(multi_parameters) - - # self.logger.info("2: %s: %s" % (type(baseparams), baseparams)) - - # self.logger.info("4") - # self.logger.info("Running with params (1): %s" % baseparams) - - # results = await self.run_recursed_items(func, baseparams, {}) - # if isinstance(results, dict) or isinstance(results, list): - # json_object = True - - # {'call': ['GoogleSafebrowsing_2_0', 'VirusTotal_GetReport_3_0']} - # 1. Check if list length is same as minlength - # 2. If NOT same length, duplicate based on length of array - # arraylength = 3 ["1", "2", "3"] - # arraylength = 4 ["1", "2", "3", "4"] - # minlength = 12 - 12/3 = 4 per item = ["1", "1", "1", "1", "2", "2", ...] - - #try: - # firstlist = True - # for key, value in baseparams.items(): - # self.logger.info("Itemtype: %s" % type(value)) - # if isinstance(value, list): - # try: - # newvalue = value[i] - # except IndexError: - # pass - - # if len(value) != minlength and len(value) > 0: - # newarray = [] - # self.logger.info("VALUE: ", value) - # additiontime = minlength/len(value) - # self.logger.info("Bad length for value: %d - should be %d. Additiontime: %d" % (len(value), minlength, additiontime)) - # if firstlist: - # self.logger.info("Running normal list (FIRST)") - # for subvalue in value: - # for number in range(int(additiontime)): - # newarray.append(subvalue) - # else: - # #self.logger.info("Running secondary lists") - # ## 1. Set up length of array - # ## 2. Put values spread out - # # FIXME: This works well, except if lists are same length - # newarray = [""] * minlength - - # cnt = 0 - # for number in range(int(additiontime)): - # for subvaluerange in range(len(value)): - # # newlocation = number+(additiontime*subvaluerange) - # # self.logger.info("%d+(%d*%d) = %d. VAL: %s" % (number, additiontime, subvaluerange, newlocation, value[subvaluerange])) - # # Reverse if same length? - # if int(minlength/len(value)) == len(value): - # tmp = int(len(value)-subvaluerange-1) - # self.logger.info("NEW: %d" % tmp) - # newarray[cnt] = value[tmp] - # else: - # newarray[cnt] = value[subvaluerange] - # cnt += 1 - - # #self.logger.info("Newarray =", newarray) - # newvalue = newarray[i] - # firstlist = False - - # baseparams[key] = newvalue - - # self.logger.info("3") - #except IndexError as e: - # self.logger.info("IndexError: %s" % e) - # baseparams[key] = "IndexError: %s" % e - #except KeyError as e: - # self.logger.info("KeyError: %s" % e) - # baseparams[key] = "KeyError: %s" % e - #self.logger.info("4") - #self.logger.info("Running with params (1): %s" % baseparams) - - #results = await self.run_recursed_items(func, baseparams, {}) - #if isinstance(results, dict) or isinstance(results, list): - # json_object = True - - # Check the structure here. If "isloop", try to recurse? - # ret, is_loop = recurse_json(innervalue, parsersplit[outercnt+1:]) - #ret = await func(**baseparams) - #self.logger.info("Return from execution: %s" % ret) - #if ret == None: - # results.append("") - # json_object = False - #elif isinstance(ret, dict) or isinstance(ret, list): - # results.append(ret) - # json_object = True - #else: - # ret = ret.replace("\"", "\\\"", -1) - - # try: - # results.append(json.loads(ret)) - # json_object = True - # except json.decoder.JSONDecodeError as e: - # #self.logger.info("Json: %s" % e) - # results.append(ret) - - #self.logger.info("Inner ret parsed: %s" % ret) - # Dump the result as a string of a list #self.logger.info("RESULTS: %s" % results) if isinstance(results, list) or isinstance(results, dict): diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index 7ff5c0b1..f5a74fc7 100644 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -801,3 +801,111 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { //resp.WriteHeader(200) } + +func activateWorkflowAppDocker(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("[WARNING] Api authentication failed in get active apps: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Role == "org-reader" { + log.Printf("[WARNING] Org-reader doesn't have access to activate workflow app (shared): %s (%s)", user.Username, user.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Read only user"}`)) + return + } + + ctx := context.Background() + 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] + } + + app, err := shuffle.GetApp(ctx, fileId, user, false) + if err != nil { + appName := request.URL.Query().Get("app_name") + appVersion := request.URL.Query().Get("app_version") + + if len(appName) > 0 && len(appVersion) > 0 { + apps, err := shuffle.FindWorkflowAppByName(ctx, appName) + //log.Printf("[INFO] Found %d apps for %s", len(apps), appName) + if err != nil || len(apps) == 0 { + log.Printf("[WARNING] Error getting app %s (app config): %s", appName, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + + selectedApp := shuffle.WorkflowApp{} + for _, app := range apps { + if !app.Sharing && !app.Public { + continue + } + + if app.Name == appName { + selectedApp = app + } + + if app.Name == appName && app.AppVersion == appVersion { + selectedApp = app + } + } + + app = &selectedApp + } else { + log.Printf("[WARNING] Error getting app with ID %s (app config): %s", fileId, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`)) + return + } + } + + if app.Sharing || app.Public { + org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id) + if err == nil { + added := false + if !shuffle.ArrayContains(org.ActiveApps, app.ID) { + org.ActiveApps = append(org.ActiveApps, app.ID) + added = true + } + + if added { + err = shuffle.SetOrg(ctx, *org, org.Id) + if err != nil { + log.Printf("[WARNING] Failed setting org when autoadding apps on save: %s", err) + } else { + log.Printf("[INFO] Added public app %s (%s) to org %s (%s)", app.Name, app.ID, user.ActiveOrg.Name, user.ActiveOrg.Id) + cacheKey := fmt.Sprintf("apps_%s", user.Id) + shuffle.DeleteCache(ctx, cacheKey) + } + } + } + } else { + log.Printf("[WARNING] User is trying to activate %s which is NOT public", app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("[DEBUG] App %s (%s) activated for org %s by user %s", app.Name, app.ID, user.ActiveOrg.Id, user.Username) + + // If onprem, it should autobuild the container(s) from here + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 61de2288..e34d2c91 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5764,7 +5764,7 @@ func initHandlers() { // App specific // From here down isnt checked for org specific - r.HandleFunc("/api/v1/apps/{appId}/activate", shuffle.ActivateWorkflowApp).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/apps/{appId}/activate", activateWorkflowAppDocker).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps/frameworkConfiguration", shuffle.GetFrameworkConfiguration).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps/frameworkConfiguration", shuffle.SetFrameworkConfiguration).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/{appId}", shuffle.UpdateWorkflowAppConfig).Methods("PATCH", "OPTIONS") diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index c559b3b8..eb1debea 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1425,13 +1425,19 @@ const AngularWorkflow = (defaultprops) => { responseJson.filter((app) => internalIds.includes(app.name)) ); } else { - setFilteredApps( - responseJson.filter( - (app) => - !internalIds.includes(app.name) && - !(!app.activated && app.generated) - ) - ); + //setFilteredApps( + // responseJson.filter( + // (app) => + // !internalIds.includes(app.name) && + // !(!app.activated && app.generated) + // ) + //); + + var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name)) + tmpFiltered = sortByKey(tmpFiltered, "activated") + setFilteredApps(tmpFiltered) + + //!(!app.activated && app.generated) setPrioritizedApps( responseJson.filter((app) => internalIds.includes(app.name)) ); @@ -1781,6 +1787,29 @@ const AngularWorkflow = (defaultprops) => { return; } + const connected = event.target.connectedEdges().jsons() + for (var key in connected) { + const edge = connected[key] + //console.log("EDGE:", edge) + + //const edge = edgeBase.json() + + const sourcenode = cy.getElementById(edge.data.source) + const destinationnode = cy.getElementById(edge.data.target) + if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { + continue + } + + const edgeCurve = calculateEdgeCurve(sourcenode.data(), destinationnode.data()) + const currentedge = cy.getElementById(edge.data.id) + if (currentedge !== undefined && currentedge !== null) { + currentedge.style('control-point-distance', edgeCurve.distance) + currentedge.style('control-point-weight', edgeCurve.weight) + } + + + } + if (styledElements.length === 1) { console.log( "Should reset location and autofill: ", @@ -1932,6 +1961,8 @@ const AngularWorkflow = (defaultprops) => { return; } + + /* // Tried looking for the closest node by position. aStar path not working entirely. console.log("NODE: ", event.target) @@ -2382,6 +2413,34 @@ const AngularWorkflow = (defaultprops) => { }); }; + const activateApp = (appid) => { + fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Failed to activate") + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success === false) { + alert.error("Failed to activate the app") + } else { + alert.success("App activated for your organization!") + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + const GetExampleResult = (item) => { var exampledata = item.example === undefined ? "" : item.example; if (workflowExecutions.length > 0) { @@ -3426,6 +3485,7 @@ const AngularWorkflow = (defaultprops) => { console.log("START"); cy.removeListener("select"); }); + cy.on("boxend", (e) => { console.log("END"); cy.removeListener("select"); @@ -3726,7 +3786,7 @@ const AngularWorkflow = (defaultprops) => { return; } - event.target.removeStyle(); + //event.target.removeStyle(); }; // This is here to have a proper transition for lines @@ -3756,6 +3816,7 @@ const AngularWorkflow = (defaultprops) => { ) { console.log(sourcecolor) console.log(targetcolor) + if (event.target !== null && event.target.value !== null) { event.target.animate({ style: { @@ -3779,7 +3840,52 @@ const AngularWorkflow = (defaultprops) => { } } - }; + } + + // Thanks :) + // https://codepen.io/guillaumethomas/pen/xxbbBKO + const calculateEdgeCurve = (sourcenode, destinationnode) => { + const xParsed = destinationnode.position.x - sourcenode.position.x + const yParsed = destinationnode.position.y - sourcenode.position.y + + const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed); + const costheta = xParsed / z; + const alpha = 0.25; + var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta]; + var controlPointWeight = [alpha, 1-alpha] + + //'control-point-weight': ['0.33', '0.66'], + //var controlPointWeight = ["0.33", "0.66"] + //var controlPointDistance = ["33%", "-66%"] + //var controlPointWeight = ["0.00", "1.00"] + /* + if (yParsed !== 0) { + //const degreeFound = Math.atan2(xParsed / yParsed) + const degreeFound = Math.atan2(xParsed, yParsed) * 180 / Math.PI + + if (degreeFound > 90 && degreeFound < 180) { + console.log("TOPRIGHT") + } else if (degreeFound < 90 && degreeFound > 0) { + console.log("BOTTOMRIGHT") + } else if (degreeFound < 0 && degreeFound > -90) { + console.log("BOTTOMLEFT") + //controlPointWeight = ["0.20", "0.80"] + //controlPointWeight = "0.7" + //controlPointDistance = "50%" + + } else if (degreeFound < -90 && degreeFound > -180) { + console.log("TOPLEFT") + } else { + console.log("STRAIGHT!") + } + } + */ + + return { + "distance": controlPointDistance, + "weight": controlPointWeight, + } + } const setupGraph = () => { const actions = workflow.actions.map((action) => { @@ -3916,7 +4022,7 @@ const AngularWorkflow = (defaultprops) => { }; // This is an attempt at prettier edges. The numbers are weird to work with. - /* + // Bezier curves //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html const sourcenode = actions.find(node => node.data._id === branch.source_id) const destinationnode = actions.find(node => node.data._id === branch.destination_id) @@ -3925,19 +4031,12 @@ const AngularWorkflow = (defaultprops) => { console.log("SOURCE: ", sourcenode.position) console.log("DESTINATIONNODE: ", destinationnode.position) - var opposite = true - if (sourcenode.position.x > destinationnode.position.x) { - opposite = false - } else { - opposite = true - } - + const edgeCurve = calculateEdgeCurve(sourcenode, destinationnode) edge.style = { - 'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"], - 'control-point-weight': ['0.3', '0.7'], + 'control-point-distance': edgeCurve.distance, + 'control-point-weight': edgeCurve.weight, } } - */ return edge; }); @@ -4846,6 +4945,13 @@ const AngularWorkflow = (defaultprops) => { var newAppData = parsedApp.data; if (newAppData.type === "ACTION") { + + //const activateApp = (appid) => { + if (newAppData.activated === false) { + console.log("SHOULD ACTIVATE!") + activateApp(newAppData.app_id) + } + // AUTHENTICATION if (app.authentication.required) { // Setup auth here :) @@ -4995,6 +5101,7 @@ const AngularWorkflow = (defaultprops) => { ? "cloud" : environments[defaultEnvironmentIndex].Name; + // activated: app.generated === true ? app.activated === false ? false : true : true, const newAppData = { app_name: app.name, app_version: app.app_version, @@ -5077,7 +5184,8 @@ const AngularWorkflow = (defaultprops) => { const image = app.large_image; const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle)); const pixelSize = !hover ? "2px" : "4px"; - newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0 + //) && !(!app.activated && app.generated) + newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0 && !(app.activated && app.generated) ? `${pixelSize} solid ${green}` : `${pixelSize} solid ${yellow}`;