Merge branch '1.2.0' of github.com:Shuffle/Shuffle into 1.2.0

This commit is contained in:
Aditya
2023-06-30 06:31:28 +05:30
22 changed files with 575 additions and 15902 deletions
+93 -32
View File
@@ -18,6 +18,10 @@ import urllib.parse
import jinja2 import jinja2
import datetime import datetime
import dateutil import dateutil
import threading
import concurrent.futures
from io import StringIO as StringBuffer from io import StringIO as StringBuffer
from io import BytesIO from io import BytesIO
from liquid import Liquid, defaults from liquid import Liquid, defaults
@@ -267,6 +271,7 @@ def split(base, sep):
### ###
### ###
class AppBase: class AppBase:
__version__ = None __version__ = None
app_name = None app_name = None
@@ -396,7 +401,7 @@ class AppBase:
try: try:
new_input = input_data.split() new_input = input_data.split()
except Exception as e: except Exception as e:
self.logger.info(f"[ERROR] Failed to run magic parser during split (1): {e}") self.logger.info(f"[ERROR] Failed to run parser during split (1): {e}")
return input_data return input_data
# Won't ever touch this one? # Won't ever touch this one?
@@ -452,9 +457,11 @@ class AppBase:
else: else:
self.logger.warning(f"[WARNING] Magic output not defined.") self.logger.warning(f"[WARNING] Magic output not defined.")
except KeyError as e: except KeyError as e:
self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result) - keyerror: {e}") #self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result) - keyerror: {e}")
pass
except Exception as e: except Exception as e:
self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result): {e}") #self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result): {e}")
pass
# Try it with some magic # Try it with some magic
@@ -471,7 +478,7 @@ class AppBase:
self.logger.info(f"[INFO] URL FOR RESULT (URL): {url}") self.logger.info(f"[INFO] URL FOR RESULT (URL): {url}")
try: try:
log_contents = "disabled: add env SHUFFLE_LOGS_DISABLED=true to Orborus to re-enable logs for apps" log_contents = "disabled: add env SHUFFLE_LOGS_DISABLED=true to Orborus to re-enable logs for apps. Can not be enabled natively in Cloud except in Hybrid mode."
if not os.getenv("SHUFFLE_LOGS_DISABLED") == "true": if not os.getenv("SHUFFLE_LOGS_DISABLED") == "true":
log_contents = self.log_capture_string.getvalue() log_contents = self.log_capture_string.getvalue()
@@ -1025,11 +1032,9 @@ class AppBase:
for subparams in param_multiplier: for subparams in param_multiplier:
#self.logger.info(f"SUBPARAMS IN MULTI: {subparams}") #self.logger.info(f"SUBPARAMS IN MULTI: {subparams}")
try: try:
#tmp = await func(**subparams)
while True: while True:
try: try:
#tmp = await func(**subparams)
tmp = func(**subparams) tmp = func(**subparams)
break break
except TypeError as e: except TypeError as e:
@@ -1042,7 +1047,7 @@ class AppBase:
try: try:
del subparams[field] del subparams[field]
self.logger.info("Removed field invalid field %s" % field) self.logger.info("Removed invalid field %s (1)" % field)
except KeyError: except KeyError:
break break
else: else:
@@ -1488,7 +1493,7 @@ class AppBase:
# If found, we get the full results list from backend # If found, we get the full results list from backend
fullexecution = {} fullexecution = {}
if isinstance(self.full_execution, str) and len(self.full_execution) == 0: if isinstance(self.full_execution, str) and len(self.full_execution) == 0:
self.logger.info("[DEBUG] NO EXECUTION - LOADING!") #self.logger.info("[DEBUG] NO EXECUTION - LOADING!")
try: try:
failed = False failed = False
rettext = "" rettext = ""
@@ -1525,6 +1530,7 @@ class AppBase:
rettext = ret.text rettext = ret.text
failed = True failed = True
time.sleep(8)
break break
if failed: if failed:
@@ -1832,7 +1838,7 @@ class AppBase:
else: else:
parse_string = inner_result parse_string = inner_result
print("PARSE STRING: %s" % parse_string) #print("PARSE STRING: %s" % parse_string)
return parse_string, True return parse_string, True
# Looks for parantheses to grab special cases within a string, e.g: # Looks for parantheses to grab special cases within a string, e.g:
@@ -1971,7 +1977,7 @@ class AppBase:
if isinstance(seconditem, int): if isinstance(seconditem, int):
seconditem = str(seconditem) seconditem = str(seconditem)
print("[DEBUG] ACTUAL PARSED: %s" % actualitem) #print("[DEBUG] ACTUAL PARSED: %s" % actualitem)
# Means it's a single item -> continue # Means it's a single item -> continue
if seconditem == "": if seconditem == "":
@@ -2093,13 +2099,13 @@ class AppBase:
actionname_lower = parsersplit[0][1:].lower() actionname_lower = parsersplit[0][1:].lower()
#Actionname: Start_node #Actionname: Start_node
print(f"\n[INFO] Actionname: {actionname_lower}") #print(f"\n[INFO] Actionname: {actionname_lower}")
# 1. Find the action # 1. Find the action
baseresult = "" baseresult = ""
appendresult = "" appendresult = ""
print("[INFO] Parsersplit length: %d" % len(parsersplit)) #print("[INFO] Parsersplit length: %d" % len(parsersplit))
if (actionname_lower.startswith("exec ") or actionname_lower.startswith("webhook ") or actionname_lower.startswith("schedule ") or actionname_lower.startswith("userinput ") or actionname_lower.startswith("email_trigger ") or actionname_lower.startswith("trigger ")) and len(parsersplit) == 1: if (actionname_lower.startswith("exec ") or actionname_lower.startswith("webhook ") or actionname_lower.startswith("schedule ") or actionname_lower.startswith("userinput ") or actionname_lower.startswith("email_trigger ") or actionname_lower.startswith("trigger ")) and len(parsersplit) == 1:
record = False record = False
for char in actionname_lower: for char in actionname_lower:
@@ -2142,7 +2148,6 @@ class AppBase:
print("[DEBUG] No results to get values from.") print("[DEBUG] No results to get values from.")
baseresult = "$" + parsersplit[0][1:] baseresult = "$" + parsersplit[0][1:]
print("[DEBUG] BEFORE VARIABLES!")
if len(baseresult) == 0: if len(baseresult) == 0:
try: try:
for variable in execution_data["workflow"]["workflow_variables"]: for variable in execution_data["workflow"]["workflow_variables"]:
@@ -2153,13 +2158,12 @@ class AppBase:
break break
except KeyError as e: except KeyError as e:
print("[INFO] KeyError wf variables: %s" % e) #print("[INFO] KeyError wf variables: %s" % e)
pass pass
except TypeError as e: except TypeError as e:
print("[INFO] TypeError wf variables: %s" % e) #print("[INFO] TypeError wf variables: %s" % e)
pass pass
print("[DEBUG] BEFORE EXECUTION VAR")
if len(baseresult) == 0: if len(baseresult) == 0:
try: try:
for variable in execution_data["execution_variables"]: for variable in execution_data["execution_variables"]:
@@ -2177,14 +2181,14 @@ class AppBase:
except KeyError as error: except KeyError as error:
print(f"[DEBUG] KeyError in JSON: {error}") print(f"[DEBUG] KeyError in JSON: {error}")
print(f"[INFO] After first trycatch. Baseresult")#, baseresult) #print(f"[INFO] After first trycatch. Baseresult")#, baseresult)
# 2. Find the JSON data # 2. Find the JSON data
# Returns if there isn't any JSON in the base ($nodename) # Returns if there isn't any JSON in the base ($nodename)
if len(baseresult) == 0: if len(baseresult) == 0:
return ""+appendresult, False return ""+appendresult, False
print("[INFO] After second return") #print("[INFO] After second return")
# Returns if the result is JUST something like $nodename, not $nodename.value # Returns if the result is JUST something like $nodename, not $nodename.value
if len(parsersplit) == 1: if len(parsersplit) == 1:
returndata = str(baseresult)+str(appendresult) returndata = str(baseresult)+str(appendresult)
@@ -2195,7 +2199,7 @@ class AppBase:
baseresult = baseresult.replace(" False", " false,") baseresult = baseresult.replace(" False", " false,")
# Tries to actually read it as JSON with some stupid formatting # Tries to actually read it as JSON with some stupid formatting
print("[INFO] After third parser return - Formatted")#, baseresult) #print("[INFO] After third parser return - Formatted")#, baseresult)
basejson = {} basejson = {}
try: try:
basejson = json.loads(baseresult) basejson = json.loads(baseresult)
@@ -2219,7 +2223,6 @@ class AppBase:
print("[WARNING] Parseditem issue: %s" % e) print("[WARNING] Parseditem issue: %s" % e)
pass pass
print("[DEBUG] DATA: (%s) %s" % (type(data), data))
if is_loop: if is_loop:
print("[DEBUG] DATA IS A LOOP - SHOULD WRAP") print("[DEBUG] DATA IS A LOOP - SHOULD WRAP")
if parsersplit[-1] == "#": if parsersplit[-1] == "#":
@@ -2231,7 +2234,6 @@ class AppBase:
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data)) parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
print("[DEBUG] Before last return with %s" % appendresult)
returndata = str(parseditem)+str(appendresult) returndata = str(parseditem)+str(appendresult)
# New in 0.8.97: Don't return items without lists # New in 0.8.97: Don't return items without lists
@@ -2558,7 +2560,7 @@ class AppBase:
# Basic fix in case variant isn't set # Basic fix in case variant isn't set
# Variant is ALWAYS STATIC_VALUE from mid 2021~ # Variant is ALWAYS STATIC_VALUE from mid 2021~
try: try:
self.logger.info(f"[DEBUG] Parameter '{paramname}' of length {len(parameter['value'])}") #self.logger.info(f"[DEBUG] Parameter '{paramname}' of length {len(parameter['value'])}")
parameter["variant"] = parameter["variant"] parameter["variant"] = parameter["variant"]
except: except:
parameter["variant"] = "STATIC_VALUE" parameter["variant"] = "STATIC_VALUE"
@@ -2928,7 +2930,7 @@ class AppBase:
# Checks whether conditions are met, otherwise set # Checks whether conditions are met, otherwise set
branchcheck, tmpresult = check_branch_conditions(action, fullexecution, self) branchcheck, tmpresult = check_branch_conditions(action, fullexecution, self)
if isinstance(tmpresult, object) or isinstance(tmpresult, list) or isinstance(tmpresult, dict): if isinstance(tmpresult, object) or isinstance(tmpresult, list) or isinstance(tmpresult, dict):
self.logger.info("[DEBUG] Fixing branch return as object -> string") #self.logger.info("[DEBUG] Fixing branch return as object -> string")
try: try:
#tmpresult = tmpresult.replace("'", "\"") #tmpresult = tmpresult.replace("'", "\"")
tmpresult = json.dumps(tmpresult) tmpresult = json.dumps(tmpresult)
@@ -3064,7 +3066,6 @@ class AppBase:
#self.logger.info(action["parameters"]) #self.logger.info(action["parameters"])
# This seems redundant now # This seems redundant now
self.logger.info("[DEBUG] Pre parameters")
for parameter in newparams: for parameter in newparams:
action["parameters"].append(parameter) action["parameters"].append(parameter)
@@ -3086,7 +3087,6 @@ class AppBase:
# Multi_parameter has the data for each. variable # Multi_parameter has the data for each. variable
minlength = 0 minlength = 0
self.logger.info("[DEBUG] Pre-loading parameters")
multi_parameters = json.loads(json.dumps(params)) multi_parameters = json.loads(json.dumps(params))
multiexecution = False multiexecution = False
multi_execution_lists = [] multi_execution_lists = []
@@ -3344,9 +3344,6 @@ class AppBase:
# This part has fucked over so many random JSON usages because of weird paranthesis parsing # This part has fucked over so many random JSON usages because of weird paranthesis parsing
value = parse_wrapper_start(value, self) value = parse_wrapper_start(value, self)
#self.logger.info("[DEBUG] Post return: %s" % value)
#self.logger.info("POST data value: %s" % value)
try: try:
if str(value).startswith("b'") and str(value).endswith("'"): if str(value).startswith("b'") and str(value).endswith("'"):
@@ -3479,8 +3476,73 @@ class AppBase:
break break
try: try:
newres = func(**params) #try:
# Individual functions shouldn't take longer than this
# This is an attempt to make timeouts occur less, incentivizing users to make use efficient API's
# PS: Not implemented for lists - only single actions as of May 2023
timeout = 30
# Check if current app is Shuffle Tools, then set to 55 due to certain actions being slow (ioc parser..)
#uu In general, this should be disabled for onprem
if self.action["app_name"].lower() == "shuffle tools":
timeout = 55
timeout = 30
try:
executor = concurrent.futures.ThreadPoolExecutor()
future = executor.submit(func, **params)
newres = future.result(timeout)
if not future.done():
# The future is still running, so we need to cancel it
future.cancel()
newres = json.dumps({
"success": False,
"reason": "Timeout error within %d seconds. This happens if we can't reach or use the API you're trying to use within the time limit." % timeout,
"exception": str(e),
})
else:
# The future is done, so we can just get the result from newres :)
#newres = future.result()
#print("Future is done!")
pass
except concurrent.futures.TimeoutError as e:
newres = json.dumps({
"success": False,
"reason": "Timeout error within %d seconds (2). This happens if we can't reach or use the API you're trying to use within the time limit" % timeout
})
break break
#thread = threading.Thread(target=func, args=(**params,))
#thread.start()
#thread.join(timeout)
#if thread.is_alive():
# # The thread is still running, so we need to stop it
# # You can handle this as needed, such as raising an exception
# timeout_handler()
#with Timeout(timeout):
# newres = func(**params)
# break
#except Timeout.Timeout as e:
# self.logger.info(f"[DEBUG] Timeout error: {e}")
# newres = json.dumps({
# "success": False,
# "reason": "Timeout error within %d seconds. This typically happens if we can't reach the API you're trying to reach." % timeout,
# "exception": str(e),
# })
# break
except TypeError as e: except TypeError as e:
newres = "" newres = ""
self.logger.info(f"[DEBUG] Got exec type error: {e}") self.logger.info(f"[DEBUG] Got exec type error: {e}")
@@ -3512,7 +3574,7 @@ class AppBase:
try: try:
del params[field] del params[field]
self.logger.info("[WARNING] Removed field invalid field %s" % field) self.logger.info("[WARNING] Removed invalid field %s (2)" % field)
except KeyError: except KeyError:
break break
else: else:
@@ -3605,7 +3667,7 @@ class AppBase:
result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres) result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres)
self.logger.info("Can't handle type %s value from function" % (type(newres))) self.logger.info("Can't handle type %s value from function" % (type(newres)))
self.logger.info("[INFO] POST NEWRES RESULT!")#, result) #self.logger.info("[INFO] POST NEWRES RESULT!")#, result)
else: else:
#self.logger.info("[INFO] APP_SDK DONE: Starting MULTI execution (length: %d) with values %s" % (minlength, multi_parameters)) #self.logger.info("[INFO] APP_SDK DONE: Starting MULTI execution (length: %d) with values %s" % (minlength, multi_parameters))
# 1. Use number of executions based on the arrays being similar # 1. Use number of executions based on the arrays being similar
@@ -3776,7 +3838,6 @@ class AppBase:
#logger.info(f"[DEBUG] Datatype: {type(requestdata)}: {requestdata}") #logger.info(f"[DEBUG] Datatype: {type(requestdata)}: {requestdata}")
# Remaking class for each request # Remaking class for each request
#print(f"APP: {app}")
app = cls(redis=None, logger=logger, console_logger=logger) app = cls(redis=None, logger=logger, console_logger=logger)
extra_info = "" extra_info = ""
-189
View File
@@ -385,170 +385,6 @@ func buildImage(tags []string, dockerfileFolder string) error {
return nil return nil
} }
// FIXME - very specific for webhooks. Make it easier?
func stopWebhook(image string, identifier string) error {
ctx := context.Background()
containername := fmt.Sprintf("%s-%s", image, identifier)
cli, err := client.NewEnvClient()
if err != nil {
log.Println("Unable to create docker client")
return err
}
// containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
// All: true,
// })
if err := cli.ContainerStop(ctx, containername, nil); err != nil {
log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err)
}
removeOptions := types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil {
log.Printf("Unable to remove container: %s", err)
}
return nil
}
// Starts a new webhook
func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
cors := shuffle.HandleCors(resp, request)
if cors {
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) != 32 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
return
}
ctx := context.Background()
hook, err := shuffle.GetHook(ctx, fileId)
if err != nil {
log.Printf("Failed getting hook %s (stop docker): %s", fileId, err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
log.Printf("Status: %s", hook.Status)
log.Printf("Running: %t", hook.Running)
if !hook.Running {
message := fmt.Sprintf("Error: %s isn't running", hook.Id)
log.Println(message)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message)))
return
}
hook.Status = "stopped"
hook.Running = false
hook.Actions = []shuffle.HookAction{}
err = shuffle.SetHook(ctx, *hook)
if err != nil {
log.Printf("Failed setting hook: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
image := "webhook"
// This is here to force stop and remove the old webhook
err = stopWebhook(image, fileId)
if err != nil {
log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
}
resp.WriteHeader(200)
resp.Write([]byte(`{"success": true, "message": "Stopped webhook"}`))
}
// THis is an example
// Can also be used as base data?
var webhook = `{
"id": "d6ef8912e8bd37776e654cbc14c2629c",
"info": {
"url": "http://localhost:5001",
"name": "TheHive",
"description": "Webhook for TheHive"
},
"transforms": {},
"actions": {},
"type": "webhook",
"running": false,
"status": "stopped"
}`
// Starts a new webhook
func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) {
ctx := context.Background()
cors := shuffle.HandleCors(resp, request)
if cors {
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) != 32 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
return
}
err := shuffle.DeleteKey(ctx, "hooks", fileId)
if err != nil {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "Can't delete"}`))
return
}
image := "webhook"
// This is here to force stop and remove the old webhook
err = stopWebhook(image, fileId)
if err != nil {
log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
resp.Write([]byte(`{"success": false, "message": "Couldn't stop webhook"}`))
return
}
resp.WriteHeader(200)
resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`))
}
// Checks if an image exists // Checks if an image exists
func imageCheckBuilder(images []string) error { func imageCheckBuilder(images []string) error {
//log.Printf("[FIXME] ImageNames to check: %#v", images) //log.Printf("[FIXME] ImageNames to check: %#v", images)
@@ -594,31 +430,6 @@ func imageCheckBuilder(images []string) error {
return nil return nil
} }
func hookTest() {
var hook shuffle.Hook
err := json.Unmarshal([]byte(webhook), &hook)
log.Println(webhook)
if err != nil {
log.Printf("Failed hook unmarshaling: %s", err)
return
}
ctx := context.Background()
err = shuffle.SetHook(ctx, hook)
if err != nil {
log.Printf("Failed setting hook: %s", err)
}
returnHook, err := shuffle.GetHook(ctx, hook.Id)
if err != nil {
log.Printf("Failed getting hook %s (test): %s", hook.Id, err)
}
if len(returnHook.Id) > 0 {
log.Printf("Success! - %s", returnHook.Id)
}
}
// https://stackoverflow.com/questions/23935141/how-to-copy-docker-images-from-one-host-to-another-without-using-a-repository // https://stackoverflow.com/questions/23935141/how-to-copy-docker-images-from-one-host-to-another-without-using-a-repository
func getDockerImage(resp http.ResponseWriter, request *http.Request) { func getDockerImage(resp http.ResponseWriter, request *http.Request) {
cors := shuffle.HandleCors(resp, request) cors := shuffle.HandleCors(resp, request)
+50 -45
View File
@@ -1,100 +1,105 @@
module main module shuffle-shared
replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared
go 1.19 go 1.19
replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
require ( require (
cloud.google.com/go/datastore v1.10.0 cloud.google.com/go/datastore v1.11.0
cloud.google.com/go/pubsub v1.28.0 cloud.google.com/go/pubsub v1.31.0
cloud.google.com/go/storage v1.28.1 cloud.google.com/go/storage v1.30.1
github.com/basgys/goxml2json v1.1.0 github.com/basgys/goxml2json v1.1.0
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
github.com/docker/docker v20.10.21+incompatible github.com/docker/docker v24.0.2+incompatible
github.com/frikky/kin-openapi v0.42.0 github.com/frikky/kin-openapi v0.42.0
github.com/fsouza/go-dockerclient v1.9.0 github.com/fsouza/go-dockerclient v1.9.7
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-billy/v5 v5.4.1
github.com/go-git/go-git/v5 v5.5.0 github.com/go-git/go-git/v5 v5.7.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.4.19 github.com/shuffle/shuffle-shared v0.4.19
golang.org/x/crypto v0.3.0 golang.org/x/crypto v0.9.0
google.golang.org/api v0.103.0 google.golang.org/api v0.125.0
google.golang.org/appengine v1.6.7 google.golang.org/appengine v1.6.7
google.golang.org/grpc v1.51.0 google.golang.org/grpc v1.55.0
gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
cloud.google.com/go v0.105.0 // indirect cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute v1.13.0 // indirect cloud.google.com/go/compute v1.19.3 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.7.0 // indirect cloud.google.com/go/iam v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/hcsshim v0.9.3 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/adrg/strutil v0.2.3 // indirect github.com/adrg/strutil v0.2.3 // indirect
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
github.com/cloudflare/circl v1.1.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect
github.com/containerd/cgroups v1.0.3 // indirect github.com/containerd/containerd v1.6.18 // indirect
github.com/containerd/containerd v1.6.6 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/frikky/go-elasticsearch/v8 v8.13.1 // indirect github.com/frikky/go-elasticsearch/v8 v8.13.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.19.5 // indirect github.com/go-openapi/swag v0.19.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-github/v28 v28.1.1 // indirect github.com/google/go-github/v28 v28.1.1 // indirect
github.com/google/go-querystring v1.0.0 // indirect github.com/google/go-querystring v1.0.0 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/googleapis/gax-go/v2 v2.10.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect github.com/imdario/mergo v0.3.15 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mailru/easyjson v0.7.0 // indirect github.com/klauspost/compress v1.11.13 // indirect
github.com/moby/sys/mount v0.3.3 // indirect github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/moby/sys/mountinfo v0.6.2 // indirect github.com/moby/patternmatcher v0.5.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/opencontainers/runc v1.1.2 // indirect github.com/opencontainers/runc v1.1.5 // indirect
github.com/opensearch-project/opensearch-go v1.1.0 // indirect
github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pjbgf/sha1cd v0.2.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/sergi/go-diff v1.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect github.com/skeema/knownhosts v1.1.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/src-d/gcfg v1.4.0 // indirect github.com/src-d/gcfg v1.4.0 // indirect
github.com/xanzy/ssh-agent v0.3.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.2.0 // indirect golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.4.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
+4 -5
View File
@@ -37,9 +37,6 @@ import (
"cloud.google.com/go/storage" "cloud.google.com/go/storage"
"google.golang.org/appengine/mail" "google.golang.org/appengine/mail"
//"github.com/elastic/go-elasticsearch/v7"
//"github.com/elastic/go-elasticsearch/v8/esapi"
"github.com/frikky/kin-openapi/openapi2" "github.com/frikky/kin-openapi/openapi2"
"github.com/frikky/kin-openapi/openapi2conv" "github.com/frikky/kin-openapi/openapi2conv"
"github.com/frikky/kin-openapi/openapi3" "github.com/frikky/kin-openapi/openapi3"
@@ -3908,8 +3905,8 @@ func runInitEs(ctx context.Context) {
} }
if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") { if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") {
log.Printf("[INFO] Waiting 10 seconds during init to make sure the opensearch instance is up and running with security features properly") log.Printf("[INFO] Waiting during init to make sure the opensearch instance is up and running with security features properly")
time.Sleep(10 * time.Second) time.Sleep(30 * time.Second)
} }
_ = setUsers _ = setUsers
@@ -6055,7 +6052,9 @@ func initHandlers() {
r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS")
// Docker orborus specific - downloads an image // Docker orborus specific - downloads an image
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
+87 -68
View File
@@ -165,7 +165,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
executionRequests, err := shuffle.GetWorkflowQueue(ctx, id, 100) executionRequests, err := shuffle.GetWorkflowQueue(ctx, id, 100)
if err != nil { if err != nil {
log.Printf("[WARNING] (1) Failed reading body for workflowqueue: %s", err) log.Printf("[WARNING] (1) Failed reading body for workflowqueue: %s", err)
resp.WriteHeader(401) resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Entity parsing error - confirm"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Entity parsing error - confirm"}`)))
return return
} }
@@ -179,8 +179,8 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
body, err := ioutil.ReadAll(request.Body) body, err := ioutil.ReadAll(request.Body)
if err != nil { if err != nil {
log.Println("Failed reading body for stream result queue") log.Println("[WARNING] Failed reading body for stream result queue")
resp.WriteHeader(401) resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return return
} }
@@ -190,16 +190,16 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
var removeExecutionRequests shuffle.ExecutionRequestWrapper var removeExecutionRequests shuffle.ExecutionRequestWrapper
err = json.Unmarshal(body, &removeExecutionRequests) err = json.Unmarshal(body, &removeExecutionRequests)
if err != nil { if err != nil {
log.Printf("Failed executionrequest in queue unmarshaling: %s", err) log.Printf("[WARNING] Failed executionrequest in queue unmarshaling: %s", err)
resp.WriteHeader(401) resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return return
} }
if len(removeExecutionRequests.Data) == 0 { if len(removeExecutionRequests.Data) == 0 {
log.Printf("No requests to fix remove from DB") log.Printf("[WARNING] No requests to fix remove from DB")
resp.WriteHeader(401) resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Some removal error"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Queue removal error"}`)))
return return
} }
@@ -636,7 +636,6 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
} }
//log.Printf("Actionresult unmarshal: %s", string(body)) //log.Printf("Actionresult unmarshal: %s", string(body))
log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
ctx := context.Background() ctx := context.Background()
err = shuffle.ValidateNewWorkerExecution(ctx, body) err = shuffle.ValidateNewWorkerExecution(ctx, body)
if err == nil { if err == nil {
@@ -647,6 +646,8 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
log.Printf("[DEBUG] Handling other execution variant (subflow?): %s", err) log.Printf("[DEBUG] Handling other execution variant (subflow?): %s", err)
} }
log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
var actionResult shuffle.ActionResult var actionResult shuffle.ActionResult
err = json.Unmarshal(body, &actionResult) err = json.Unmarshal(body, &actionResult)
if err != nil { if err != nil {
@@ -698,60 +699,63 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
} }
} }
if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" { /*
log.Printf("[INFO] SHOULD WAIT A BIT AND RUN CLOUD STUFF WITH USER INPUT! WAITING!") // Removed as UserInput is now handled as an app
if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" {
log.Printf("[INFO] SHOULD WAIT A BIT AND RUN USER INPUT! WAITING!")
var trigger shuffle.Trigger var trigger shuffle.Trigger
err = json.Unmarshal([]byte(actionResult.Result), &trigger) err = json.Unmarshal([]byte(actionResult.Result), &trigger)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed unmarshaling actionresult for user input: %s", err) log.Printf("[WARNING] Failed unmarshaling actionresult for user input: %s", err)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
orgId := workflowExecution.ExecutionOrg orgId := workflowExecution.ExecutionOrg
if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 { if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 {
orgId = workflowExecution.Workflow.OrgId orgId = workflowExecution.Workflow.OrgId
} }
err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId) err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed userinput handler: %s", err) log.Printf("[WARNING] Failed userinput handler: %s", err)
actionResult.Result = fmt.Sprintf(`{"success": False, "reason": "%s"}`, err) actionResult.Result = fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)
workflowExecution.Results = append(workflowExecution.Results, actionResult) workflowExecution.Results = append(workflowExecution.Results, actionResult)
workflowExecution.Status = "ABORTED" workflowExecution.Status = "ABORTED"
err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed to set execution during wait: %s", err) log.Printf("[WARNING] Failed to set execution during wait: %s", err)
} else { } else {
log.Printf("[INFO] Successfully set the execution %s to waiting.", workflowExecution.ExecutionId) log.Printf("[INFO] Successfully set the execution %s to waiting.", workflowExecution.ExecutionId)
}
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err)))
return
} else {
log.Printf("[INFO] Successful userinput handler")
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`)))
actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}`
workflowExecution.Results = append(workflowExecution.Results, actionResult)
workflowExecution.Status = actionResult.Status
err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
if err != nil {
log.Printf("[WARNING] Failed setting userinput: %s", err)
} else {
log.Printf("[DEBUG] Successfully set the execution to waiting.")
}
}
return
} }
*/
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err)))
return
} else {
log.Printf("[INFO] Successful userinput handler")
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`)))
actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}`
workflowExecution.Results = append(workflowExecution.Results, actionResult)
workflowExecution.Status = actionResult.Status
err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
if err != nil {
log.Printf("[WARNING] Failed setting userinput: %s", err)
} else {
log.Printf("[DEBUG] Successfully set the execution to waiting.")
}
}
return
}
runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp) runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp)
} }
@@ -1049,14 +1053,16 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
workflow = *tmpworkflow workflow = *tmpworkflow
} }
if len(workflow.ExecutingOrg.Id) == 0 { /*
if len(orgId) > 0 { if len(workflow.ExecutingOrg.Id) == 0 {
workflow.ExecutingOrg.Id = orgId if len(orgId) > 0 {
} else { workflow.ExecutingOrg.Id = orgId
log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID) } else {
return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined") log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID)
return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined")
}
} }
} */
if len(workflow.Actions) == 0 { if len(workflow.Actions) == 0 {
workflow.Actions = []shuffle.Action{} workflow.Actions = []shuffle.Action{}
@@ -1100,8 +1106,13 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request, 10) workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request, 10)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed in prepareExecution for execution Id '%s': %s", workflowExecution.ExecutionId, err) if strings.Contains(fmt.Sprintf("%s", err), "User Input") {
return workflowExecution, fmt.Sprintf("Failed preparration: %s", err), err // Special for user input callbacks
return workflowExecution, fmt.Sprintf("%s", err), nil
} else {
log.Printf("[WARNING] Failed in prepareExecution: %s", err)
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err
}
} }
err = imageCheckBuilder(execInfo.ImageNames) err = imageCheckBuilder(execInfo.ImageNames)
@@ -1293,7 +1304,8 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
return return
} }
//memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) log.Printf("[INFO] Inside execute workflow for ID %s", fileId)
ctx := context.Background() ctx := context.Background()
workflow, err := shuffle.GetWorkflow(ctx, fileId) workflow, err := shuffle.GetWorkflow(ctx, fileId)
if err != nil && workflow.ID == "" { if err != nil && workflow.ID == "" {
@@ -1310,6 +1322,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
// 1. Parent workflow contains this workflow ID in the source trigger? // 1. Parent workflow contains this workflow ID in the source trigger?
// 2. Parent workflow's owner is same org? // 2. Parent workflow's owner is same org?
// 3. Parent execution auth is correct // 3. Parent execution auth is correct
log.Printf("[INFO] Inside execute workflow access validation!")
executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow) executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow)
if !executionAuthValid { if !executionAuthValid {
@@ -1344,6 +1357,12 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
workflow.ExecutingOrg = user.ActiveOrg workflow.ExecutingOrg = user.ActiveOrg
workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request, user.ActiveOrg.Id) workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request, user.ActiveOrg.Id)
if err == nil { if err == nil {
if strings.Contains(executionResp, "User Input:") {
resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
return
}
resp.WriteHeader(200) resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization))) resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization)))
return return
@@ -2691,7 +2710,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId
if len(triggerType) == 0 { if len(triggerType) == 0 {
log.Printf("[WARNING] No type specified for user input node") log.Printf("[WARNING] No type specified for user input node")
return errors.New("No type specified for user input node") //return errors.New("No type specified for user input node")
} }
// FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms // FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms
+1 -1
View File
@@ -62,7 +62,7 @@ services:
container_name: shuffle-opensearch container_name: shuffle-opensearch
environment: environment:
- bootstrap.memory_lock=true - bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
- cluster.initial_master_nodes=shuffle-opensearch - cluster.initial_master_nodes=shuffle-opensearch
- cluster.routing.allocation.disk.threshold_enabled=false - cluster.routing.allocation.disk.threshold_enabled=false
- cluster.name=shuffle-cluster - cluster.name=shuffle-cluster
+1
View File
@@ -503,6 +503,7 @@ const App = (message, props) => {
path="/workflows" path="/workflows"
element={ element={
<Workflows <Workflows
checkLogin={checkLogin}
cookies={cookies} cookies={cookies}
removeCookie={removeCookie} removeCookie={removeCookie}
isLoaded={isLoaded} isLoaded={isLoaded}
+1 -1
View File
@@ -362,7 +362,7 @@ const AppGrid = props => {
: null : null
} }
<span style={{position: "absolute", display: "flex", textAlign: "right", float: "right", right: 0, bottom: 120, }}> <span style={{position: "absolute", display: "flex", textAlign: "right", float: "right", right: 0, bottom: isMobile?"":120, }}>
<Typography variant="body2" color="textSecondary" style={{}}> <Typography variant="body2" color="textSecondary" style={{}}>
Search by Search by
</Typography> </Typography>
+2 -1
View File
@@ -39,8 +39,9 @@ const AppSearchPopout = (props) => {
return null return null
} }
// <Paper style={{width: 275, maxHeight: 400, zIndex: 100000, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -15, left: 50, }}>
return ( return (
<Paper style={{width: 275, maxHeight: 400, zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -15, left: 50, overflow: "hidden", }}> <Paper style={{minWidth: 275, width: 275, minHeight: 400, maxHeight: 400, zIndex: 100000, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -15, left: 50, }}>
{paperTitle !== undefined && paperTitle.length > 0 ? {paperTitle !== undefined && paperTitle.length > 0 ?
<span> <span>
<Typography variant="h6" style={{textAlign: "center"}}> <Typography variant="h6" style={{textAlign: "center"}}>
@@ -79,6 +79,17 @@ const ConfigureWorkflow = (props) => {
}, },
}); });
// ONLY when component is being unloaded, run stop() function
// This is to prevent the interval from running when the component is not being used
/*
useEffect(() => {
return () => {
stop()
}
}, [])
*/
// Where is this from? // Where is this from?
if (workflow === undefined || workflow === null) { if (workflow === undefined || workflow === null) {
return null; return null;
+1 -4
View File
@@ -2729,10 +2729,7 @@ const ParsedAction = (props) => {
// Look for the ID // Look for the ID
const found = false; const found = false;
for (let [key,keyval] in Object.entries(workflowExecutions)) { for (let [key,keyval] in Object.entries(workflowExecutions)) {
if ( if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
workflowExecutions[key].results === undefined ||
workflowExecutions[key].results === null
) {
continue; continue;
} }
+5 -5
View File
@@ -15,7 +15,7 @@ import Priority from "../components/Priority.jsx";
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
const Priorities = (props) => { const Priorities = (props) => {
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, checkLogin, } = props; const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, } = props;
const [showDismissed, setShowDismissed] = React.useState(false); const [showDismissed, setShowDismissed] = React.useState(false);
const [showRead, setShowRead] = React.useState(false); const [showRead, setShowRead] = React.useState(false);
@@ -25,9 +25,9 @@ const Priorities = (props) => {
return ( return (
<div style={{maxWidth: 1000, }}> <div style={{maxWidth: 1000, }}>
<h2 style={{ display: "inline" }}>Priorities</h2> <h2 style={{ display: "inline" }}>Suggestions</h2>
<span style={{ marginLeft: 25 }}> <span style={{ marginLeft: 25 }}>
Priorities identified by Shuffle to help you discover ways to protect yourself.&nbsp; Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company. These range from simple configurations in Shuffle to Usecases you may have missed.&nbsp;
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -46,7 +46,7 @@ const Priorities = (props) => {
/>&nbsp; Show dismissed />&nbsp; Show dismissed
{userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? {userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ?
<Typography variant="h4"> <Typography variant="h4">
No Priorities found No Suggestions found
</Typography> </Typography>
: :
userdata.priorities.map((priority, index) => { userdata.priorities.map((priority, index) => {
@@ -67,7 +67,7 @@ const Priorities = (props) => {
<Divider style={{marginTop: 50, marginBottom: 50, }} /> <Divider style={{marginTop: 50, marginBottom: 50, }} />
<h2 style={{ display: "inline" }}>Notifications</h2> <h2 style={{ display: "inline" }}>Notifications</h2>
<span style={{ marginLeft: 25 }}> <span style={{ marginLeft: 25 }}>
Notifications help you find potential problems with your workflows and apps Notifications help you find potential problems with your workflows and apps.&nbsp;
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
+45 -7
View File
@@ -11,6 +11,11 @@ import {
Card, Card,
} from "@material-ui/core"; } from "@material-ui/core";
// import magic wand icon from material ui icons
import {
AutoFixHigh as AutoFixHighIcon,
ArrowForward as ArrowForwardIcon,
} from '@mui/icons-material';
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
const Priority = (props) => { const Priority = (props) => {
@@ -60,18 +65,51 @@ const Priority = (props) => {
}); });
} }
return ( return (
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", height: 70, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}> <div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", height: 70, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
<div style={{flex: 2, overflow: "hidden",}}> <div style={{flex: 2, overflow: "hidden",}}>
<Typography variant="body1" > <span style={{display: "flex", }}>
{priority.name} {priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
</Typography> <Typography variant="body1" >
<Typography variant="body2" color="textSecondary"> {priority.name}
{priority.description} </Typography>
</Typography> </span>
{priority.type === "usecase" && priority.description.includes("&") ?
<span style={{display: "flex", marginTop: 10, }}>
<img src={priority.description.split("&")[1]} alt={priority.name} style={{height: 30, width: 30, marginRight: 5, borderRadius: theme.palette.borderRadius, marginRight: 10, }} />
<Typography variant="body2" color="textSecondary" style={{marginTop: 3, }}>
{priority.description.split("&")[0]}
</Typography>
{priority.description.split("&").length > 3 ?
<span style={{display: "flex", }}>
<ArrowForwardIcon style={{marginLeft: 15, marginRight: 15, }}/>
<img src={priority.description.split("&")[3]} alt={priority.name+"2"} style={{height: 30, width: 30, borderRadius: theme.palette.borderRadius, marginRight: 10, }} />
<Typography variant="body2" color="textSecondary" style={{marginTop: 3}}>
{priority.description.split("&")[2]}
</Typography>
</span>
: null}
</span>
:
<Typography variant="body2" color="textSecondary">
{priority.description}
</Typography>
}
</div> </div>
<div style={{flex: 1, display: "flex", marginLeft: 30, }}> <div style={{flex: 1, display: "flex", marginLeft: 30, }}>
<Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 200, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "white", }} variant="contained" color="secondary" onClick={() => {navigate(priority.url)}}> <Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "white", }} variant="contained" color="secondary" onClick={() => {
/*
ReactGA.event({
category: "",
action: `partner_${partner.name}_click`,
label: "",
})
*/
navigate(priority.url)
}}>
explore explore
</Button> </Button>
{priority.active === true ? {priority.active === true ?
-44
View File
@@ -693,52 +693,8 @@ const WelcomeForm = (props) => {
<Typography variant="body1" style={{marginTop: 15, marginBottom: 0, maxWidth: 500, margin: "auto", marginBottom: 15, }} color="textSecondary"> <Typography variant="body1" style={{marginTop: 15, marginBottom: 0, maxWidth: 500, margin: "auto", marginBottom: 15, }} color="textSecondary">
These are some of our Workflow templates, used to start new Workflows. Use the right and left buttons to find <a href="/usecases" target="_blank" rel="norefferer" style={{color: "#f86a3e", textDecoration: "none", }}>new Usecases</a>, and click the orange button to build it. These are some of our Workflow templates, used to start new Workflows. Use the right and left buttons to find <a href="/usecases" target="_blank" rel="norefferer" style={{color: "#f86a3e", textDecoration: "none", }}>new Usecases</a>, and click the orange button to build it.
</Typography> </Typography>
{/*<Divider />*/}
{/*
<div style={{width: 475, margin: "auto",}}>
{usecaseButtons.map((usecase, index) => {
return (
<Chip
key={usecase.name}
style={{
backgroundColor: defaultSearch === usecase.name ? usecase.color : theme.palette.surfaceColor,
marginRight: 10,
paddingLeft: 5,
paddingRight: 5,
height: 28,
cursor: "pointer",
border: `1px solid ${usecase.color}`,
color: "white",
borderRadius: theme.palette.borderRadius,
}}
label={`${index+1}. ${usecase.name}`}
onClick={() => {
console.log("Clicked: ", usecase.name)
if (defaultSearch === usecase.name) {
//setSelectedUsecaseCategory("")
} else {
handleSetSearch(usecase.name, usecase.usecase)
}
//addFilter(usecase.name.slice(3,usecase.name.length))
}}
variant="outlined"
color="primary"
/>
)
})}
</div>
*/}
<div style={{marginTop: 0, }}> <div style={{marginTop: 0, }}>
{/*
<UsecaseSearch
globalUrl={globalUrl}
defaultSearch={defaultSearch}
appFramework={appFramework}
apps={apps}
/>
*/}
<div className="thumbs" style={{display: "flex"}}> <div className="thumbs" style={{display: "flex"}}>
<Tooltip title={"Previous usecase"}> <Tooltip title={"Previous usecase"}>
<IconButton <IconButton
+214 -33
View File
@@ -177,7 +177,7 @@ const Admin = (props) => {
const [secret2FA, setSecret2FA] = React.useState(""); const [secret2FA, setSecret2FA] = React.useState("");
const [show2faSetup, setShow2faSetup] = useState(false); const [show2faSetup, setShow2faSetup] = useState(false);
const [adminTab, setAdminTab] = React.useState(1); const [adminTab, setAdminTab] = React.useState(2);
const [showApiKey, setShowApiKey] = useState(false); const [showApiKey, setShowApiKey] = useState(false);
const [billingInfo, setBillingInfo] = React.useState({}); const [billingInfo, setBillingInfo] = React.useState({});
const [selectedStatus, setSelectedStatus] = React.useState([]); const [selectedStatus, setSelectedStatus] = React.useState([]);
@@ -189,8 +189,6 @@ const Admin = (props) => {
} }
}, [isDropzone]); }, [isDropzone]);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const get2faCode = (userId) => { const get2faCode = (userId) => {
@@ -299,6 +297,165 @@ const Admin = (props) => {
) )
} }
// Basically just a simple way to get a generated email
// This also may help understand how to communicate with users
// both inside and outside Shuffle
// This could also be generated on the backend
const mailsendingButton = (org) => {
if (org === undefined || org === null) {
return ""
}
if (users.length === 0) {
return ""
}
// 1 mail based on users that have only apps
// Another based on those doing workflows
// Another based on those trying usecases(?) or templates
//
// Start based on edr, siem & ticketing
// Talk about enrichment?
// Check suggested usecases
// Check suggested workflows
var your_apps = "- Connecting "
var subject_add = 0
var subject = "Want to automate "
if (org.security_framework !== undefined && org.security_framework !== null) {
if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") {
your_apps += org.security_framework.cases.name.replace("_", " ", -1) + ", "
if (subject_add < 2) {
if (subject_add === 1) {
subject += " & "
}
subject_add += 1
subject += org.security_framework.cases.name.replace("_", " ", -1)
}
}
if (org.security_framework.siem.name !== undefined && org.security_framework.siem.name !== null && org.security_framework.siem.name !== "") {
your_apps += org.security_framework.siem.name.replace("_", " ", -1) + ", "
if (subject_add < 2) {
if (subject_add === 1) {
subject += " & "
}
subject_add += 1
subject += org.security_framework.siem.name.replace("_", " ", -1)
}
}
if (org.security_framework.communication.name !== undefined && org.security_framework.communication.name !== null && org.security_framework.communication.name !== "") {
your_apps += org.security_framework.communication.name.replace("_", " ", -1) + ", "
if (subject_add < 2) {
if (subject_add === 1) {
subject += " & "
}
subject_add += 1
subject += org.security_framework.communication.name.replace("_", " ", -1)
}
}
if (org.security_framework.edr.name !== undefined && org.security_framework.edr.name !== null && org.security_framework.edr.name !== "") {
your_apps += org.security_framework.edr.name.replace("_", " ", -1) + ", "
if (subject_add < 2) {
if (subject_add === 1) {
subject += " & "
}
subject_add += 1
subject += org.security_framework.edr.name.replace("_", " ", -1)
}
}
if (org.security_framework.intel.name !== undefined && org.security_framework.intel.name !== null && org.security_framework.intel.name !== "") {
your_apps += org.security_framework.intel.name.replace("_", " ", -1) + ", "
if (subject_add < 2) {
if (subject_add === 1) {
subject += " & "
}
subject_add += 1
subject += org.security_framework.intel.name.replace("_", " ", -1)
}
}
// Remove comma
subject += "?"
your_apps = your_apps.substring(0, your_apps.length - 2)
}
// Add usecases they may not have tried (from recommendations): org.priorities where item type is usecase
var usecases = "- Building usecases like "
const active_usecase = org.priorities.filter((item) => item.type === "usecase" && item.active === true)
if (active_usecase.length > 0) {
for (var i = 0; i < active_usecase.length; i++) {
if (active_usecase[i].name.includes("Suggested Usecase: ")) {
usecases += active_usecase[i].name.replace("Suggested Usecase: ", "", -1) + ", "
} else {
usecases += active_usecase[i].name + ", "
}
}
usecases = usecases.substring(0, usecases.length - 2)
}
if (your_apps.length <= 15) {
your_apps = ""
}
if (usecases.length <= 30) {
usecases = ""
}
var workflow_amount = "a few"
var admins = ""
// Loop users
for (var i = 0; i < users.length; i++) {
if (users[i].role === "admin") {
admins += users[i].username + ","
}
}
// Remove last comma
admins = admins.substring(0, admins.length - 1)
if (your_apps.length > 5) {
your_apps += "%0D%0A"
}
if (usecases.length > 5) {
usecases += "%0D%0A"
}
// Get drift username from userdata.username before @ in email
const username = userdata.username.substring(0, userdata.username.indexOf("@"))
var body = `Hey,%0D%0AI saw you trying to use Shuffle, and thought we may be able to help. Right now, it looks like you have ${workflow_amount} workflows made, but I'm not sure if you're getting the most out of Shuffle.%0D%0A%0D%0AIf you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A
Some of the things we can help with:%0D%0A
${your_apps}
- Properly authenticating and custom building apps%0D%0A
${usecases}
- Creating special usecases%0D%0A%0D%0A
Let me know if you're interested, or set up a call here: https://drift.me/${username}`
return `mailto:${admins}?subject=${subject}&body=${body}`
}
const deleteAuthentication = (data) => { const deleteAuthentication = (data) => {
alert.info("Deleting auth " + data.label); alert.info("Deleting auth " + data.label);
@@ -367,6 +524,13 @@ const Admin = (props) => {
}); });
}; };
if (userdata.support === true && selectedOrganization.id !== "" && selectedOrganization.id !== undefined && selectedOrganization.id !== null && selectedOrganization.id !== userdata.active_org.id) {
alert.info("Refreshing window to fix org support access")
window.location.reload()
return null
}
const handleVerify2FA = (userId, code) => { const handleVerify2FA = (userId, code) => {
const data = { const data = {
code: code, code: code,
@@ -799,6 +963,10 @@ const Admin = (props) => {
if (responseJson.lead_info !== undefined && responseJson.lead_info !== null) { if (responseJson.lead_info !== undefined && responseJson.lead_info !== null) {
var leads = [] var leads = []
if (responseJson.lead_info.contacted) {
leads.push("contacted")
}
if (responseJson.lead_info.customer) { if (responseJson.lead_info.customer) {
leads.push("customer") leads.push("customer")
} }
@@ -1181,11 +1349,6 @@ const Admin = (props) => {
var localData = ""; var localData = "";
// useEffect(() => {
// console.log('confirm', fileContent);
// }, [fileContent])
const getSchedules = () => { const getSchedules = () => {
fetch(globalUrl + "/api/v1/workflows/schedules", { fetch(globalUrl + "/api/v1/workflows/schedules", {
@@ -1322,6 +1485,10 @@ const Admin = (props) => {
}); });
}; };
useEffect(() => {
getUsers()
}, []);
const getSettings = () => { const getSettings = () => {
fetch(globalUrl + "/api/v1/getsettings", { fetch(globalUrl + "/api/v1/getsettings", {
method: "GET", method: "GET",
@@ -2189,27 +2356,45 @@ const Admin = (props) => {
*/} */}
{userdata.support === true ? {userdata.support === true ?
<FormControl sx={{ m: 1, width: 300, }} style={{top: -10, right: 50, position: "absolute" }}> <span style={{display: "flex", top: -10, right: 50, position: "absolute"}}>
<InputLabel id="">Status</InputLabel> <a href={mailsendingButton(selectedOrganization)} target="_blank" rel="noopener noreferrer" style={{textDecoration: "none"}} disabled={selectedStatus.length !== 0}>
<Select <Button
style={{minWidth: 150, maxWidth: 150, }} variant="outlined"
labelId="multiselect-status" color="primary"
id="multiselect-status" disabled={selectedStatus.length !== 0}
multiple style={{ minWidth: 80, maxWidth: 80, height: "100%", }}
value={selectedStatus} onClick={() => {
onChange={handleStatusChange} console.log("Should send mail to admins of org with context")
input={<OutlinedInput label="Status" />} handleStatusChange({target: {value: ["contacted"]}})
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps} // open a mailto with subject "hello" and sender "frikky@shuffler.io"
> }}
{["lead", "pov", "demo done", "customer", "student", ].map((name) => ( >
<MenuItem key={name} value={name}> Sales mail
<Checkbox checked={selectedStatus.indexOf(name) > -1} /> </Button>
<ListItemText primary={name} /> </a>
</MenuItem> <FormControl sx={{ m: 1, width: 300, }} style={{}}>
))} <InputLabel id="">Status</InputLabel>
</Select> <Select
</FormControl> style={{minWidth: 150, maxWidth: 150, }}
labelId="multiselect-status"
id="multiselect-status"
multiple
value={selectedStatus}
onChange={handleStatusChange}
input={<OutlinedInput label="Status" />}
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps}
>
{["contacted", "lead", "pov", "demo done", "customer", "student", ].map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={selectedStatus.indexOf(name) > -1} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</span>
: null} : null}
<Tooltip <Tooltip
@@ -2588,10 +2773,6 @@ const Admin = (props) => {
userdata={userdata} userdata={userdata}
adminTab={adminTab} adminTab={adminTab}
globalUrl={globalUrl} globalUrl={globalUrl}
handleGetOrg={handleGetOrg}
selectedOrganization={selectedOrganization}
selectedOrganization={selectedOrganization}
setSelectedOrganization={setSelectedOrganization}
checkLogin={checkLogin} checkLogin={checkLogin}
/> />
: adminTab === 3 ? : adminTab === 3 ?
+8 -7
View File
@@ -11484,7 +11484,7 @@ const AngularWorkflow = (defaultprops) => {
> >
<div style={{ flex: "1" }}> <div style={{ flex: "1" }}>
<h3 style={{ marginBottom: "5px" }}> <h3 style={{ marginBottom: "5px" }}>
{selectedTrigger.app_name}: {selectedTrigger.status} {selectedTrigger.app_name}
</h3> </h3>
<a <a
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -11526,7 +11526,7 @@ const AngularWorkflow = (defaultprops) => {
onChange={selectedTriggerChange} onChange={selectedTriggerChange}
/> />
<div style={{ marginTop: "20px" }}> {/*<div style={{ marginTop: "20px" }}>
Environment: Environment:
<TextField <TextField
style={{ style={{
@@ -11549,6 +11549,7 @@ const AngularWorkflow = (defaultprops) => {
value={selectedTrigger.environment} value={selectedTrigger.environment}
/> />
</div> </div>
*/}
<Divider <Divider
style={{ style={{
marginTop: "20px", marginTop: "20px",
@@ -13697,7 +13698,7 @@ const AngularWorkflow = (defaultprops) => {
> >
<h2 style={{ color: "rgba(255,255,255,0.5)" }}> <h2 style={{ color: "rgba(255,255,255,0.5)" }}>
<DirectionsRunIcon style={{ marginRight: 10 }} /> <DirectionsRunIcon style={{ marginRight: 10 }} />
All Executions All Workflow Runs
</h2> </h2>
</Breadcrumbs> </Breadcrumbs>
<Button <Button
@@ -13710,7 +13711,7 @@ const AngularWorkflow = (defaultprops) => {
color="primary" color="primary"
> >
<CachedIcon style={{ marginRight: 10 }} /> <CachedIcon style={{ marginRight: 10 }} />
Refresh executions Refresh Runs
</Button> </Button>
<Divider <Divider
style={{ style={{
@@ -13956,7 +13957,7 @@ const AngularWorkflow = (defaultprops) => {
style={{ color: "rgba(255,255,255,0.5)", cursor: "pointer" }} style={{ color: "rgba(255,255,255,0.5)", cursor: "pointer" }}
onClick={() => { }} onClick={() => { }}
> >
See other Executions See more runs
</h2> </h2>
</span> </span>
</Breadcrumbs> </Breadcrumbs>
@@ -14781,7 +14782,7 @@ const AngularWorkflow = (defaultprops) => {
{curapp === null ? null : ( {curapp === null ? null : (
<img <img
alt={selectedResult.action.app_name} alt={selectedResult.action.app_name}
src={selectedResult === undefined ? theme.palette.defaultImage : selectedResult.action.app_name === "shuffle-subflow" ? triggers[4].large_image : selectedResult.action.app_name === "User Input" ? triggers[5].large_image : selectedResult.action.large_image !== undefined && selectedResult.action.large_image !== null && selectedResult.action.large_image !== "" ? selectedResult.action.large_image : curapp.large_image} src={selectedResult === undefined ? theme.palette.defaultImage : selectedResult.action.app_name === "shuffle-subflow" ? triggers[4].large_image : selectedResult.action.app_name === "User Input" ? triggers[5].large_image : selectedResult.action !== undefined && selectedResult.action.large_image !== undefined && selectedResult.action.large_image !== null && selectedResult.action.large_image !== "" ? selectedResult.action.large_image : curapp !== undefined ? curapp.large_image : theme.palette.defaultImage}
style={{ style={{
marginRight: 20, marginRight: 20,
width: imgsize, width: imgsize,
@@ -15509,7 +15510,7 @@ const AngularWorkflow = (defaultprops) => {
<div> <div>
<DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}> <DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
<div style={{ color: "white" }}> <div style={{ color: "white" }}>
Authentication for {selectedApp.name} Authentication for {selectedApp.name.replace("_", " ", -1)}
</div> </div>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
File diff suppressed because one or more lines are too long
+2
View File
@@ -127,6 +127,8 @@ const Search = (props) => {
textColor="secondary" textColor="secondary"
onChange={setConfig} onChange={setConfig}
aria-label="disabled tabs example" aria-label="disabled tabs example"
variant="scrollable"
scrollButtons="auto"
> >
<Tab <Tab
label=<span> label=<span>
+1 -1
View File
@@ -334,7 +334,7 @@ const Welcome = (props) => {
const defaultImage = "/images/experienced.png" const defaultImage = "/images/experienced.png"
const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : defaultImage const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : defaultImage
return ( return (
<div style={{width: 1000, margin: "auto", paddingBottom: 150, minHeight: 1500, }}> <div style={{width: 1000, margin: "auto", paddingBottom: 150, minHeight: 1500, marginTop: 50, }}>
{/* {/*
<div style={{position: "fixed", bottom: 110, right: 110, display: "flex", }}> <div style={{position: "fixed", bottom: 110, right: 110, display: "flex", }}>
<img src="/images/Arrow.png" style={{width: 250, height: "100%",}} /> <img src="/images/Arrow.png" style={{width: 250, height: "100%",}} />
+33 -9
View File
@@ -9,6 +9,7 @@ import { Navigate } from "react-router-dom";
import SecurityFramework from '../components/SecurityFramework.jsx'; import SecurityFramework from '../components/SecurityFramework.jsx';
import EditWorkflow from "../components/EditWorkflow.jsx" import EditWorkflow from "../components/EditWorkflow.jsx"
import { ShepherdTour, ShepherdTourContext } from 'react-shepherd' import { ShepherdTour, ShepherdTourContext } from 'react-shepherd'
import Priority from "../components/Priority.jsx";
import { isMobile } from "react-device-detect" import { isMobile } from "react-device-detect"
@@ -524,7 +525,7 @@ export const validateJson = (showResult) => {
}; };
const Workflows = (props) => { const Workflows = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props; const { globalUrl, isLoggedIn, isLoaded, userdata, checkLogin } = props;
document.title = "Shuffle - Workflows"; document.title = "Shuffle - Workflows";
let navigate = useNavigate(); let navigate = useNavigate();
@@ -585,6 +586,7 @@ const Workflows = (props) => {
const [drawerOpen, setDrawerOpen] = React.useState(false) const [drawerOpen, setDrawerOpen] = React.useState(false)
const [videoViewOpen, setVideoViewOpen] = React.useState(false) const [videoViewOpen, setVideoViewOpen] = React.useState(false)
const [gettingStartedItems, setGettingStartedItems] = React.useState([]) const [gettingStartedItems, setGettingStartedItems] = React.useState([])
const drawerWidth = drawerOpen ? 325 : 0 const drawerWidth = drawerOpen ? 325 : 0
const sidebarKey = "getting_started_sidebar" const sidebarKey = "getting_started_sidebar"
@@ -618,7 +620,6 @@ const Workflows = (props) => {
setDrawerOpen(true) setDrawerOpen(true)
} else { } else {
if (sidebar === "open") { if (sidebar === "open") {
console.log("OPEN the thingy!")
setDrawerOpen(true) setDrawerOpen(true)
} else { } else {
setDrawerOpen(false) setDrawerOpen(false)
@@ -1126,6 +1127,11 @@ const Workflows = (props) => {
var newcategories = [] var newcategories = []
for (var key in categorydata) { for (var key in categorydata) {
var category = categorydata[key] var category = categorydata[key]
// Check if category is bool
if (typeof category === "boolean") {
continue
}
category.matches = [] category.matches = []
for (var subcategorykey in category.list) { for (var subcategorykey in category.list) {
@@ -1216,8 +1222,8 @@ const Workflows = (props) => {
color: "#ffffff", color: "#ffffff",
width: "100%", width: "100%",
display: "flex", display: "flex",
minWidth: isMobile ? "100%" : drawerWidth > 0 ? 824 : 1024, minWidth: isMobile ? "100%" : 1024,
maxWidth: isMobile ? "100%" : drawerWidth > 0 ? 824 : 1024, maxWidth: isMobile ? "100%" : 1024,
margin: drawerWidth === 0 ? "auto" : `auto ${drawerWidth+100} auto auto`, margin: drawerWidth === 0 ? "auto" : `auto ${drawerWidth+100} auto auto`,
paddingBottom: 200, paddingBottom: 200,
}; };
@@ -1583,7 +1589,8 @@ const Workflows = (props) => {
const innerColor = "rgba(255,255,255,0.3)"; const innerColor = "rgba(255,255,255,0.3)";
const setupPaperStyle = { const setupPaperStyle = {
minHeight: paperAppStyle.minHeight, minHeight: paperAppStyle.minHeight,
width: paperAppStyle.width, maxWidth: "100%",
minWidth: paperAppStyle.width,
color: innerColor, color: innerColor,
padding: paperAppStyle.padding, padding: paperAppStyle.padding,
borderRadius: paperAppStyle.borderRadius, borderRadius: paperAppStyle.borderRadius,
@@ -1612,7 +1619,7 @@ const Workflows = (props) => {
}} }}
> >
<Tooltip title={`New Workflow`} placement="bottom"> <Tooltip title={`New Workflow`} placement="bottom">
<span style={{ textAlign: "center", width: 300, margin: "auto" }}> <span style={{ textAlign: "center", minWidth: 300, margin: "auto" }}>
<AddCircleIcon style={{ height: 65, width: 65 }} /> <AddCircleIcon style={{ height: 65, width: 65 }} />
</span> </span>
</Tooltip> </Tooltip>
@@ -3197,7 +3204,7 @@ const Workflows = (props) => {
var workflowDelay = -150 var workflowDelay = -150
var appDelay = -75 var appDelay = -75
const foundPriority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
return ( return (
<div style={viewStyle}> <div style={viewStyle}>
<div style={workflowViewStyle}> <div style={workflowViewStyle}>
@@ -3470,9 +3477,17 @@ const Workflows = (props) => {
</div> </div>
: null} : null}
<div style={{marginTop: 15, }}> {foundPriority != null && workflows.length < 6 ?
<Priority
globalUrl={globalUrl}
priority={foundPriority}
checkLogin={checkLogin}
/>
: null}
<div style={{marginTop: 15, marginBottom: 50, }}>
{view === "grid" ? ( {view === "grid" ? (
<Grid container spacing={4} style={paperAppContainer}> <Grid container spacing={filteredWorkflows.length === 0 ? 12 : filteredWorkflows.length === 1 ? 6 : 4} style={paperAppContainer}>
<Zoom in={true} style={{ transitionDelay: `${workflowDelay}ms` }}> <Zoom in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
<NewWorkflowPaper /> <NewWorkflowPaper />
</Zoom> </Zoom>
@@ -3507,6 +3522,15 @@ const Workflows = (props) => {
)} )}
</div> </div>
{foundPriority != null && filteredWorkflows.length > 6 ?
<Priority
style={{marginTop: 15, }}
globalUrl={globalUrl}
priority={foundPriority}
checkLogin={checkLogin}
/>
: null}
<div style={{ marginBottom: 100 }} /> <div style={{ marginBottom: 100 }} />
</div> </div>
</div> </div>
+1 -1
View File
@@ -1,5 +1,5 @@
NAME=shuffle-orborus NAME=shuffle-orborus
VERSION=1.2.0 VERSION=1.2.1
echo "Running docker build with $NAME:$VERSION" echo "Running docker build with $NAME:$VERSION"
#docker rmi frikky/shuffle:$NAME --force #docker rmi frikky/shuffle:$NAME --force
+15 -20
View File
@@ -207,10 +207,6 @@ func deployServiceWorkers(image string) {
// Looks for and cleans up all existing items in swarm we can't re-use (Shuffle only) // Looks for and cleans up all existing items in swarm we can't re-use (Shuffle only)
// frikky@debian:~/git/shuffle/functions/onprem/worker$ docker service create --replicas 5 --name shuffle-workers --env SHUFFLE_SWARM_CONFIG=run --publish published=33333,target=33333 ghcr.io/shuffle/shuffle-worker:nightly // frikky@debian:~/git/shuffle/functions/onprem/worker$ docker service create --replicas 5 --name shuffle-workers --env SHUFFLE_SWARM_CONFIG=run --publish published=33333,target=33333 ghcr.io/shuffle/shuffle-worker:nightly
networkName := "shuffle_swarm_executions"
if len(swarmNetworkName) > 0 {
networkName = swarmNetworkName
}
// Get a list of network interfaces // Get a list of network interfaces
interfaces, err := net.Interfaces() interfaces, err := net.Interfaces()
@@ -260,6 +256,11 @@ func deployServiceWorkers(image string) {
//docker network create --driver=overlay workers //docker network create --driver=overlay workers
// Specific subnet? // Specific subnet?
networkName := "shuffle_swarm_executions"
if len(swarmNetworkName) > 0 {
networkName = swarmNetworkName
}
networkCreateOptions := types.NetworkCreate{ networkCreateOptions := types.NetworkCreate{
Driver: "overlay", Driver: "overlay",
Options: options, Options: options,
@@ -335,15 +336,6 @@ func deployServiceWorkers(image string) {
//} //}
} }
//serviceOptions := types.ServiceCreateOptions{}
//service, err := dockercli.ServiceCreate(
// context.Background(),
// serviceSpec,
// serviceOptions,
//)
//containerName := fmt.Sprintf("shuffle-worker-%s", parsedUuid)
replicas := uint64(1) replicas := uint64(1)
scaleReplicas := os.Getenv("SHUFFLE_SCALE_REPLICAS") scaleReplicas := os.Getenv("SHUFFLE_SCALE_REPLICAS")
if len(scaleReplicas) > 0 { if len(scaleReplicas) > 0 {
@@ -358,7 +350,6 @@ func deployServiceWorkers(image string) {
} }
innerContainerName := fmt.Sprintf("shuffle-workers") innerContainerName := fmt.Sprintf("shuffle-workers")
cnt, _ := findActiveSwarmNodes() cnt, _ := findActiveSwarmNodes()
nodeCount := uint64(1) nodeCount := uint64(1)
if cnt > 0 { if cnt > 0 {
@@ -394,8 +385,12 @@ func deployServiceWorkers(image string) {
swarm.NetworkAttachmentConfig{ swarm.NetworkAttachmentConfig{
Target: networkName, Target: networkName,
}, },
swarm.NetworkAttachmentConfig{
Target: "ingress",
},
}, },
EndpointSpec: &swarm.EndpointSpec{ EndpointSpec: &swarm.EndpointSpec{
Mode: "vip",
Ports: []swarm.PortConfig{ Ports: []swarm.PortConfig{
swarm.PortConfig{ swarm.PortConfig{
Protocol: swarm.PortConfigProtocolTCP, Protocol: swarm.PortConfigProtocolTCP,
@@ -425,9 +420,9 @@ func deployServiceWorkers(image string) {
fmt.Sprintf("TZ=%s", timezone), fmt.Sprintf("TZ=%s", timezone),
fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")), fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")),
}, },
Hosts: []string{ //Hosts: []string{
innerContainerName, // innerContainerName,
}, //},
}, },
RestartPolicy: &swarm.RestartPolicy{ RestartPolicy: &swarm.RestartPolicy{
Condition: swarm.RestartPolicyConditionOnFailure, Condition: swarm.RestartPolicyConditionOnFailure,
@@ -747,8 +742,8 @@ func initializeImages() {
} }
if baseimagename == "" { if baseimagename == "" {
baseimagename = "shuffle/shuffle" // Dockerhub baseimagename = "frikky/shuffle" // Dockerhub
baseimagename = "shuffle" // Github (ghcr.io) baseimagename = "shuffle" // Github (ghcr.io)
log.Printf("[DEBUG] Setting baseimagename") log.Printf("[DEBUG] Setting baseimagename")
} }
@@ -1582,6 +1577,6 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error {
_ = body _ = body
log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s. DEBUGGING: docker service logs shuffle-workers | grep %s", workflowExecution.ExecutionId, streamUrl, workflowExecution.ExecutionId) log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s. DEBUGGING: docker service logs shuffle-workers 2&>1 | grep %s", workflowExecution.ExecutionId, streamUrl, workflowExecution.ExecutionId)
return nil return nil
} }