+309
-115
@@ -67,7 +67,7 @@ class AppBase:
|
|||||||
"started_at": int(time.time()),
|
"started_at": int(time.time()),
|
||||||
"status": "EXECUTING"
|
"status": "EXECUTING"
|
||||||
}
|
}
|
||||||
self.logger.info("ACTION RESULT: %s", action_result)
|
self.logger.info("ACTION RESULT (start): %s", action_result)
|
||||||
|
|
||||||
if len(self.action) == 0:
|
if len(self.action) == 0:
|
||||||
print("ACTION env not defined")
|
print("ACTION env not defined")
|
||||||
@@ -361,18 +361,115 @@ class AppBase:
|
|||||||
newlist.append("parsing_error")
|
newlist.append("parsing_error")
|
||||||
return " ".join(newlist)
|
return " ".join(newlist)
|
||||||
|
|
||||||
|
# Parses JSON loops and such down to the item you're looking for
|
||||||
|
def recurse_json(basejson, parsersplit):
|
||||||
|
match = "#(\d+):?-?([0-9a-z]+)?#?"
|
||||||
|
print("Split: %s\n%s" % (parsersplit, basejson))
|
||||||
|
try:
|
||||||
|
outercnt = 0
|
||||||
|
|
||||||
|
# Loops over split values
|
||||||
|
for value in parsersplit:
|
||||||
|
print("VALUE: %s\n" % value)
|
||||||
|
actualitem = re.findall(match, value, re.MULTILINE)
|
||||||
|
if value == "#":
|
||||||
|
newvalue = []
|
||||||
|
for innervalue in basejson:
|
||||||
|
# 1. Check the next item (message)
|
||||||
|
# 2. Call this function again
|
||||||
|
|
||||||
|
try:
|
||||||
|
ret, is_loop = recurse_json(innervalue, parsersplit[outercnt+1:])
|
||||||
|
except IndexError:
|
||||||
|
# Only in here if it's the last loop without anything in it?
|
||||||
|
ret, is_loop = recurse_json(innervalue, parsersplit[outercnt:])
|
||||||
|
|
||||||
|
newvalue.append(ret)
|
||||||
|
|
||||||
|
# Magical way of returning which makes app sdk identify
|
||||||
|
# it as multi execution
|
||||||
|
return newvalue, True
|
||||||
|
elif len(actualitem) > 0:
|
||||||
|
# FIXME: This is absolutely not perfect.
|
||||||
|
print("In recursion v2: ", actualitem)
|
||||||
|
|
||||||
|
is_loop = True
|
||||||
|
newvalue = []
|
||||||
|
firstitem = actualitem[0][0]
|
||||||
|
seconditem = actualitem[0][1]
|
||||||
|
|
||||||
|
# Means it's a single item -> continue
|
||||||
|
if seconditem == "":
|
||||||
|
print("In first - handling %s", seconditem)
|
||||||
|
tmpitem = basejson[int(firstitem)]
|
||||||
|
try:
|
||||||
|
newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:])
|
||||||
|
except IndexError:
|
||||||
|
newvalue, is_loop = (tmpitem, parsersplit[outercnt+1:])
|
||||||
|
else:
|
||||||
|
if seconditem == "max":
|
||||||
|
seconditem = len(basejson)
|
||||||
|
if seconditem == "min":
|
||||||
|
seconditem = 0
|
||||||
|
|
||||||
|
newvalue = []
|
||||||
|
for i in range(int(firstitem), int(seconditem)):
|
||||||
|
# 1. Check the next item (message)
|
||||||
|
# 2. Call this function again
|
||||||
|
print("Base: %s" % basejson[i])
|
||||||
|
|
||||||
|
try:
|
||||||
|
ret, is_loop = recurse_json(basejson[i], parsersplit[outercnt+1:])
|
||||||
|
except IndexError:
|
||||||
|
print("INDEXERROR: ", parsersplit[outercnt])
|
||||||
|
#ret = innervalue
|
||||||
|
ret, is_loop = recurse_json(innervalue, parsersplit[outercnt:])
|
||||||
|
|
||||||
|
print(ret)
|
||||||
|
#exit()
|
||||||
|
newvalue.append(ret)
|
||||||
|
|
||||||
|
return newvalue, is_loop
|
||||||
|
|
||||||
|
# FIXME: Add specific loop for other indexes
|
||||||
|
else:
|
||||||
|
#print("BEFORE NORMAL VALUE: ", basejson, value)
|
||||||
|
if len(value) == 0:
|
||||||
|
return basejson, False
|
||||||
|
|
||||||
|
if isinstance(basejson[value], str):
|
||||||
|
print(f"LOADING STRING '%s' AS JSON" % basejson[value])
|
||||||
|
try:
|
||||||
|
basejson = json.loads(basejson[value])
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value])
|
||||||
|
return basejson[value], False
|
||||||
|
else:
|
||||||
|
basejson = basejson[value]
|
||||||
|
|
||||||
|
outercnt += 1
|
||||||
|
|
||||||
|
except KeyError as e:
|
||||||
|
print("Lower keyerror: %s" % e)
|
||||||
|
#return basejson
|
||||||
|
#return "KeyError: Couldn't find key: %s" % e
|
||||||
|
|
||||||
|
return basejson, False
|
||||||
|
|
||||||
# Takes a workflow execution as argument
|
# Takes a workflow execution as argument
|
||||||
# Returns a string if the result is single, or a list if it's a list
|
# Returns a string if the result is single, or a list if it's a list
|
||||||
def get_json_value(execution_data, input_data):
|
def get_json_value(execution_data, input_data):
|
||||||
parsersplit = input_data.split(".")
|
parsersplit = input_data.split(".")
|
||||||
actionname = parsersplit[0][1:].replace(" ", "_", -1)
|
actionname = parsersplit[0][1:].replace(" ", "_", -1)
|
||||||
|
#Actionname: Start_node
|
||||||
|
|
||||||
print(f"Actionname: {actionname}")
|
print(f"Actionname: {actionname}")
|
||||||
|
|
||||||
# 1. Find the action
|
# 1. Find the action
|
||||||
baseresult = ""
|
baseresult = ""
|
||||||
actionname_lower = actionname.lower()
|
actionname_lower = actionname.lower()
|
||||||
try:
|
try:
|
||||||
if actionname_lower == "exec":
|
if actionname_lower == "exec" or actionname_lower == "webhook" or actionname_lower == "schedule" or actionname_lower == "userinput" or actionname_lower == "email_trigger" or actionname_lower == "trigger":
|
||||||
baseresult = execution_data["execution_argument"]
|
baseresult = execution_data["execution_argument"]
|
||||||
else:
|
else:
|
||||||
for result in execution_data["results"]:
|
for result in execution_data["results"]:
|
||||||
@@ -421,76 +518,37 @@ class AppBase:
|
|||||||
|
|
||||||
# 2. Find the JSON data
|
# 2. Find the JSON data
|
||||||
if len(baseresult) == 0:
|
if len(baseresult) == 0:
|
||||||
return ""
|
return "", False
|
||||||
|
|
||||||
if len(parsersplit) == 1:
|
if len(parsersplit) == 1:
|
||||||
return baseresult
|
return baseresult, False
|
||||||
|
|
||||||
baseresult = baseresult.replace("\'", "\"")
|
baseresult = baseresult.replace("\'", "\"")
|
||||||
basejson = {}
|
basejson = {}
|
||||||
try:
|
try:
|
||||||
basejson = json.loads(baseresult)
|
basejson = json.loads(baseresult)
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
return baseresult
|
return baseresult, False
|
||||||
|
|
||||||
# This whole thing should be recursive.
|
data, is_loop = recurse_json(basejson, parsersplit[1:])
|
||||||
try:
|
parseditem = data
|
||||||
cnt = 0
|
if is_loop:
|
||||||
for value in parsersplit[1:]:
|
print("DATA IS A LOOP - SHOULD WRAP")
|
||||||
cnt += 1
|
if parsersplit[-1] == "#":
|
||||||
|
print("SET DATA WRAPPER TO NORMAL!")
|
||||||
|
parseditem = "${SHUFFLE_NO_SPLITTER%s}$" % json.dumps(data)
|
||||||
|
else:
|
||||||
|
# Return value: ${id[12345, 45678]}$
|
||||||
|
print("SET DATA WRAPPER TO %s!" % parsersplit[-1])
|
||||||
|
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
|
||||||
|
|
||||||
print("VALUE: %s" % value)
|
return parseditem, is_loop
|
||||||
if value == "#":
|
|
||||||
# FIXME - not recursive - should go deeper if there are more #
|
|
||||||
print("HANDLE RECURSIVE LOOP OF %s" % basejson)
|
|
||||||
returnlist = []
|
|
||||||
try:
|
|
||||||
for innervalue in basejson:
|
|
||||||
print("Value: %s" % innervalue[parsersplit[cnt+1]])
|
|
||||||
returnlist.append(innervalue[parsersplit[cnt+1]])
|
|
||||||
except IndexError as e:
|
|
||||||
print("Indexerror inner: %s" % e)
|
|
||||||
# Basically means its a normal list, not a crazy one :)
|
|
||||||
# Custom format for ${name[0,1,2,...]}$
|
|
||||||
indexvalue = "${NO_SPLITTER%s}$" % json.dumps(basejson)
|
|
||||||
if len(returnlist) > 0:
|
|
||||||
indexvalue = "${NO_SPLITTER%s}$" % json.dumps(returnlist)
|
|
||||||
|
|
||||||
print("INDEXVAL: ", indexvalue)
|
|
||||||
return indexvalue
|
|
||||||
except TypeError as e:
|
|
||||||
print("TypeError inner: %s" % e)
|
|
||||||
|
|
||||||
# Example format: ${[]}$
|
|
||||||
parseditem = "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist))
|
|
||||||
print("PARSED LOOP ITEM: %s" % parseditem)
|
|
||||||
return parseditem
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("BEFORE NORMAL VALUE: ", basejson, value)
|
|
||||||
if len(value) == 0:
|
|
||||||
return basejson
|
|
||||||
|
|
||||||
if isinstance(basejson[value], str):
|
|
||||||
print(f"LOADING STRING '%s' AS JSON" % basejson[value])
|
|
||||||
try:
|
|
||||||
basejson = json.loads(basejson[value])
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value])
|
|
||||||
return basejson[value]
|
|
||||||
else:
|
|
||||||
basejson = basejson[value]
|
|
||||||
|
|
||||||
except KeyError as e:
|
|
||||||
print("Lower keyerror: %s" % e)
|
|
||||||
return "KeyError: Couldn't find key: %s" % e
|
|
||||||
|
|
||||||
return basejson
|
|
||||||
|
|
||||||
# Parses parameters sent to it and returns whether it did it successfully with the values found
|
# Parses parameters sent to it and returns whether it did it successfully with the values found
|
||||||
def parse_params(action, fullexecution, parameter):
|
def parse_params(action, fullexecution, parameter):
|
||||||
# Skip if it starts with $?
|
# Skip if it starts with $?
|
||||||
jsonparsevalue = "$."
|
jsonparsevalue = "$."
|
||||||
|
is_loop = False
|
||||||
|
|
||||||
# Matches with space in the first part, but not in subsequent parts.
|
# Matches with space in the first part, but not in subsequent parts.
|
||||||
# JSON / yaml etc shouldn't have spaces in their fields anyway.
|
# JSON / yaml etc shouldn't have spaces in their fields anyway.
|
||||||
@@ -510,7 +568,9 @@ class AppBase:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
value = get_json_value(fullexecution, to_be_replaced)
|
# Handles for loops etc.
|
||||||
|
value, is_loop = get_json_value(fullexecution, to_be_replaced)
|
||||||
|
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
@@ -588,7 +648,8 @@ class AppBase:
|
|||||||
# This will never be a loop aka multi argument
|
# This will never be a loop aka multi argument
|
||||||
parameter["value"] = to_be_replaced
|
parameter["value"] = to_be_replaced
|
||||||
|
|
||||||
value = get_json_value(fullexecution, to_be_replaced)
|
value, is_loop = get_json_value(fullexecution, to_be_replaced)
|
||||||
|
print("Loop: %s" % is_loop)
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
@@ -600,7 +661,7 @@ class AppBase:
|
|||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
||||||
|
|
||||||
return "", parameter["value"]
|
return "", parameter["value"], is_loop
|
||||||
|
|
||||||
def run_validation(sourcevalue, check, destinationvalue):
|
def run_validation(sourcevalue, check, destinationvalue):
|
||||||
self.logger.info("Checking %s %s %s" % (sourcevalue, check, destinationvalue))
|
self.logger.info("Checking %s %s %s" % (sourcevalue, check, destinationvalue))
|
||||||
@@ -654,8 +715,6 @@ class AppBase:
|
|||||||
if branch["destination_id"] != action["id"]:
|
if branch["destination_id"] != action["id"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.logger.info("Relevant branch: %s" % branch)
|
|
||||||
|
|
||||||
# Remove anything without a condition
|
# Remove anything without a condition
|
||||||
try:
|
try:
|
||||||
if (branch["conditions"]) == 0 or branch["conditions"] == None:
|
if (branch["conditions"]) == 0 or branch["conditions"] == None:
|
||||||
@@ -671,7 +730,7 @@ class AppBase:
|
|||||||
|
|
||||||
# Parse all values first here
|
# Parse all values first here
|
||||||
sourcevalue = condition["source"]["value"]
|
sourcevalue = condition["source"]["value"]
|
||||||
check, sourcevalue = parse_params(action, fullexecution, condition["source"])
|
check, sourcevalue, is_loop = parse_params(action, fullexecution, condition["source"])
|
||||||
if check:
|
if check:
|
||||||
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
||||||
|
|
||||||
@@ -680,7 +739,7 @@ class AppBase:
|
|||||||
sourcevalue = parse_wrapper_start(sourcevalue)
|
sourcevalue = parse_wrapper_start(sourcevalue)
|
||||||
destinationvalue = condition["destination"]["value"]
|
destinationvalue = condition["destination"]["value"]
|
||||||
|
|
||||||
check, destinationvalue = parse_params(action, fullexecution, condition["destination"])
|
check, destinationvalue, is_loop = parse_params(action, fullexecution, condition["destination"])
|
||||||
if check:
|
if check:
|
||||||
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
||||||
|
|
||||||
@@ -795,8 +854,10 @@ class AppBase:
|
|||||||
minlength = 0
|
minlength = 0
|
||||||
multi_parameters = json.loads(json.dumps(params))
|
multi_parameters = json.loads(json.dumps(params))
|
||||||
multiexecution = False
|
multiexecution = False
|
||||||
|
multi_execution_lists = []
|
||||||
for parameter in action["parameters"]:
|
for parameter in action["parameters"]:
|
||||||
check, value = parse_params(action, fullexecution, parameter)
|
check, value, is_loop = parse_params(action, fullexecution, parameter)
|
||||||
|
|
||||||
if check:
|
if check:
|
||||||
raise "Value check error: %s" % Exception(check)
|
raise "Value check error: %s" % Exception(check)
|
||||||
|
|
||||||
@@ -811,52 +872,91 @@ class AppBase:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
print("Return value: %s" % value)
|
||||||
actionname = action["name"]
|
actionname = action["name"]
|
||||||
#print("Multicheck ", actualitem)
|
#print("Multicheck ", actualitem)
|
||||||
|
print("Actual item: %s" % actualitem)
|
||||||
if len(actualitem) > 0:
|
if len(actualitem) > 0:
|
||||||
multiexecution = True
|
multiexecution = True
|
||||||
|
|
||||||
# This is here to handle for loops within variables.. kindof
|
# Loop WITHOUT JSON variables go here.
|
||||||
# 1. Find the length of the longest array
|
# Loop WITH variables go in else.
|
||||||
# 2. Build an array with the base values based on parameter["value"]
|
if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER":
|
||||||
# 3. Get the n'th value of the generated list from values
|
print("Pre replacement: %s" % actualitem[0][2])
|
||||||
# 4. Execute all n answers
|
tmpitem = value
|
||||||
replacements = {}
|
|
||||||
for replace in actualitem:
|
|
||||||
try:
|
|
||||||
to_be_replaced = replace[0]
|
|
||||||
actualitem = replace[2]
|
|
||||||
except IndexError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
replacement = actualitem[0][2]
|
||||||
|
if replacement.startswith("\"") and replacement.endswith("\""):
|
||||||
|
replacement = replacement[1:len(replacement)-1]
|
||||||
|
|
||||||
|
replacement = replacement.replace("\'", "\"", -1)
|
||||||
|
print("POST replacement: %s" % replacement)
|
||||||
|
|
||||||
|
json_replacement = replacement
|
||||||
try:
|
try:
|
||||||
itemlist = json.loads(actualitem)
|
json_replacement = json.loads(replacement)
|
||||||
if len(itemlist) > minlength:
|
|
||||||
minlength = len(itemlist)
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
print("JSON Error: %s in %s" % (e, actualitem))
|
print("JSON error singular: %s" % e)
|
||||||
|
|
||||||
replacements[to_be_replaced] = actualitem
|
if len(json_replacement) > minlength:
|
||||||
|
minlength = len(json_replacement)
|
||||||
|
|
||||||
# This is a result array for JUST this value..
|
tmpitem = tmpitem.replace(actualitem[0][0], replacement, 1)
|
||||||
# What if there are more?
|
params[parameter["name"]] = tmpitem
|
||||||
resultarray = []
|
multi_execution_lists.append(json_replacement)
|
||||||
for i in range(0, minlength):
|
multi_parameters[parameter["name"]] = json_replacement
|
||||||
tmpitem = json.loads(json.dumps(parameter["value"]))
|
|
||||||
for key, value in replacements.items():
|
|
||||||
replacement = json.dumps(json.loads(value)[i])
|
|
||||||
if replacement.startswith("\"") and replacement.endswith("\""):
|
|
||||||
replacement = replacement[1:len(replacement)-1]
|
|
||||||
#except json.decoder.JSONDecodeError as e:
|
|
||||||
|
|
||||||
print("REPLACING %s with %s" % (key, replacement))
|
#print("LENGTH OF ARR: %d" % len(resultarray))
|
||||||
#replacement = parse_wrapper_start(replacement)
|
#print("RESULTARRAY: %s" % resultarray)
|
||||||
tmpitem = tmpitem.replace(key, replacement, -1)
|
print("MULTI finished: %s" % replacement)
|
||||||
|
else:
|
||||||
|
|
||||||
resultarray.append(tmpitem)
|
# This is here to handle for loops within variables.. kindof
|
||||||
|
# 1. Find the length of the longest array
|
||||||
|
# 2. Build an array with the base values based on parameter["value"]
|
||||||
|
# 3. Get the n'th value of the generated list from values
|
||||||
|
# 4. Execute all n answers
|
||||||
|
replacements = {}
|
||||||
|
for replace in actualitem:
|
||||||
|
try:
|
||||||
|
to_be_replaced = replace[0]
|
||||||
|
actualitem = replace[2]
|
||||||
|
except IndexError:
|
||||||
|
continue
|
||||||
|
|
||||||
# With this parameter ready, add it to... a greater list of parameters. Rofl
|
try:
|
||||||
multi_parameters[parameter["name"]] = resultarray
|
itemlist = json.loads(actualitem)
|
||||||
|
if len(itemlist) > minlength:
|
||||||
|
minlength = len(itemlist)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("JSON Error: %s in %s" % (e, actualitem))
|
||||||
|
|
||||||
|
replacements[to_be_replaced] = actualitem
|
||||||
|
|
||||||
|
# This is a result array for JUST this value..
|
||||||
|
# What if there are more?
|
||||||
|
resultarray = []
|
||||||
|
for i in range(0, minlength):
|
||||||
|
tmpitem = json.loads(json.dumps(parameter["value"]))
|
||||||
|
for key, value in replacements.items():
|
||||||
|
replacement = json.dumps(json.loads(value)[i])
|
||||||
|
if replacement.startswith("\"") and replacement.endswith("\""):
|
||||||
|
replacement = replacement[1:len(replacement)-1]
|
||||||
|
#except json.decoder.JSONDecodeError as e:
|
||||||
|
|
||||||
|
#print("REPLACING %s with %s" % (key, replacement))
|
||||||
|
#replacement = parse_wrapper_start(replacement)
|
||||||
|
tmpitem = tmpitem.replace(key, replacement, -1)
|
||||||
|
|
||||||
|
resultarray.append(tmpitem)
|
||||||
|
|
||||||
|
# With this parameter ready, add it to... a greater list of parameters. Rofl
|
||||||
|
print("LENGTH OF ARR: %d" % len(resultarray))
|
||||||
|
print("RESULTARRAY: %s" % resultarray)
|
||||||
|
if resultarray not in multi_execution_lists:
|
||||||
|
multi_execution_lists.append(resultarray)
|
||||||
|
|
||||||
|
multi_parameters[parameter["name"]] = resultarray
|
||||||
else:
|
else:
|
||||||
# Parses things like int(value)
|
# Parses things like int(value)
|
||||||
self.logger.info("Parsing wrapper data for %s" % value)
|
self.logger.info("Parsing wrapper data for %s" % value)
|
||||||
@@ -865,10 +965,33 @@ class AppBase:
|
|||||||
params[parameter["name"]] = value
|
params[parameter["name"]] = value
|
||||||
multi_parameters[parameter["name"]] = value
|
multi_parameters[parameter["name"]] = value
|
||||||
|
|
||||||
|
# Fix lists here
|
||||||
|
print("CHECKING multi execution list!")
|
||||||
|
if len(multi_execution_lists) > 0:
|
||||||
|
print("\n Multi execution list has more data: %d" % len(multi_execution_lists))
|
||||||
|
filteredlist = []
|
||||||
|
for listitem in multi_execution_lists:
|
||||||
|
if listitem in filteredlist:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filteredlist.append(listitem)
|
||||||
|
|
||||||
|
#print("New list length: %d" % len(filteredlist))
|
||||||
|
if len(filteredlist) > 1:
|
||||||
|
print("Calculating new multi-loop length with %d lists" % len(filteredlist))
|
||||||
|
tmplength = 1
|
||||||
|
for innerlist in filteredlist:
|
||||||
|
print("List length: %d. %d*%d" % (len(innerlist), len(innerlist), tmplength))
|
||||||
|
tmplength = len(innerlist)*tmplength
|
||||||
|
|
||||||
|
minlength = tmplength
|
||||||
|
|
||||||
|
print("New multi execution length: %d\n" % tmplength)
|
||||||
|
|
||||||
# FIXME - this is horrible, but works for now
|
# FIXME - this is horrible, but works for now
|
||||||
#for i in range(calltimes):
|
#for i in range(calltimes):
|
||||||
if not multiexecution:
|
if not multiexecution:
|
||||||
print("APP_SDK DONE: Starting normal execution of function")
|
print("APP_SDK DONE: Starting NORMAL execution of function")
|
||||||
newres = await func(**params)
|
newres = await func(**params)
|
||||||
#print("NEWRES: ", newres)
|
#print("NEWRES: ", newres)
|
||||||
if isinstance(newres, str):
|
if isinstance(newres, str):
|
||||||
@@ -881,20 +1004,67 @@ class AppBase:
|
|||||||
print("Can't handle type %s value from function" % (type(newres)))
|
print("Can't handle type %s value from function" % (type(newres)))
|
||||||
print("POST NEWRES RESULT: ", result)
|
print("POST NEWRES RESULT: ", result)
|
||||||
else:
|
else:
|
||||||
print("APP_SDK DONE: Starting MULTI execution with", multi_parameters)
|
print("APP_SDK DONE: Starting MULTI execution with values %s of length %d" % (multi_parameters, minlength))
|
||||||
# 1. Use number of executions based on longest array
|
# 1. Use number of executions based on the arrays being similar
|
||||||
# 2. Find the right value from the parsed multi_params
|
# 2. Find the right value from the parsed multi_params
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
json_object = False
|
json_object = False
|
||||||
for i in range(0, minlength):
|
for i in range(0, minlength):
|
||||||
# To be able to use the results as a list:
|
# To be able to use the results as a list:
|
||||||
baseparams = json.loads(json.dumps(multi_parameters))
|
baseparams = json.loads(json.dumps(multi_parameters))
|
||||||
|
# {'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:
|
try:
|
||||||
|
firstlist = True
|
||||||
for key, value in baseparams.items():
|
for key, value in baseparams.items():
|
||||||
|
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
baseparams[key] = value[i]
|
try:
|
||||||
|
newvalue = value[i]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if len(value) != minlength and len(value) > 0:
|
||||||
|
newarray = []
|
||||||
|
print("VALUE: ", value)
|
||||||
|
additiontime = minlength/len(value)
|
||||||
|
print("Bad length for value: %d - should be %d. Additiontime: %d" % (len(value), minlength, additiontime))
|
||||||
|
if firstlist:
|
||||||
|
print("Running normal list (FIRST)")
|
||||||
|
for subvalue in value:
|
||||||
|
for number in range(int(additiontime)):
|
||||||
|
newarray.append(subvalue)
|
||||||
|
else:
|
||||||
|
#print("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)
|
||||||
|
# print("%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)
|
||||||
|
print("NEW: %d" % tmp)
|
||||||
|
newarray[cnt] = value[tmp]
|
||||||
|
else:
|
||||||
|
newarray[cnt] = value[subvaluerange]
|
||||||
|
cnt += 1
|
||||||
|
|
||||||
|
#print("Newarray =", newarray)
|
||||||
|
newvalue = newarray[i]
|
||||||
|
firstlist = False
|
||||||
|
|
||||||
|
baseparams[key] = newvalue
|
||||||
except IndexError as e:
|
except IndexError as e:
|
||||||
print("IndexError: %s" % e)
|
print("IndexError: %s" % e)
|
||||||
baseparams[key] = "IndexError: %s" % e
|
baseparams[key] = "IndexError: %s" % e
|
||||||
@@ -902,25 +1072,49 @@ class AppBase:
|
|||||||
print("KeyError: %s" % e)
|
print("KeyError: %s" % e)
|
||||||
baseparams[key] = "KeyError: %s" % e
|
baseparams[key] = "KeyError: %s" % e
|
||||||
|
|
||||||
#print("Running with params %s" % baseparams)
|
print("Running with params %s" % baseparams)
|
||||||
ret = await func(**baseparams)
|
ret = await func(**baseparams)
|
||||||
ret = ret.replace("\"", "\\\"", -1)
|
if isinstance(ret, dict) or isinstance(ret, list):
|
||||||
print("Inner ret parsed: %s" % ret)
|
|
||||||
|
|
||||||
try:
|
|
||||||
results.append(json.loads(ret))
|
|
||||||
json_object = True
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
results.append(ret)
|
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:
|
||||||
|
#print("Json: %s" % e)
|
||||||
|
results.append(ret)
|
||||||
|
|
||||||
|
#print("Inner ret parsed: %s" % ret)
|
||||||
|
|
||||||
# Dump the result as a string of a list
|
# Dump the result as a string of a list
|
||||||
print("RESULTS: %s" % results)
|
#print("RESULTS: %s" % results)
|
||||||
if isinstance(results, list):
|
if isinstance(results, list):
|
||||||
print("JSON OBJECT? ", json_object)
|
print("JSON OBJECT? ", json_object)
|
||||||
if json_object:
|
if json_object:
|
||||||
result = json.dumps(results)
|
result = json.dumps(results)
|
||||||
else:
|
else:
|
||||||
result = "[\""+"\", \"".join(results)+"\"]"
|
result = "["
|
||||||
|
for item in results:
|
||||||
|
try:
|
||||||
|
json.loads(item)
|
||||||
|
result += item
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
# Common nested issue which puts " around everything
|
||||||
|
try:
|
||||||
|
tmpitem = item.replace("\\\"", "\"", -1)
|
||||||
|
json.loads(tmpitem)
|
||||||
|
result += tmpitem
|
||||||
|
|
||||||
|
except:
|
||||||
|
result += "\"%s\"" % item
|
||||||
|
|
||||||
|
result += ", "
|
||||||
|
|
||||||
|
result = result[:-2]
|
||||||
|
result += "]"
|
||||||
else:
|
else:
|
||||||
print("Normal result?")
|
print("Normal result?")
|
||||||
result = results
|
result = results
|
||||||
@@ -932,7 +1126,7 @@ class AppBase:
|
|||||||
action_result["result"] = result
|
action_result["result"] = result
|
||||||
|
|
||||||
self.logger.debug(f"Executed {action['label']}-{action['id']} with result: {result}")
|
self.logger.debug(f"Executed {action['label']}-{action['id']} with result: {result}")
|
||||||
self.logger.debug(f"Data: %s" % action_result)
|
#self.logger.debug(f"Data: %s" % action_result)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
print("TypeError issue: %s" % e)
|
print("TypeError issue: %s" % e)
|
||||||
action_result["status"] = "FAILURE"
|
action_result["status"] = "FAILURE"
|
||||||
@@ -947,7 +1141,7 @@ class AppBase:
|
|||||||
print(f"Failed to execute: {e}")
|
print(f"Failed to execute: {e}")
|
||||||
self.logger.exception(f"Failed to execute {e}-{action['id']}")
|
self.logger.exception(f"Failed to execute {e}-{action['id']}")
|
||||||
action_result["status"] = "FAILURE"
|
action_result["status"] = "FAILURE"
|
||||||
action_result["result"] = "General exception: %s" % e
|
action_result["result"] = f"General exception: {e}"
|
||||||
|
|
||||||
action_result["completed_at"] = int(time.time())
|
action_result["completed_at"] = int(time.time())
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
NAME=app_sdk
|
NAME=app_sdk
|
||||||
VERSION=0.6.2
|
VERSION=0.7.3
|
||||||
|
|
||||||
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
||||||
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|||||||
+82
-196
@@ -310,20 +310,12 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
|
|||||||
|
|
||||||
// Specific check for SSL verification
|
// Specific check for SSL verification
|
||||||
// This is critical for onprem stuff.
|
// This is critical for onprem stuff.
|
||||||
verifyParam := ""
|
//verifyParam := ""
|
||||||
verifyWrapper := ""
|
//verifyWrapper := ""
|
||||||
verifyAddin := ""
|
//verifyAddin := ""
|
||||||
if len(swagger.Servers) == 0 {
|
verifyParam := ", ssl_verify=False"
|
||||||
verifyParam = ", verify=True"
|
verifyWrapper := `if type(ssl_verify) == str: ssl_verify = False if ssl_verify.lower() == "false" or ssl_verify == "0" else True`
|
||||||
verifyWrapper = `if type(ssl_verify) == str: ssl_verify = False if ssl_verify.lower() == "false" or ssl_verify == "0" else True`
|
verifyAddin := ", verify=ssl_verify"
|
||||||
verifyAddin = ", verify=ssl_verify"
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
verifyParam = ", ssl_verify=True"
|
|
||||||
verifyWrapper = `if type(ssl_verify) == str: ssl_verify = False if ssl_verify.lower() == "false" or ssl_verify == "0" else True`
|
|
||||||
verifyAddin = ", verify=ssl_verify"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parameters) > 0 {
|
if len(parameters) > 0 {
|
||||||
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
|
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
|
||||||
@@ -404,11 +396,10 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
|
|||||||
bodyAddin,
|
bodyAddin,
|
||||||
verifyAddin,
|
verifyAddin,
|
||||||
)
|
)
|
||||||
|
//if strings.Contains(functionname, "get_returns_the_vuln") {
|
||||||
if strings.Contains(functionname, "get_returns_the_vuln") {
|
// log.Println(data)
|
||||||
log.Println(data)
|
// log.Printf("Queries: %s", queryString)
|
||||||
log.Printf("Queries: %s", queryString)
|
//}
|
||||||
}
|
|
||||||
|
|
||||||
//log.Printf(data)
|
//log.Printf(data)
|
||||||
return functionname, data
|
return functionname, data
|
||||||
@@ -562,7 +553,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
|
|||||||
})
|
})
|
||||||
} else if securitySchemes["BasicAuth"] != nil {
|
} else if securitySchemes["BasicAuth"] != nil {
|
||||||
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
||||||
Name: "username_auth",
|
Name: "username_basic",
|
||||||
Value: "",
|
Value: "",
|
||||||
Example: "username",
|
Example: "username",
|
||||||
Description: securitySchemes["BasicAuth"].Value.Description,
|
Description: securitySchemes["BasicAuth"].Value.Description,
|
||||||
@@ -574,7 +565,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
||||||
Name: "password_auth",
|
Name: "password_basic",
|
||||||
Value: "",
|
Value: "",
|
||||||
Example: "*****",
|
Example: "*****",
|
||||||
Description: securitySchemes["BasicAuth"].Value.Description,
|
Description: securitySchemes["BasicAuth"].Value.Description,
|
||||||
@@ -975,31 +966,16 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [
|
|||||||
optionalQueries := []string{}
|
optionalQueries := []string{}
|
||||||
parameters := []string{}
|
parameters := []string{}
|
||||||
optionalParameters := []WorkflowAppActionParameter{}
|
optionalParameters := []WorkflowAppActionParameter{}
|
||||||
if len(swagger.Servers) == 0 {
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
Name: "ssl_verify",
|
||||||
Name: "ssl_verify",
|
Description: "Check if you want to verify request",
|
||||||
Description: "Check if you want to verify request",
|
Multiline: false,
|
||||||
Multiline: false,
|
Required: false,
|
||||||
Required: false,
|
Example: "True",
|
||||||
Example: "True",
|
Schema: SchemaDefinition{
|
||||||
Schema: SchemaDefinition{
|
Type: "string",
|
||||||
Type: "string",
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
||||||
Name: "ssl_verify",
|
|
||||||
Description: "Check if you want to verify request",
|
|
||||||
Multiline: false,
|
|
||||||
Required: false,
|
|
||||||
Example: "True",
|
|
||||||
Schema: SchemaDefinition{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headersFound := []string{}
|
headersFound := []string{}
|
||||||
if len(path.Connect.Parameters) > 0 {
|
if len(path.Connect.Parameters) > 0 {
|
||||||
@@ -1124,31 +1100,16 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
|
|||||||
// FIXME - remove this when authentication is properly introduced
|
// FIXME - remove this when authentication is properly introduced
|
||||||
parameters := []string{}
|
parameters := []string{}
|
||||||
optionalParameters := []WorkflowAppActionParameter{}
|
optionalParameters := []WorkflowAppActionParameter{}
|
||||||
if len(swagger.Servers) == 0 {
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
Name: "ssl_verify",
|
||||||
Name: "ssl_verify",
|
Description: "Check if you want to verify the SSL certificate request",
|
||||||
Description: "Check if you want to verify the SSL certificate request",
|
Multiline: false,
|
||||||
Multiline: false,
|
Required: false,
|
||||||
Required: false,
|
Example: "False - default=True",
|
||||||
Example: "False - default=True",
|
Schema: SchemaDefinition{
|
||||||
Schema: SchemaDefinition{
|
Type: "string",
|
||||||
Type: "string",
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
||||||
Name: "ssl_verify",
|
|
||||||
Description: "Check if you want to verify request",
|
|
||||||
Multiline: false,
|
|
||||||
Required: false,
|
|
||||||
Example: "True",
|
|
||||||
Schema: SchemaDefinition{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headersFound := []string{}
|
headersFound := []string{}
|
||||||
if len(path.Get.Parameters) > 0 {
|
if len(path.Get.Parameters) > 0 {
|
||||||
@@ -1273,31 +1234,16 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
|
|||||||
optionalQueries := []string{}
|
optionalQueries := []string{}
|
||||||
parameters := []string{}
|
parameters := []string{}
|
||||||
optionalParameters := []WorkflowAppActionParameter{}
|
optionalParameters := []WorkflowAppActionParameter{}
|
||||||
if len(swagger.Servers) == 0 {
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
Name: "ssl_verify",
|
||||||
Name: "ssl_verify",
|
Description: "Check if you want to verify request",
|
||||||
Description: "Check if you want to verify request",
|
Multiline: false,
|
||||||
Multiline: false,
|
Required: false,
|
||||||
Required: false,
|
Example: "True",
|
||||||
Example: "True",
|
Schema: SchemaDefinition{
|
||||||
Schema: SchemaDefinition{
|
Type: "string",
|
||||||
Type: "string",
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
||||||
Name: "ssl_verify",
|
|
||||||
Description: "Check if you want to verify request",
|
|
||||||
Multiline: false,
|
|
||||||
Required: false,
|
|
||||||
Example: "True",
|
|
||||||
Schema: SchemaDefinition{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headersFound := []string{}
|
headersFound := []string{}
|
||||||
if len(path.Head.Parameters) > 0 {
|
if len(path.Head.Parameters) > 0 {
|
||||||
@@ -1422,31 +1368,16 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []
|
|||||||
optionalQueries := []string{}
|
optionalQueries := []string{}
|
||||||
parameters := []string{}
|
parameters := []string{}
|
||||||
optionalParameters := []WorkflowAppActionParameter{}
|
optionalParameters := []WorkflowAppActionParameter{}
|
||||||
if len(swagger.Servers) == 0 {
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
Name: "ssl_verify",
|
||||||
Name: "ssl_verify",
|
Description: "Check if you want to verify request",
|
||||||
Description: "Check if you want to verify request",
|
Multiline: false,
|
||||||
Multiline: false,
|
Required: false,
|
||||||
Required: false,
|
Example: "True",
|
||||||
Example: "True",
|
Schema: SchemaDefinition{
|
||||||
Schema: SchemaDefinition{
|
Type: "string",
|
||||||
Type: "string",
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
||||||
Name: "ssl_verify",
|
|
||||||
Description: "Check if you want to verify request",
|
|
||||||
Multiline: false,
|
|
||||||
Required: false,
|
|
||||||
Example: "True",
|
|
||||||
Schema: SchemaDefinition{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headersFound := []string{}
|
headersFound := []string{}
|
||||||
if len(path.Delete.Parameters) > 0 {
|
if len(path.Delete.Parameters) > 0 {
|
||||||
@@ -1570,31 +1501,16 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
|
|||||||
optionalQueries := []string{}
|
optionalQueries := []string{}
|
||||||
parameters := []string{}
|
parameters := []string{}
|
||||||
optionalParameters := []WorkflowAppActionParameter{}
|
optionalParameters := []WorkflowAppActionParameter{}
|
||||||
if len(swagger.Servers) == 0 {
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
Name: "ssl_verify",
|
||||||
Name: "ssl_verify",
|
Description: "Check if you want to verify request",
|
||||||
Description: "Check if you want to verify request",
|
Multiline: false,
|
||||||
Multiline: false,
|
Required: false,
|
||||||
Required: false,
|
Example: "True",
|
||||||
Example: "True",
|
Schema: SchemaDefinition{
|
||||||
Schema: SchemaDefinition{
|
Type: "string",
|
||||||
Type: "string",
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
||||||
Name: "ssl_verify",
|
|
||||||
Description: "Check if you want to verify request",
|
|
||||||
Multiline: false,
|
|
||||||
Required: false,
|
|
||||||
Example: "True",
|
|
||||||
Schema: SchemaDefinition{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headersFound := []string{}
|
headersFound := []string{}
|
||||||
if len(path.Post.Parameters) > 0 {
|
if len(path.Post.Parameters) > 0 {
|
||||||
@@ -1718,31 +1634,16 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W
|
|||||||
optionalQueries := []string{}
|
optionalQueries := []string{}
|
||||||
parameters := []string{}
|
parameters := []string{}
|
||||||
optionalParameters := []WorkflowAppActionParameter{}
|
optionalParameters := []WorkflowAppActionParameter{}
|
||||||
if len(swagger.Servers) == 0 {
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
Name: "ssl_verify",
|
||||||
Name: "ssl_verify",
|
Description: "Check if you want to verify request",
|
||||||
Description: "Check if you want to verify request",
|
Multiline: false,
|
||||||
Multiline: false,
|
Required: false,
|
||||||
Required: false,
|
Example: "True",
|
||||||
Example: "True",
|
Schema: SchemaDefinition{
|
||||||
Schema: SchemaDefinition{
|
Type: "string",
|
||||||
Type: "string",
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
||||||
Name: "ssl_verify",
|
|
||||||
Description: "Check if you want to verify request",
|
|
||||||
Multiline: false,
|
|
||||||
Required: false,
|
|
||||||
Example: "True",
|
|
||||||
Schema: SchemaDefinition{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headersFound := []string{}
|
headersFound := []string{}
|
||||||
if len(path.Patch.Parameters) > 0 {
|
if len(path.Patch.Parameters) > 0 {
|
||||||
@@ -1866,31 +1767,16 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
|
|||||||
optionalQueries := []string{}
|
optionalQueries := []string{}
|
||||||
parameters := []string{}
|
parameters := []string{}
|
||||||
optionalParameters := []WorkflowAppActionParameter{}
|
optionalParameters := []WorkflowAppActionParameter{}
|
||||||
if len(swagger.Servers) == 0 {
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
Name: "ssl_verify",
|
||||||
Name: "ssl_verify",
|
Description: "Check if you want to verify request",
|
||||||
Description: "Check if you want to verify request",
|
Multiline: false,
|
||||||
Multiline: false,
|
Required: false,
|
||||||
Required: false,
|
Example: "True",
|
||||||
Example: "True",
|
Schema: SchemaDefinition{
|
||||||
Schema: SchemaDefinition{
|
Type: "string",
|
||||||
Type: "string",
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if swagger.Servers[0].URL == "" {
|
|
||||||
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
||||||
Name: "ssl_verify",
|
|
||||||
Description: "Check if you want to verify request",
|
|
||||||
Multiline: false,
|
|
||||||
Required: false,
|
|
||||||
Example: "True",
|
|
||||||
Schema: SchemaDefinition{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headersFound := []string{}
|
headersFound := []string{}
|
||||||
if len(path.Put.Parameters) > 0 {
|
if len(path.Put.Parameters) > 0 {
|
||||||
|
|||||||
+435
-46
@@ -150,7 +150,7 @@ type UserAuth struct {
|
|||||||
|
|
||||||
type UserAuthField struct {
|
type UserAuthField struct {
|
||||||
Key string `json:"key" datastore:"key"`
|
Key string `json:"key" datastore:"key"`
|
||||||
Value string `json:"value" datastore:"value"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not environment, but execution environment
|
// Not environment, but execution environment
|
||||||
@@ -181,6 +181,7 @@ type User struct {
|
|||||||
Id string `datastore:"id" json:"id"`
|
Id string `datastore:"id" json:"id"`
|
||||||
Orgs []string `datastore:"orgs" json:"orgs"`
|
Orgs []string `datastore:"orgs" json:"orgs"`
|
||||||
CreationTime int64 `datastore:"creation_time" json:"creation_time"`
|
CreationTime int64 `datastore:"creation_time" json:"creation_time"`
|
||||||
|
ActiveOrg Org `json:"active_org" datastore:"active_org"`
|
||||||
Active bool `datastore:"active" json:"active"`
|
Active bool `datastore:"active" json:"active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +210,7 @@ type Contact struct {
|
|||||||
type Translator struct {
|
type Translator struct {
|
||||||
Src struct {
|
Src struct {
|
||||||
Name string `json:"name" datastore:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
Value string `json:"value" datastore:"value"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
Description string `json:"description" datastore:"description,noindex"`
|
Description string `json:"description" datastore:"description,noindex"`
|
||||||
Required string `json:"required" datastore:"required"`
|
Required string `json:"required" datastore:"required"`
|
||||||
Type string `json:"type" datastore:"type"`
|
Type string `json:"type" datastore:"type"`
|
||||||
@@ -219,7 +220,7 @@ type Translator struct {
|
|||||||
} `json:"src" datastore:"src"`
|
} `json:"src" datastore:"src"`
|
||||||
Dst struct {
|
Dst struct {
|
||||||
Name string `json:"name" datastore:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
Value string `json:"value" datastore:"value"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
Type string `json:"type" datastore:"type"`
|
Type string `json:"type" datastore:"type"`
|
||||||
Description string `json:"description" datastore:"description,noindex"`
|
Description string `json:"description" datastore:"description,noindex"`
|
||||||
Required string `json:"required" datastore:"required"`
|
Required string `json:"required" datastore:"required"`
|
||||||
@@ -231,7 +232,7 @@ type Translator struct {
|
|||||||
|
|
||||||
type Appconfig struct {
|
type Appconfig struct {
|
||||||
Key string `json:"key" datastore:"key"`
|
Key string `json:"key" datastore:"key"`
|
||||||
Value string `json:"value" datastore:"value"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScheduleApp struct {
|
type ScheduleApp struct {
|
||||||
@@ -1013,13 +1014,13 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) {
|
|||||||
user, err := handleApiAuthentication(resp, request)
|
user, err := handleApiAuthentication(resp, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`))
|
resp.Write([]byte(`{"success": false, "reason": "Can't handle set env auth"}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Role != "admin" {
|
if user.Role != "admin" {
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`))
|
resp.Write([]byte(`{"success": false, "reason": "Can't set environment without being admin"}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1097,7 +1098,7 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(`{"success": true}`))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNewUser(username, password, role, apikey string) error {
|
func createNewUser(username, password, role, apikey string, org Org) error {
|
||||||
// Returns false if there is an issue
|
// Returns false if there is an issue
|
||||||
// Use this for register
|
// Use this for register
|
||||||
err := checkPasswordStrength(password)
|
err := checkPasswordStrength(password)
|
||||||
@@ -1137,7 +1138,7 @@ func createNewUser(username, password, role, apikey string) error {
|
|||||||
newUser.Verified = false
|
newUser.Verified = false
|
||||||
newUser.CreationTime = time.Now().Unix()
|
newUser.CreationTime = time.Now().Unix()
|
||||||
newUser.Active = true
|
newUser.Active = true
|
||||||
newUser.Orgs = []string{"default"}
|
newUser.Orgs = []string{org.Id}
|
||||||
|
|
||||||
// FIXME - Remove this later
|
// FIXME - Remove this later
|
||||||
if role == "admin" {
|
if role == "admin" {
|
||||||
@@ -1148,6 +1149,8 @@ func createNewUser(username, password, role, apikey string) error {
|
|||||||
newUser.Roles = []string{"user"}
|
newUser.Roles = []string{"user"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newUser.ActiveOrg = org
|
||||||
|
|
||||||
if len(apikey) > 0 {
|
if len(apikey) > 0 {
|
||||||
newUser.ApiKey = apikey
|
newUser.ApiKey = apikey
|
||||||
}
|
}
|
||||||
@@ -1182,25 +1185,25 @@ func createNewUser(username, password, role, apikey string) error {
|
|||||||
log.Printf("Error adding User %s: %s", username, err)
|
log.Printf("Error adding User %s: %s", username, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String())
|
// url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String())
|
||||||
const verifyMessage = `
|
// const verifyMessage = `
|
||||||
Registration URL :)
|
//Registration URL :)
|
||||||
|
//
|
||||||
%s
|
//%s
|
||||||
`
|
// `
|
||||||
addr := newUser.Username
|
// addr := newUser.Username
|
||||||
|
//
|
||||||
msg := &mail.Message{
|
// msg := &mail.Message{
|
||||||
Sender: "Shuffle <frikky@shuffler.io>",
|
// Sender: "Shuffle <frikky@shuffler.io>",
|
||||||
To: []string{addr},
|
// To: []string{addr},
|
||||||
Subject: "Verify your username - Shuffle",
|
// Subject: "Verify your username - Shuffle",
|
||||||
Body: fmt.Sprintf(verifyMessage, url),
|
// Body: fmt.Sprintf(verifyMessage, url),
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
log.Println(msg.Body)
|
// log.Println(msg.Body)
|
||||||
if err := mail.Send(ctx, msg); err != nil {
|
// if err := mail.Send(ctx, msg); err != nil {
|
||||||
log.Printf("Couldn't send email: %v", err)
|
// log.Printf("Couldn't send email: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = increaseStatisticsField(ctx, "successful_register", username, 1)
|
err = increaseStatisticsField(ctx, "successful_register", username, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1249,7 +1252,8 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
if count == 0 {
|
if count == 0 {
|
||||||
role = "admin"
|
role = "admin"
|
||||||
}
|
}
|
||||||
err = createNewUser(data.Username, data.Password, role, "")
|
|
||||||
|
err = createNewUser(data.Username, data.Password, role, "", user.ActiveOrg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed registering user: %s", err)
|
log.Printf("Failed registering user: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -1623,6 +1627,10 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
// This is a long check to see if an inactive admin can access the site
|
// This is a long check to see if an inactive admin can access the site
|
||||||
parsedAdmin := "false"
|
parsedAdmin := "false"
|
||||||
|
if userInfo.Role == "admin" {
|
||||||
|
parsedAdmin = "true"
|
||||||
|
}
|
||||||
|
|
||||||
if !userInfo.Active {
|
if !userInfo.Active {
|
||||||
if userInfo.Role == "admin" {
|
if userInfo.Role == "admin" {
|
||||||
parsedAdmin = "true"
|
parsedAdmin = "true"
|
||||||
@@ -1692,16 +1700,66 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
Expires: expiration,
|
Expires: expiration,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Updating user info if there's something wrong
|
||||||
|
if (len(userInfo.ActiveOrg.Name) == 0 || len(userInfo.ActiveOrg.Id) == 0) && len(userInfo.Orgs) > 0 {
|
||||||
|
_, err := getOrg(ctx, userInfo.Orgs[0])
|
||||||
|
if err != nil {
|
||||||
|
var orgs []Org
|
||||||
|
q := datastore.NewQuery("Organizations")
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &orgs)
|
||||||
|
if err == nil {
|
||||||
|
newStringOrgs := []string{}
|
||||||
|
newOrgs := []Org{}
|
||||||
|
for _, org := range orgs {
|
||||||
|
if strings.ToLower(org.Name) == strings.ToLower(userInfo.Orgs[0]) {
|
||||||
|
newOrgs = append(newOrgs, org)
|
||||||
|
newStringOrgs = append(newStringOrgs, org.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newOrgs) > 0 {
|
||||||
|
userInfo.ActiveOrg = newOrgs[0]
|
||||||
|
userInfo.Orgs = newStringOrgs
|
||||||
|
|
||||||
|
err = setUser(ctx, &userInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error patching User for activeOrg: %s", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Updated the users' org")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed getting orgs for user. Major issue.: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 1. Check if the org exists by ID
|
||||||
|
// 2. if it does, overwrite user
|
||||||
|
userInfo.ActiveOrg = Org{
|
||||||
|
Id: userInfo.Orgs[0],
|
||||||
|
}
|
||||||
|
err = setUser(ctx, &userInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error patching User for activeOrg: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOrg, err := json.Marshal(userInfo.ActiveOrg)
|
||||||
|
if err != nil {
|
||||||
|
currentOrg = []byte("{}")
|
||||||
|
}
|
||||||
|
|
||||||
returnData := fmt.Sprintf(`
|
returnData := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"admin": %s,
|
"admin": %s,
|
||||||
"tutorials": [],
|
"tutorials": [],
|
||||||
"id": "%s",
|
"id": "%s",
|
||||||
"orgs": [{"name": "Shuffle", "id": "123", "role": "admin"}],
|
"orgs": [%s],
|
||||||
"selected_org": {"name": "Shuffle", "id": "123", "role": "admin"},
|
"active_org": %s,
|
||||||
"cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]
|
"cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]
|
||||||
}`, parsedAdmin, userInfo.Id, userInfo.Session, expiration.Unix())
|
}`, parsedAdmin, userInfo.Id, currentOrg, currentOrg, userInfo.Session, expiration.Unix())
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(returnData))
|
resp.Write([]byte(returnData))
|
||||||
@@ -1973,7 +2031,7 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(users) != 1 {
|
if len(users) != 1 {
|
||||||
log.Printf(`Found multiple users with the same username: %s: %d`, t.Username, len(users))
|
log.Printf(`Found multiple or no users with the same username: %s: %d`, t.Username, len(users))
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s (%d)"}`, len(users), t.Username)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s (%d)"}`, len(users), t.Username)))
|
||||||
return
|
return
|
||||||
@@ -2166,6 +2224,61 @@ func handleGetEnvironments(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write(newjson)
|
resp.Write(newjson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleGetOrgs(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
cors := handleCors(resp, request)
|
||||||
|
if cors {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := handleApiAuthentication(resp, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Api authentication failed in set new workflowhandler: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Role != "admin" {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Not admin"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
var orgs []Org
|
||||||
|
q := datastore.NewQuery("Organizations")
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &orgs)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Can't get users"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//newUsers := []User{}
|
||||||
|
//for _, item := range users {
|
||||||
|
// if len(item.Username) == 0 {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// item.Password = ""
|
||||||
|
// item.Session = ""
|
||||||
|
// item.VerificationToken = ""
|
||||||
|
|
||||||
|
// newUsers = append(newUsers, item)
|
||||||
|
//}
|
||||||
|
|
||||||
|
newjson, err := json.Marshal(orgs)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed unmarshal: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write(newjson)
|
||||||
|
}
|
||||||
|
|
||||||
func handleGetUsers(resp http.ResponseWriter, request *http.Request) {
|
func handleGetUsers(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -2281,7 +2394,7 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(users) != 1 {
|
if len(users) != 1 {
|
||||||
log.Printf(`Found multiple users with the same username: %s: %d`, data.Username, len(users))
|
log.Printf(`Found multiple or no users with the same username: %s: %d`, data.Username, len(users))
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s"}`, len(users), data.Username)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s"}`, len(users), data.Username)))
|
||||||
return
|
return
|
||||||
@@ -2383,6 +2496,28 @@ func getSession(ctx context.Context, thissession string) (*session, error) {
|
|||||||
return curUser, nil
|
return curUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListBooks returns a list of books, ordered by title.
|
||||||
|
func getOrg(ctx context.Context, id string) (*Org, error) {
|
||||||
|
key := datastore.NameKey("Organizations", id, nil)
|
||||||
|
curOrg := &Org{}
|
||||||
|
if err := dbclient.Get(ctx, key, curOrg); err != nil {
|
||||||
|
return &Org{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return curOrg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setOrg(ctx context.Context, data Org, id string) error {
|
||||||
|
// clear session_token and API_token for user
|
||||||
|
k := datastore.NameKey("Organizations", id, nil)
|
||||||
|
if _, err := dbclient.Put(ctx, k, &data); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListBooks returns a list of books, ordered by title.
|
// ListBooks returns a list of books, ordered by title.
|
||||||
func getUser(ctx context.Context, id string) (*User, error) {
|
func getUser(ctx context.Context, id string) (*User, error) {
|
||||||
key := datastore.NameKey("Users", id, nil)
|
key := datastore.NameKey("Users", id, nil)
|
||||||
@@ -2946,6 +3081,7 @@ func getSpecificWebhook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
// FIXME: Schedule = trigger?
|
||||||
schedule, err := getSchedule(ctx, workflowId)
|
schedule, err := getSchedule(ctx, workflowId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed setting schedule: %s", err)
|
log.Printf("Failed setting schedule: %s", err)
|
||||||
@@ -4781,8 +4917,8 @@ func setTriggerAuth(ctx context.Context, trigger TriggerAuth) error {
|
|||||||
func getOutlookClient(ctx context.Context, code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) {
|
func getOutlookClient(ctx context.Context, code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) {
|
||||||
|
|
||||||
conf := &oauth2.Config{
|
conf := &oauth2.Config{
|
||||||
ClientID: "70e37005-c954-4290-b573-d4b94e484336",
|
ClientID: "",
|
||||||
ClientSecret: ".eNw/A[kQFB5zL.agvRputdEJENeJ392",
|
ClientSecret: "",
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
"Mail.Read",
|
"Mail.Read",
|
||||||
"User.Read",
|
"User.Read",
|
||||||
@@ -6233,6 +6369,91 @@ func runInit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
setUsers := false
|
||||||
|
orgQuery := datastore.NewQuery("Organizations")
|
||||||
|
var activeOrgs []Org
|
||||||
|
_, err = dbclient.GetAll(ctx, orgQuery, &activeOrgs)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting organizations!")
|
||||||
|
} else {
|
||||||
|
// Add all users to it
|
||||||
|
if len(activeOrgs) == 1 {
|
||||||
|
setUsers = true
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Organizations exist!")
|
||||||
|
if len(activeOrgs) == 0 {
|
||||||
|
log.Printf(`No orgs. Setting org "default"`)
|
||||||
|
orgSetupName := "default"
|
||||||
|
orgId := uuid.NewV4().String()
|
||||||
|
newOrg := Org{
|
||||||
|
Name: orgSetupName,
|
||||||
|
Id: orgId,
|
||||||
|
Org: orgSetupName,
|
||||||
|
Users: []User{},
|
||||||
|
Roles: []string{"admin", "user"},
|
||||||
|
CloudSync: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setOrg(ctx, newOrg, orgId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed setting organization: %s", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully created the default org!")
|
||||||
|
setUsers = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("There are %d org(s).", len(activeOrgs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the users to the base organization since only one exists (default)
|
||||||
|
if setUsers && len(activeOrgs) > 0 {
|
||||||
|
activeOrg := activeOrgs[0]
|
||||||
|
|
||||||
|
q := datastore.NewQuery("Users")
|
||||||
|
var users []User
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &users)
|
||||||
|
if err == nil {
|
||||||
|
setOrgBool := false
|
||||||
|
for _, user := range users {
|
||||||
|
newUser := User{
|
||||||
|
Username: user.Username,
|
||||||
|
Id: user.Id,
|
||||||
|
ActiveOrg: Org{
|
||||||
|
Id: activeOrg.Id,
|
||||||
|
},
|
||||||
|
Orgs: []string{activeOrg.Id},
|
||||||
|
Role: user.Role,
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, orgUser := range activeOrg.Users {
|
||||||
|
if user.Id == orgUser.Id {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found && len(user.Username) > 0 {
|
||||||
|
log.Printf("Adding user %s to org %s", user.Username, activeOrg.Name)
|
||||||
|
activeOrg.Users = append(activeOrg.Users, newUser)
|
||||||
|
setOrgBool = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if setOrgBool {
|
||||||
|
err = setOrg(ctx, activeOrg, activeOrg.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed setting org %s: %s!", activeOrg.Name, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("UPDATED org %s!", activeOrg.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Should add %d users to organization default", len(users))
|
||||||
|
}
|
||||||
|
|
||||||
// Fix active users etc
|
// Fix active users etc
|
||||||
q := datastore.NewQuery("Users").Filter("active =", true)
|
q := datastore.NewQuery("Users").Filter("active =", true)
|
||||||
var activeusers []User
|
var activeusers []User
|
||||||
@@ -6243,6 +6464,7 @@ func runInit(ctx context.Context) {
|
|||||||
q := datastore.NewQuery("Users")
|
q := datastore.NewQuery("Users")
|
||||||
var users []User
|
var users []User
|
||||||
_, err := dbclient.GetAll(ctx, q, &users)
|
_, err := dbclient.GetAll(ctx, q, &users)
|
||||||
|
|
||||||
if len(activeusers) == 0 && len(users) > 0 {
|
if len(activeusers) == 0 && len(users) > 0 {
|
||||||
log.Printf("No active users found - setting ALL to active")
|
log.Printf("No active users found - setting ALL to active")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -6258,7 +6480,12 @@ func runInit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(user.Orgs) == 0 {
|
if len(user.Orgs) == 0 {
|
||||||
user.Orgs = []string{"default"}
|
defaultName := "default"
|
||||||
|
user.Orgs = []string{defaultName}
|
||||||
|
user.ActiveOrg = Org{
|
||||||
|
Name: defaultName,
|
||||||
|
Role: "user",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setUser(ctx, &user)
|
err = setUser(ctx, &user)
|
||||||
@@ -6281,7 +6508,11 @@ func runInit(ctx context.Context) {
|
|||||||
log.Printf("SHUFFLE_DEFAULT_USERNAME and SHUFFLE_DEFAULT_PASSWORD not defined as environments. Running without default user.")
|
log.Printf("SHUFFLE_DEFAULT_USERNAME and SHUFFLE_DEFAULT_PASSWORD not defined as environments. Running without default user.")
|
||||||
} else {
|
} else {
|
||||||
apikey := os.Getenv("SHUFFLE_DEFAULT_APIKEY")
|
apikey := os.Getenv("SHUFFLE_DEFAULT_APIKEY")
|
||||||
err = createNewUser(username, password, "admin", apikey)
|
|
||||||
|
tmpOrg := Org{
|
||||||
|
Name: "default",
|
||||||
|
}
|
||||||
|
err = createNewUser(username, password, "admin", apikey, tmpOrg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to create default user %s: %s", username, err)
|
log.Printf("Failed to create default user %s: %s", username, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -6449,6 +6680,164 @@ func runInit(ctx context.Context) {
|
|||||||
log.Printf("Finished INIT")
|
log.Printf("Finished INIT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// INFO: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit
|
||||||
|
func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
cors := handleCors(resp, request)
|
||||||
|
if cors {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := handleApiAuthentication(resp, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Api authentication failed in verify swagger: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Role != "admin" {
|
||||||
|
log.Printf("Not admin.")
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Not admin"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnData struct {
|
||||||
|
Apikey string `datastore:"apikey"`
|
||||||
|
Organization Org `datastore:"organization"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmpData ReturnData
|
||||||
|
err = json.Unmarshal(body, &tmpData)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed unmarshalling test: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
org, err := getOrg(ctx, tmpData.Organization.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Organization doesn't exist: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Check if user is admin of this org
|
||||||
|
userFound := false
|
||||||
|
admin := false
|
||||||
|
for _, inneruser := range org.Users {
|
||||||
|
if inneruser.Id == user.Id {
|
||||||
|
userFound = true
|
||||||
|
log.Printf("Role: %s", inneruser.Role)
|
||||||
|
if inneruser.Role == "admin" {
|
||||||
|
admin = true
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userFound {
|
||||||
|
log.Printf("User %s doesn't exist in organization %s", user.Id, org.Id)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Enable admin check in org for sync setup and conf.
|
||||||
|
_ = admin
|
||||||
|
//if !admin {
|
||||||
|
// log.Printf("User %s isn't admin hence can't set up sync for org %s", user.Id, org.Id)
|
||||||
|
// resp.WriteHeader(401)
|
||||||
|
// resp.Write([]byte(`{"success": false}`))
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
log.Printf("Apidata: %s", tmpData.Apikey)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
syncPath := "http://192.168.3.6:5002/api/v1/cloud/sync"
|
||||||
|
|
||||||
|
type requestStruct struct {
|
||||||
|
ApiKey string `json:"api_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
requestData := requestStruct{
|
||||||
|
ApiKey: tmpData.Apikey,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(requestData)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed marshaling api key data: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync."`, err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"POST",
|
||||||
|
syncPath,
|
||||||
|
bytes.NewBuffer(b),
|
||||||
|
)
|
||||||
|
|
||||||
|
newresp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(400)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync: %s"`, err)))
|
||||||
|
//setBadMemcache(ctx, docPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if newresp.StatusCode != 200 {
|
||||||
|
resp.WriteHeader(400)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Response code %d during sync. Expecting 200."`, newresp.StatusCode)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(newresp.Body)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't parse sync data"`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseStruct struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
log.Printf("Respbody: %s", string(respBody))
|
||||||
|
|
||||||
|
responseData := responseStruct{}
|
||||||
|
err = json.Unmarshal(respBody, &responseData)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed handling cloud data"`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseData.Success {
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
if len(responseData.Reason) > 0 {
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, responseData.Reason)))
|
||||||
|
} else {
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp.WriteHeader(400)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, responseData.Reason)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func initHandlers() {
|
func initHandlers() {
|
||||||
var err error
|
var err error
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -6467,11 +6856,6 @@ func initHandlers() {
|
|||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/api/v1/_ah/health", healthCheckHandler)
|
r.HandleFunc("/api/v1/_ah/health", healthCheckHandler)
|
||||||
|
|
||||||
// Sends an email if the right things are specified
|
|
||||||
r.HandleFunc("/functions/sendmail", handleSendalert).Methods("POST", "OPTIONS")
|
|
||||||
r.HandleFunc("/functions/outlook/register", handleNewOutlookRegister).Methods("GET", "OPTIONS")
|
|
||||||
r.HandleFunc("/functions/outlook/getFolders", handleGetOutlookFolders).Methods("GET", "OPTIONS")
|
|
||||||
|
|
||||||
// Make user related locations
|
// Make user related locations
|
||||||
r.HandleFunc("/api/v1/users/generateapikey", handleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/generateapikey", handleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/login", handleLogin).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/login", handleLogin).Methods("POST", "OPTIONS")
|
||||||
@@ -6506,9 +6890,14 @@ func initHandlers() {
|
|||||||
// Queuebuilder and Workflow streams. First is to update a stream, second to get a stream
|
// Queuebuilder and Workflow streams. First is to update a stream, second to get a stream
|
||||||
// Changed from workflows/streams to streams, as appengine was messing up
|
// Changed from workflows/streams to streams, as appengine was messing up
|
||||||
// This does not increase the API counter
|
// This does not increase the API counter
|
||||||
|
// Used by frontend
|
||||||
r.HandleFunc("/api/v1/streams", handleWorkflowQueue).Methods("POST")
|
r.HandleFunc("/api/v1/streams", handleWorkflowQueue).Methods("POST")
|
||||||
r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
|
// Used by orborus
|
||||||
|
r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET")
|
||||||
|
r.HandleFunc("/api/v1/workflows/queue/confirm", handleGetWorkflowqueueConfirm).Methods("POST")
|
||||||
|
|
||||||
// App specific
|
// App specific
|
||||||
r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS")
|
||||||
@@ -6535,8 +6924,6 @@ func initHandlers() {
|
|||||||
/* Everything below here increases the counters*/
|
/* Everything below here increases the counters*/
|
||||||
r.HandleFunc("/api/v1/workflows", getWorkflows).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/workflows", getWorkflows).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/workflows", setNewWorkflow).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/workflows", setNewWorkflow).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET")
|
|
||||||
r.HandleFunc("/api/v1/workflows/queue/confirm", handleGetWorkflowqueueConfirm).Methods("POST")
|
|
||||||
r.HandleFunc("/api/v1/workflows/schedules", handleGetSchedules).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/workflows/schedules", handleGetSchedules).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/workflows/download_remote", loadSpecificWorkflows).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/workflows/download_remote", loadSpecificWorkflows).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS")
|
r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS")
|
||||||
@@ -6557,7 +6944,7 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/hooks/{key}/delete", handleDeleteHook).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/hooks/{key}/delete", handleDeleteHook).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
// Trigger hmm
|
// Trigger hmm
|
||||||
r.HandleFunc("/api/v1/triggers/{key}", handleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
//r.HandleFunc("/api/v1/triggers/{key}", handleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/stats/{key}", handleGetSpecificStats).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/stats/{key}", handleGetSpecificStats).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
@@ -6568,7 +6955,9 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/validate_openapi", validateSwagger).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/validate_openapi", validateSwagger).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/execution_cleanup", cleanupExecutions).Methods("GET", "OPTIONS")
|
// NEW for 0.8.0
|
||||||
|
r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
http.Handle("/", r)
|
http.Handle("/", r)
|
||||||
}
|
}
|
||||||
|
|||||||
+300
-83
@@ -67,11 +67,28 @@ type ExecutionRequest struct {
|
|||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SyncFeatures struct {
|
||||||
|
Apps SyncData `json:"apps" datastore:"apps"`
|
||||||
|
Workflows SyncData `json:"apps" datastore:"apps"`
|
||||||
|
Schedules SyncData `json:"apps" datastore:"apps"`
|
||||||
|
Autocomplete SyncData `json:"apps" datastore:"apps"`
|
||||||
|
Authentication SyncData `json:"apps" datastore:"apps"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncData struct {
|
||||||
|
Active bool `json:"active" datastore:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role is just used for feedback for a user
|
||||||
type Org struct {
|
type Org struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
Org string `json:"org"`
|
Id string `json:"id" datastore:"id"`
|
||||||
Users []User `json:"users"`
|
Org string `json:"org" datastore:"org"`
|
||||||
Id string `json:"id"`
|
Users []User `json:"users" datastore:"users"`
|
||||||
|
Role string `json:"role" datastore:"role"`
|
||||||
|
Roles []string `json:"roles" datastore:"roles"`
|
||||||
|
CloudSync bool `json:"cloud_sync" datastore:"CloudSync"`
|
||||||
|
SyncFeatures SyncFeatures `json:"sync_features" datastore:"sync_features"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppAuthenticationStorage struct {
|
type AppAuthenticationStorage struct {
|
||||||
@@ -127,7 +144,7 @@ type WorkflowAppActionParameter struct {
|
|||||||
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
|
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
|
||||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||||
Example string `json:"example" datastore:"example" yaml:"example"`
|
Example string `json:"example" datastore:"example" yaml:"example"`
|
||||||
Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
|
Value string `json:"value" datastore:"value,noindex" yaml:"value,omitempty"`
|
||||||
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
|
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
|
||||||
Options []string `json:"options" datastore:"options" yaml:"options"`
|
Options []string `json:"options" datastore:"options" yaml:"options"`
|
||||||
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
|
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
|
||||||
@@ -161,7 +178,7 @@ type WorkflowAppAction struct {
|
|||||||
Description string `json:"description" datastore:"description,noindex"`
|
Description string `json:"description" datastore:"description,noindex"`
|
||||||
ID string `json:"id" datastore:"id"`
|
ID string `json:"id" datastore:"id"`
|
||||||
Name string `json:"name" datastore:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
Value string `json:"value" datastore:"value"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
} `json:"execution_variable" datastore:"execution_variables"`
|
} `json:"execution_variable" datastore:"execution_variables"`
|
||||||
Returns struct {
|
Returns struct {
|
||||||
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
|
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
|
||||||
@@ -306,7 +323,7 @@ type Workflow struct {
|
|||||||
Description string `json:"description" datastore:"description,noindex"`
|
Description string `json:"description" datastore:"description,noindex"`
|
||||||
ID string `json:"id" datastore:"id"`
|
ID string `json:"id" datastore:"id"`
|
||||||
Name string `json:"name" datastore:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
Value string `json:"value" datastore:"value"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
} `json:"workflow_variables" datastore:"workflow_variables"`
|
} `json:"workflow_variables" datastore:"workflow_variables"`
|
||||||
ExecutionVariables []struct {
|
ExecutionVariables []struct {
|
||||||
Description string `json:"description" datastore:"description,noindex"`
|
Description string `json:"description" datastore:"description,noindex"`
|
||||||
@@ -336,7 +353,7 @@ type AuthenticationParams struct {
|
|||||||
ID string `json:"id" datastore:"id" yaml:"id"`
|
ID string `json:"id" datastore:"id" yaml:"id"`
|
||||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||||
Example string `json:"example" datastore:"example" yaml:"example"`
|
Example string `json:"example" datastore:"example" yaml:"example"`
|
||||||
Value string `json:"value,omitempty" datastore:"value" yaml:"value"`
|
Value string `json:"value,omitempty" datastore:"value,noindex" yaml:"value"`
|
||||||
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
|
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
|
||||||
Required bool `json:"required" datastore:"required" yaml:"required"`
|
Required bool `json:"required" datastore:"required" yaml:"required"`
|
||||||
In string `json:"in" datastore:"in" yaml:"in"`
|
In string `json:"in" datastore:"in" yaml:"in"`
|
||||||
@@ -346,13 +363,23 @@ type AuthenticationParams struct {
|
|||||||
|
|
||||||
type AuthenticationStore struct {
|
type AuthenticationStore struct {
|
||||||
Key string `json:"key" datastore:"key"`
|
Key string `json:"key" datastore:"key"`
|
||||||
Value string `json:"value" datastore:"value"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecutionRequestWrapper struct {
|
type ExecutionRequestWrapper struct {
|
||||||
Data []ExecutionRequest `json:"data"`
|
Data []ExecutionRequest `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppExecutionExample struct {
|
||||||
|
AppName string `json:"app_name" datastore:"app_name"`
|
||||||
|
AppVersion string `json:"app_version" datastore:"app_version"`
|
||||||
|
AppAction string `json:"app_action" datastore:"app_action"`
|
||||||
|
AppId string `json:"app_id" datastore:"app_id"`
|
||||||
|
ExampleId string `json:"example_id" datastore:"example_id"`
|
||||||
|
SuccessExamples []string `json:"success_examples" datastore:"success_examples,noindex"`
|
||||||
|
FailureExamples []string `json:"failure_examples" datastore:"failure_examples,noindex"`
|
||||||
|
}
|
||||||
|
|
||||||
// This might be... a bit off, but that's fine :)
|
// This might be... a bit off, but that's fine :)
|
||||||
// This might also be stupid, as we want timelines and such
|
// This might also be stupid, as we want timelines and such
|
||||||
// Anyway, these are super basic stupid stats.
|
// Anyway, these are super basic stupid stats.
|
||||||
@@ -1078,6 +1105,10 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to increase success execution stats: %s", err)
|
log.Printf("Failed to increase success execution stats: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles extra statistics stuff when it's done
|
||||||
|
// Does autocomplete magic with JSON
|
||||||
|
go handleExecutionStatistics(*workflowExecution)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1088,8 +1119,33 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
// log.Printf("Name: %s, Env: %s", action.Name, action.Environment)
|
// log.Printf("Name: %s, Env: %s", action.Name, action.Environment)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
tmpJson, err := json.Marshal(workflowExecution)
|
||||||
|
if err == nil {
|
||||||
|
if len(tmpJson) >= 1048487 {
|
||||||
|
log.Printf("[ERROR] Result length is too long! Need to reduce result size")
|
||||||
|
|
||||||
|
// Result string `json:"result" datastore:"result,noindex"`
|
||||||
|
// Arbitrary reduction size
|
||||||
|
maxSize := 500000
|
||||||
|
newResults := []ActionResult{}
|
||||||
|
for _, item := range workflowExecution.Results {
|
||||||
|
if len(item.Result) > maxSize {
|
||||||
|
item.Result = "[ERROR] Result too large to handle (https://github.com/frikky/shuffle/issues/171)"
|
||||||
|
}
|
||||||
|
|
||||||
|
newResults = append(newResults, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowExecution.Results = newResults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = setWorkflowExecution(ctx, *workflowExecution)
|
err = setWorkflowExecution(ctx, *workflowExecution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//workflowExecution.Result = "Error setting workflow: result too large"
|
||||||
|
//workflowExecution.Status = "FINISHED"
|
||||||
|
//workflowExecution.CompletedAt = int64(time.Now().Unix())
|
||||||
|
|
||||||
log.Printf("Error saving workflow execution actionresult setting: %s", err)
|
log.Printf("Error saving workflow execution actionresult setting: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err)))
|
||||||
@@ -1100,6 +1156,90 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func JSONCheck(str string) bool {
|
||||||
|
var jsonStr interface{}
|
||||||
|
return json.Unmarshal([]byte(str), &jsonStr) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleExecutionStatistics(execution WorkflowExecution) {
|
||||||
|
// FIXME: CLEAN UP THE JSON THAT'S SAVED.
|
||||||
|
// https://github.com/frikky/Shuffle/issues/172
|
||||||
|
appResults := []AppExecutionExample{}
|
||||||
|
for _, result := range execution.Results {
|
||||||
|
resultCheck := JSONCheck(result.Result)
|
||||||
|
if !resultCheck {
|
||||||
|
log.Printf("Result is NOT JSON!")
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
log.Printf("Result IS JSON!")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
appFound := false
|
||||||
|
executionIndex := 0
|
||||||
|
for index, appExample := range appResults {
|
||||||
|
if appExample.AppId == result.Action.ID {
|
||||||
|
appFound = true
|
||||||
|
executionIndex = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if appFound {
|
||||||
|
// Append to SuccessExamples or FailureExamples
|
||||||
|
if result.Status == "ABORTED" || result.Status == "FAILURE" {
|
||||||
|
appResults[executionIndex].FailureExamples = append(appResults[executionIndex].FailureExamples, result.Result)
|
||||||
|
} else if result.Status == "FINISHED" || result.Status == "SUCCESS" {
|
||||||
|
appResults[executionIndex].SuccessExamples = append(appResults[executionIndex].SuccessExamples, result.Result)
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] Can't handle status %s", result.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appResults = append(appResults, executionExample)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// CREATE SuccessExamples or FailureExamples
|
||||||
|
executionExample := AppExecutionExample{
|
||||||
|
AppName: result.Action.AppName,
|
||||||
|
AppVersion: result.Action.AppVersion,
|
||||||
|
AppAction: result.Action.Name,
|
||||||
|
AppId: result.Action.AppID,
|
||||||
|
ExampleId: fmt.Sprintf("%s_%s", execution.ExecutionId, result.Action.AppID),
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Status == "ABORTED" || result.Status == "FAILURE" {
|
||||||
|
executionExample.FailureExamples = append(executionExample.FailureExamples, result.Result)
|
||||||
|
} else if result.Status == "FINISHED" || result.Status == "SUCCESS" {
|
||||||
|
executionExample.SuccessExamples = append(executionExample.SuccessExamples, result.Result)
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] Can't handle status %s", result.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
appResults = append(appResults, executionExample)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleId string `json:"example_id"`
|
||||||
|
// func setExampleresult(ctx context.Context, result exampleResult) error {
|
||||||
|
// log.Printf("Execution length: %d", len(appResults))
|
||||||
|
if len(appResults) > 0 {
|
||||||
|
ctx := context.Background()
|
||||||
|
successful := 0
|
||||||
|
for _, exampleresult := range appResults {
|
||||||
|
err := setExampleresult(ctx, exampleresult)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed setting examplresult %s: %s", exampleresult.ExampleId, err)
|
||||||
|
} else {
|
||||||
|
successful += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Added %d exampleresults to backend", successful)
|
||||||
|
} else {
|
||||||
|
log.Printf("No examplresults necessary to be added for execution %s", execution.ExecutionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getWorkflows(resp http.ResponseWriter, request *http.Request) {
|
func getWorkflows(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -1589,7 +1729,8 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Have a good way of tracking errors. ID's or similar.
|
// FIXME: Have a good way of tracking errors. ID's or similar.
|
||||||
if !action.IsValid {
|
if !action.IsValid && len(action.Errors) > 0 {
|
||||||
|
log.Printf("Node %s is invalid and needs to be remade. Errors: %s", action.Label, strings.Join(action.Errors, "\n"))
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Node %s is invalid and needs to be remade."}`, action.Label)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Node %s is invalid and needs to be remade."}`, action.Label)))
|
||||||
return
|
return
|
||||||
@@ -1602,11 +1743,43 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
workflow.Actions = newActions
|
workflow.Actions = newActions
|
||||||
|
|
||||||
|
newTriggers := []Trigger{}
|
||||||
for _, trigger := range workflow.Triggers {
|
for _, trigger := range workflow.Triggers {
|
||||||
log.Printf("Trigger %s: %s", trigger.TriggerType, trigger.Status)
|
log.Printf("Trigger %s: %s", trigger.TriggerType, trigger.Status)
|
||||||
|
|
||||||
|
// Check if it's actually running
|
||||||
|
// FIXME: Do this for other triggers too
|
||||||
|
if trigger.TriggerType == "SCHEDULE" && trigger.Status != "uninitialized" {
|
||||||
|
schedule, err := getSchedule(ctx, trigger.ID)
|
||||||
|
if err != nil {
|
||||||
|
trigger.Status = "stopped"
|
||||||
|
} else if schedule.Id == "" {
|
||||||
|
trigger.Status = "stopped"
|
||||||
|
}
|
||||||
|
} else if trigger.TriggerType == "WEBHOOK" && trigger.Status != "uninitialized" {
|
||||||
|
hook, err := getHook(ctx, trigger.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed getting webhook")
|
||||||
|
trigger.Status = "stopped"
|
||||||
|
} else if hook.Id == "" {
|
||||||
|
trigger.Status = "stopped"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//log.Println("TRIGGERS")
|
//log.Println("TRIGGERS")
|
||||||
allNodes = append(allNodes, trigger.ID)
|
allNodes = append(allNodes, trigger.ID)
|
||||||
|
newTriggers = append(newTriggers, trigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
workflow.Triggers = newTriggers
|
||||||
|
|
||||||
|
for _, variable := range workflow.WorkflowVariables {
|
||||||
|
if len(variable.Value) == 0 {
|
||||||
|
log.Printf("Can't have an empty variable: %s", variable.Name)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Variable %s can't be empty"}`, variable.Name)))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(workflow.Actions) == 0 {
|
if len(workflow.Actions) == 0 {
|
||||||
@@ -1764,10 +1937,16 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !authFound {
|
if !authFound {
|
||||||
log.Printf("App auth %s doesn't exist", action.AuthenticationId)
|
log.Printf("App auth %s doesn't exist. Setting error", action.AuthenticationId)
|
||||||
resp.WriteHeader(401)
|
workflow.Errors = append(workflow.Errors, fmt.Sprintf("App authentication for %s doesn't exist!", action.AppName))
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App auth %s doesn't exist"}`, action.AuthenticationId)))
|
workflow.IsValid = false
|
||||||
return
|
|
||||||
|
action.Errors = append(action.Errors, "App authentication doesn't exist")
|
||||||
|
action.IsValid = false
|
||||||
|
action.AuthenticationId = ""
|
||||||
|
//resp.WriteHeader(401)
|
||||||
|
//resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App auth %s doesn't exist"}`, action.AuthenticationId)))
|
||||||
|
//return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1791,70 +1970,77 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
// Check to see if the whole app is valid
|
// Check to see if the whole app is valid
|
||||||
if curapp.Name != action.AppName {
|
if curapp.Name != action.AppName {
|
||||||
log.Printf("App %s doesn't exist.", action.AppName)
|
workflow.Errors = append(workflow.Errors, fmt.Sprintf("App %s doesn't exist", action.AppName))
|
||||||
resp.WriteHeader(401)
|
action.Errors = append(action.Errors, "This app doesn't exist.")
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName)))
|
action.IsValid = false
|
||||||
return
|
workflow.IsValid = false
|
||||||
}
|
|
||||||
|
|
||||||
// Check tosee if the appaction is valid
|
// Append with errors
|
||||||
curappaction := WorkflowAppAction{}
|
newActions = append(newActions, action)
|
||||||
for _, curAction := range curapp.Actions {
|
log.Printf("App %s doesn't exist. Adding as error.", action.AppName)
|
||||||
if action.Name == curAction.Name {
|
//resp.WriteHeader(401)
|
||||||
curappaction = curAction
|
//resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName)))
|
||||||
break
|
//return
|
||||||
}
|
} else {
|
||||||
}
|
// Check tosee if the appaction is valid
|
||||||
|
curappaction := WorkflowAppAction{}
|
||||||
// Check to see if the action is valid
|
for _, curAction := range curapp.Actions {
|
||||||
if curappaction.Name != action.Name {
|
if action.Name == curAction.Name {
|
||||||
log.Printf("Appaction %s doesn't exist.", action.Name)
|
curappaction = curAction
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - check all parameters to see if they're valid
|
|
||||||
// Includes checking required fields
|
|
||||||
|
|
||||||
newParams := []WorkflowAppActionParameter{}
|
|
||||||
for _, param := range curappaction.Parameters {
|
|
||||||
found := false
|
|
||||||
|
|
||||||
// Handles check for parameter exists + value not empty in used fields
|
|
||||||
for _, actionParam := range action.Parameters {
|
|
||||||
if actionParam.Name == param.Name {
|
|
||||||
found = true
|
|
||||||
|
|
||||||
if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true {
|
|
||||||
log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name)))
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if actionParam.Variant == "" {
|
|
||||||
actionParam.Variant = "STATIC_VALUE"
|
|
||||||
}
|
|
||||||
|
|
||||||
newParams = append(newParams, actionParam)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles check for required params
|
// Check to see if the action is valid
|
||||||
if !found && param.Required {
|
if curappaction.Name != action.Name {
|
||||||
log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name)
|
log.Printf("Appaction %s doesn't exist.", action.Name)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false}`))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// FIXME - check all parameters to see if they're valid
|
||||||
|
// Includes checking required fields
|
||||||
|
|
||||||
action.Parameters = newParams
|
newParams := []WorkflowAppActionParameter{}
|
||||||
newActions = append(newActions, action)
|
for _, param := range curappaction.Parameters {
|
||||||
|
found := false
|
||||||
|
|
||||||
|
// Handles check for parameter exists + value not empty in used fields
|
||||||
|
for _, actionParam := range action.Parameters {
|
||||||
|
if actionParam.Name == param.Name {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true {
|
||||||
|
log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name)))
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if actionParam.Variant == "" {
|
||||||
|
actionParam.Variant = "STATIC_VALUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
newParams = append(newParams, actionParam)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles check for required params
|
||||||
|
if !found && param.Required {
|
||||||
|
log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
action.Parameters = newParams
|
||||||
|
newActions = append(newActions, action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1877,9 +2063,25 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("Failed to change total actions data: %s", err)
|
log.Printf("Failed to change total actions data: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type returnData struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
returndata := returnData{
|
||||||
|
Success: true,
|
||||||
|
Errors: workflow.Errors,
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Saved new version of workflow %s (%s)", workflow.Name, fileId)
|
log.Printf("Saved new version of workflow %s (%s)", workflow.Name, fileId)
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(`{"success": true}`))
|
newBody, err := json.Marshal(returndata)
|
||||||
|
if err != nil {
|
||||||
|
resp.Write([]byte(`{"success": true}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Write(newBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) {
|
func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) {
|
||||||
@@ -1923,6 +2125,7 @@ func abortExecution(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Check the execution if this fails.
|
||||||
user, err := handleApiAuthentication(resp, request)
|
user, err := handleApiAuthentication(resp, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Api authentication failed in abort workflow: %s", err)
|
log.Printf("Api authentication failed in abort workflow: %s", err)
|
||||||
@@ -2049,16 +2252,14 @@ func cleanupExecutions(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//if user.Role != "admin" {
|
if user.Role != "admin" {
|
||||||
// resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
// resp.Write([]byte(`{"success": false, "message": "Insufficient permissions"}`))
|
resp.Write([]byte(`{"success": false, "message": "Insufficient permissions"}`))
|
||||||
// return
|
return
|
||||||
//}
|
}
|
||||||
|
|
||||||
log.Printf("CLEANUP!")
|
|
||||||
log.Printf("%#v", user)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Removes three months from today
|
// Removes three months from today
|
||||||
timestamp := int64(time.Now().AddDate(0, -2, 0).Unix())
|
timestamp := int64(time.Now().AddDate(0, -2, 0).Unix())
|
||||||
log.Println(timestamp)
|
log.Println(timestamp)
|
||||||
@@ -2072,8 +2273,6 @@ func cleanupExecutions(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(len(workflowExecutions))
|
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte("OK"))
|
resp.Write([]byte("OK"))
|
||||||
}
|
}
|
||||||
@@ -2373,9 +2572,9 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
|
|
||||||
for _, authparam := range curAuth.Fields {
|
for _, authparam := range curAuth.Fields {
|
||||||
if param.Name == authparam.Key {
|
if param.Name == authparam.Key {
|
||||||
log.Printf("Name: %s - value: %s", param.Name, param.Value)
|
|
||||||
param.Value = authparam.Value
|
param.Value = authparam.Value
|
||||||
log.Printf("Name: %s - value: %s\n", param.Name, param.Value)
|
//log.Printf("Name: %s - value: %s", param.Name, param.Value)
|
||||||
|
//log.Printf("Name: %s - value: %s\n", param.Name, param.Value)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2634,6 +2833,8 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
err = deleteSchedule(ctx, scheduleId)
|
err = deleteSchedule(ctx, scheduleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("Failed deleting schedule: %s", err)
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "Job not found") {
|
if strings.Contains(err.Error(), "Job not found") {
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
@@ -3143,6 +3344,18 @@ func getAllWorkflows(ctx context.Context) ([]Workflow, error) {
|
|||||||
return allworkflows, nil
|
return allworkflows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setExampleresult(ctx context.Context, result AppExecutionExample) error {
|
||||||
|
key := datastore.NameKey("example_result", result.ExampleId, nil)
|
||||||
|
|
||||||
|
// New struct, to not add body, author etc
|
||||||
|
if _, err := dbclient.Put(ctx, key, &result); err != nil {
|
||||||
|
log.Printf("Error adding workflow: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Hmm, so I guess this should use uuid :(
|
// Hmm, so I guess this should use uuid :(
|
||||||
// Consistency PLX
|
// Consistency PLX
|
||||||
func setWorkflow(ctx context.Context, workflow Workflow, id string) error {
|
func setWorkflow(ctx context.Context, workflow Workflow, id string) error {
|
||||||
@@ -3210,6 +3423,7 @@ func deleteAppAuthentication(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(`{"success": true}`))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Not suitable for cloud right now :O
|
||||||
func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -3250,7 +3464,7 @@ func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
|||||||
// FIXME - check whether it's in use and maybe restrict again for later?
|
// FIXME - check whether it's in use and maybe restrict again for later?
|
||||||
// FIXME - actually delete other than private apps too..
|
// FIXME - actually delete other than private apps too..
|
||||||
private := false
|
private := false
|
||||||
if app.Downloaded {
|
if app.Downloaded && user.Role == "admin" {
|
||||||
log.Printf("Deleting downloaded app (authenticated users can do this)")
|
log.Printf("Deleting downloaded app (authenticated users can do this)")
|
||||||
} else if user.Id != app.Owner && user.Role != "admin" {
|
} else if user.Id != app.Owner && user.Role != "admin" {
|
||||||
log.Printf("Wrong user (%s) for app %s (delete)", user.Username, app.Name)
|
log.Printf("Wrong user (%s) for app %s (delete)", user.Username, app.Name)
|
||||||
@@ -3270,6 +3484,8 @@ func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finds workflows using the app to set errors
|
||||||
|
// FIXME: this will be WAY too big for cloud :O
|
||||||
for _, workflow := range workflows {
|
for _, workflow := range workflows {
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
@@ -3290,7 +3506,8 @@ func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
|||||||
workflow.Actions = newActions
|
workflow.Actions = newActions
|
||||||
|
|
||||||
for _, trigger := range workflow.Triggers {
|
for _, trigger := range workflow.Triggers {
|
||||||
log.Printf("TRIGGER: %#v", trigger)
|
_ = trigger
|
||||||
|
//log.Printf("TRIGGER: %#v", trigger)
|
||||||
//err = deleteSchedule(ctx, scheduleId)
|
//err = deleteSchedule(ctx, scheduleId)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// if strings.Contains(err.Error(), "Job not found") {
|
// if strings.Contains(err.Error(), "Job not found") {
|
||||||
@@ -5121,8 +5338,8 @@ func getWorkflowExecutions(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(workflowExecutions) == 0 {
|
if len(workflowExecutions) == 0 {
|
||||||
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte("[]"))
|
resp.Write([]byte("[]"))
|
||||||
//resp.WriteHeader(200)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ services:
|
|||||||
- HTTP_PROXY=${SHUFFLE_HTTP_PROXY}
|
- HTTP_PROXY=${SHUFFLE_HTTP_PROXY}
|
||||||
- HTTPS_PROXY=${SHUFFLE_HTTPS_PROXY}
|
- HTTPS_PROXY=${SHUFFLE_HTTPS_PROXY}
|
||||||
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
||||||
|
- SHUFFLE_ORBORUS_EXECUTION_TIMEOUT=600
|
||||||
- SHUFFLE_BASE_IMAGE_NAME=${SHUFFLE_BASE_IMAGE_NAME}
|
- SHUFFLE_BASE_IMAGE_NAME=${SHUFFLE_BASE_IMAGE_NAME}
|
||||||
- SHUFFLE_BASE_IMAGE_REGISTRY=${SHUFFLE_BASE_IMAGE_REGISTRY}
|
- SHUFFLE_BASE_IMAGE_REGISTRY=${SHUFFLE_BASE_IMAGE_REGISTRY}
|
||||||
- SHUFFLE_BASE_IMAGE_TAG_SUFFIX=${SHUFFLE_BASE_IMAGE_TAG_SUFFIX}
|
- SHUFFLE_BASE_IMAGE_TAG_SUFFIX=${SHUFFLE_BASE_IMAGE_TAG_SUFFIX}
|
||||||
|
|||||||
Generated
+5
@@ -12177,6 +12177,11 @@
|
|||||||
"prop-types": "^15.6.1"
|
"prop-types": "^15.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"material-ui-nested-menu-item": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/material-ui-nested-menu-item/-/material-ui-nested-menu-item-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-LZb8xI0FrAI/A3P2vT3CB9bmSoOFWOK0dikTc1t9VvEpp1a8hZkbVUz7VhETnoLUYu3NXCkgulmXcl3zitqI9A=="
|
||||||
|
},
|
||||||
"material-ui-pickers": {
|
"material-ui-pickers": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/material-ui-pickers/-/material-ui-pickers-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/material-ui-pickers/-/material-ui-pickers-2.2.4.tgz",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"material-icons": "^0.3.1",
|
"material-icons": "^0.3.1",
|
||||||
"material-icons-react": "^1.0.4",
|
"material-icons-react": "^1.0.4",
|
||||||
"material-ui-chip-input": "^2.0.0-beta.2",
|
"material-ui-chip-input": "^2.0.0-beta.2",
|
||||||
|
"material-ui-nested-menu-item": "^1.0.2",
|
||||||
"md5-file": "^4.0.0",
|
"md5-file": "^4.0.0",
|
||||||
"mdbreact": "^4.21.1",
|
"mdbreact": "^4.21.1",
|
||||||
"moment": "~2.20.1",
|
"moment": "~2.20.1",
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ const App = (message, props) => {
|
|||||||
<Route exact path="/oauth2" render={props => <Oauth2 isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/oauth2" render={props => <Oauth2 isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/contact" render={props => <Contact isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/contact" render={props => <Contact isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||||
<Route exact path="/admin" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
<Route exact path="/admin" render={props => <Admin userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||||
<Route exact path="/admin/:key" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
<Route exact path="/admin/:key" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||||
<Route exact path="/settings" render={props => <SettingsPage isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/settings" render={props => <SettingsPage isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/AdminSetup" render={props => <AdminSetup isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/AdminSetup" render={props => <AdminSetup isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
||||||
@@ -141,12 +141,12 @@ const App = (message, props) => {
|
|||||||
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/workflows" render={props => <Workflows isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} {...props} />} />
|
<Route exact path="/workflows" render={props => <Workflows isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} {...props} />} />
|
||||||
<Route exact path="/workflows/:key" render={props => <AngularWorkflow globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} {...props} />} />
|
<Route exact path="/workflows/:key" render={props => <AngularWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} {...props} />} />
|
||||||
<Route exact path="/docs/:key" render={props => <Docs isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/docs/:key" render={props => <Docs isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
|
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
|
||||||
<Route exact path="/introduction" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/introduction" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/introduction/:key" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/introduction/:key" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/" render={props => { window.location.pathname = "/login" }} />
|
<Route exact path="/" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// <div style={{backgroundColor: "rgba(21, 32, 43, 1)", color: "#fffff", minHeight: "100vh"}}>
|
// <div style={{backgroundColor: "rgba(21, 32, 43, 1)", color: "#fffff", minHeight: "100vh"}}>
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ const Header = props => {
|
|||||||
// Should be based on some path
|
// Should be based on some path
|
||||||
const logoCheck = !homePage ? null : null
|
const logoCheck = !homePage ? null : null
|
||||||
|
|
||||||
|
|
||||||
// Handle top bar or something
|
// Handle top bar or something
|
||||||
const loginTextBrowser = !isLoggedIn ?
|
const loginTextBrowser = !isLoggedIn ?
|
||||||
<div style={{display: "flex"}}>
|
<div style={{display: "flex"}}>
|
||||||
@@ -184,18 +183,20 @@ const Header = props => {
|
|||||||
color="primary"> Settings</Button>
|
color="primary"> Settings</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
{userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null :
|
||||||
<Link to="/admin" style={hrefStyle}>
|
<ListItem>
|
||||||
<Button
|
<Link to="/admin" style={hrefStyle}>
|
||||||
style={{}}
|
<Button
|
||||||
variant="contained"
|
style={{}}
|
||||||
color="primary"
|
variant="contained"
|
||||||
>
|
color="primary"
|
||||||
Admin
|
>
|
||||||
</Button>
|
Admin
|
||||||
</Link>
|
</Button>
|
||||||
</ListItem>
|
</Link>
|
||||||
{userdata === undefined || userdata.orgs.length <= 1 ? null :
|
</ListItem>
|
||||||
|
}
|
||||||
|
{userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null :
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<Select
|
<Select
|
||||||
SelectDisplayProps={{
|
SelectDisplayProps={{
|
||||||
|
|||||||
+183
-19
@@ -36,9 +36,16 @@ const Admin = (props) => {
|
|||||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||||
const [modalUser, setModalUser] = React.useState({});
|
const [modalUser, setModalUser] = React.useState({});
|
||||||
const [modalOpen, setModalOpen] = React.useState(false);
|
const [modalOpen, setModalOpen] = React.useState(false);
|
||||||
|
|
||||||
|
const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
|
||||||
|
const [cloudSyncApikey, setCloudSyncApikey] = React.useState("");
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||||
const [loginInfo, setLoginInfo] = React.useState("");
|
const [loginInfo, setLoginInfo] = React.useState("");
|
||||||
const [curTab, setCurTab] = React.useState(0);
|
const [curTab, setCurTab] = React.useState(0);
|
||||||
const [users, setUsers] = React.useState([]);
|
const [users, setUsers] = React.useState([]);
|
||||||
|
const [organizations, setOrganizations] = React.useState([]);
|
||||||
const [environments, setEnvironments] = React.useState([]);
|
const [environments, setEnvironments] = React.useState([]);
|
||||||
const [authentication, setAuthentication] = React.useState([]);
|
const [authentication, setAuthentication] = React.useState([]);
|
||||||
const [schedules, setSchedules] = React.useState([])
|
const [schedules, setSchedules] = React.useState([])
|
||||||
@@ -175,6 +182,41 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enableCloudSync = (apikey, organization) => {
|
||||||
|
const data = {
|
||||||
|
apikey: apikey,
|
||||||
|
organization: organization,
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = globalUrl + '/api/v1/cloud/setup';
|
||||||
|
fetch(url, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: 'include',
|
||||||
|
crossDomain: true,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response =>
|
||||||
|
response.json().then(responseJson => {
|
||||||
|
setLoading(false)
|
||||||
|
console.log(responseJson)
|
||||||
|
if (responseJson["success"] === false) {
|
||||||
|
alert.error("Failed setting up cloud sync")
|
||||||
|
} else {
|
||||||
|
alert.success("Set up cloud sync!")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
setLoading(false)
|
||||||
|
alert.error("Err: " + error.toString())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const onPasswordChange = () => {
|
const onPasswordChange = () => {
|
||||||
const data = { "username": selectedUser.username, "newpassword": newPassword }
|
const data = { "username": selectedUser.username, "newpassword": newPassword }
|
||||||
const url = globalUrl + '/api/v1/users/passwordchange';
|
const url = globalUrl + '/api/v1/users/passwordchange';
|
||||||
@@ -471,6 +513,31 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOrgs = () => {
|
||||||
|
fetch(globalUrl + "/api/v1/getorgs", {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for apps :O!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
setOrganizations(responseJson)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert.error(error.toString())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const getUsers = () => {
|
const getUsers = () => {
|
||||||
fetch(globalUrl + "/api/v1/getusers", {
|
fetch(globalUrl + "/api/v1/getusers", {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -482,7 +549,7 @@ const Admin = (props) => {
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
console.log("Status not 200 for apps :O!")
|
window.location.pathname = "/workflows"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,8 +780,69 @@ const Admin = (props) => {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
const cloudSyncModal =
|
||||||
|
<Dialog
|
||||||
|
open={cloudSyncModalOpen}
|
||||||
|
onClose={() => { setCloudSyncModalOpen(false) }}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
|
color: "white",
|
||||||
|
minWidth: "800px",
|
||||||
|
minHeight: "320px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle><span style={{ color: "white" }}>
|
||||||
|
Enable cloud features
|
||||||
|
</span></DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
What does <a href="https://shuffler.io/docs/hybrid#cloud_sync" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
|
||||||
|
<div style={{marginTop: 5}}/>
|
||||||
|
Cloud Apikey
|
||||||
|
<div style={{display: "flex", marginBottom: 20, }}>
|
||||||
|
<TextField
|
||||||
|
color="primary"
|
||||||
|
style={{backgroundColor: theme.palette.inputColor, marginRight: 10, }}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: "50px",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth={true}
|
||||||
|
autoComplete="cloud apikey"
|
||||||
|
id="apikey_field"
|
||||||
|
margin="normal"
|
||||||
|
placeholder="Cloud Apikey"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(event) => {
|
||||||
|
setCloudSyncApikey(event.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button disabled={cloudSyncApikey.length === 0 || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
|
||||||
|
setLoading(true)
|
||||||
|
enableCloudSync(
|
||||||
|
cloudSyncApikey,
|
||||||
|
selectedOrganization,
|
||||||
|
)
|
||||||
|
}} color="primary">
|
||||||
|
Test sync
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
* New triggers (userinput, hotmail realtime)<div/>
|
||||||
|
* Execute in the cloud rather than onprem<div/>
|
||||||
|
* Apps can be built in the cloud<div/>
|
||||||
|
* Easily share apps and workflows<div/>
|
||||||
|
* Access to powerful cloud search
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
const modalView =
|
const modalView =
|
||||||
<Dialog modal
|
<Dialog
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
onClose={() => { setModalOpen(false) }}
|
onClose={() => { setModalOpen(false) }}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
@@ -1229,7 +1357,10 @@ const Admin = (props) => {
|
|||||||
style={{}}
|
style={{}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setModalOpen(true)}
|
disabled
|
||||||
|
onClick={() => {
|
||||||
|
setModalOpen(true)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Add organization
|
Add organization
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1241,28 +1372,58 @@ const Admin = (props) => {
|
|||||||
style={{minWidth: 150, maxWidth: 150}}
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Orborus running (TBD)"
|
primary="id"
|
||||||
style={{minWidth: 200, maxWidth: 200}}
|
style={{minWidth: 200, maxWidth: 200}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Actions"
|
primary="Your role"
|
||||||
style={{minWidth: 150, maxWidth: 150}}
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
<ListItemText
|
||||||
<ListItem>
|
primary="Selected"
|
||||||
<ListItemText
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
primary="Enabled"
|
/>
|
||||||
style={{minWidth: 150, maxWidth: 150}}
|
<ListItemText
|
||||||
/>
|
primary="Cloud Sync"
|
||||||
<ListItemText
|
|
||||||
primary="false"
|
|
||||||
style={{minWidth: 200, maxWidth: 200}}
|
|
||||||
/>
|
|
||||||
<ListItemText
|
|
||||||
primary=<Switch checked={false} onChange={() => {console.log("INVERT")}} />
|
|
||||||
style={{minWidth: 150, maxWidth: 150}}
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
{organizations !== undefined && organizations !== null && organizations.length > 0 ?
|
||||||
|
<span>
|
||||||
|
{organizations.map((data, index) => {
|
||||||
|
const isSelected = props.userdata.active_org.id === undefined ? "False" : props.userdata.active_org.id === data.id ? "True" : "False"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem key={index}>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.name}
|
||||||
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.id}
|
||||||
|
style={{minWidth: 200, maxWidth: 200}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.role}
|
||||||
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={isSelected}
|
||||||
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary=<Switch checked={data.cloud_sync} onChange={() => {
|
||||||
|
setCloudSyncModalOpen(true)
|
||||||
|
setSelectedOrganization(data)
|
||||||
|
console.log("INVERT CLOUD SYNC")
|
||||||
|
}} />
|
||||||
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
: null}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
@@ -1316,6 +1477,8 @@ const Admin = (props) => {
|
|||||||
getEnvironments()
|
getEnvironments()
|
||||||
} else if (newValue === 3) {
|
} else if (newValue === 3) {
|
||||||
getSchedules()
|
getSchedules()
|
||||||
|
} else if (newValue === 5) {
|
||||||
|
getOrgs()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue === 6) {
|
if (newValue === 6) {
|
||||||
@@ -1360,6 +1523,7 @@ const Admin = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{modalView}
|
{modalView}
|
||||||
|
{cloudSyncModal}
|
||||||
{editUserModal}
|
{editUserModal}
|
||||||
{editAuthenticationModal}
|
{editAuthenticationModal}
|
||||||
{data}
|
{data}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -376,16 +376,6 @@ const AppCreator = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (data.servers !== undefined && data.servers.length > 0) {
|
|
||||||
var firstUrl = data.servers[0].url
|
|
||||||
if (firstUrl.endsWith("/")) {
|
|
||||||
setBaseUrl(firstUrl.slice(0, firstUrl.length-1))
|
|
||||||
} else {
|
|
||||||
setBaseUrl(firstUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.tags !== undefined && data.tags.length > 0) {
|
if (data.tags !== undefined && data.tags.length > 0) {
|
||||||
for (var key in data.tags) {
|
for (var key in data.tags) {
|
||||||
newWorkflowTags.push(data.tags[key].name)
|
newWorkflowTags.push(data.tags[key].name)
|
||||||
@@ -425,12 +415,16 @@ const AppCreator = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!allowedfunctions.includes(method.toUpperCase())) {
|
if (!allowedfunctions.includes(method.toUpperCase())) {
|
||||||
console.log(method, path)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tmpname = methodvalue.summary
|
||||||
|
if (methodvalue.operationId !== undefined && methodvalue.operationId !== null && methodvalue.operationId.length > 0) {
|
||||||
|
tmpname = methodvalue.operationId
|
||||||
|
}
|
||||||
|
|
||||||
var newaction = {
|
var newaction = {
|
||||||
"name": methodvalue.summary,
|
"name": tmpname,
|
||||||
"description": methodvalue.description,
|
"description": methodvalue.description,
|
||||||
"url": path,
|
"url": path,
|
||||||
"method": method.toUpperCase(),
|
"method": method.toUpperCase(),
|
||||||
@@ -532,6 +526,30 @@ const AppCreator = (props) => {
|
|||||||
}
|
}
|
||||||
newActions.push(newaction)
|
newActions.push(newaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.servers !== undefined && data.servers.length > 0) {
|
||||||
|
var firstUrl = data.servers[0].url
|
||||||
|
if (firstUrl.includes("{") && firstUrl.includes("}") && data.servers[0].variables !== undefined) {
|
||||||
|
const regex = /{\w+}/g
|
||||||
|
const found = firstUrl.match(regex)
|
||||||
|
if (found !== null) {
|
||||||
|
for (var key in found) {
|
||||||
|
const item = found[key].slice(1, found[key].length-1)
|
||||||
|
const foundVar = data.servers[0].variables[item]
|
||||||
|
if (foundVar["default"] !== undefined) {
|
||||||
|
firstUrl = firstUrl.replace(found[key], foundVar["default"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstUrl.endsWith("/")) {
|
||||||
|
setBaseUrl(firstUrl.slice(0, firstUrl.length-1))
|
||||||
|
} else {
|
||||||
|
setBaseUrl(firstUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -655,6 +673,7 @@ const AppCreator = (props) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary": item.name,
|
"summary": item.name,
|
||||||
|
"operationId": item.name.split(" ").join("_"),
|
||||||
"description": item.description,
|
"description": item.description,
|
||||||
"parameters": []
|
"parameters": []
|
||||||
}
|
}
|
||||||
@@ -1060,18 +1079,28 @@ const AppCreator = (props) => {
|
|||||||
:
|
:
|
||||||
<div>
|
<div>
|
||||||
{actions.map((data, index) => {
|
{actions.map((data, index) => {
|
||||||
var error = <Tooltip color="secondary" title={data.errors.join("\n")} placement="bottom">
|
var error = data.errors.length > 0 ?
|
||||||
|
<Tooltip color="primary" title={data.errors.join("\n")} placement="bottom">
|
||||||
|
<ErrorOutline />
|
||||||
|
</Tooltip>
|
||||||
|
:
|
||||||
|
<Tooltip color="secondary" title={data.errors.join("\n")} placement="bottom">
|
||||||
<CheckCircleIcon />
|
<CheckCircleIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
// "ERROR: "+data.errors.join("\n")
|
|
||||||
if (data.errors.length > 0) {
|
|
||||||
error =
|
|
||||||
<Tooltip color="primary" title={data.errors.join("\n")} placement="bottom">
|
|
||||||
<ErrorOutline />
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var bgColor = "#61afee"
|
||||||
|
if (data.method === "POST") {
|
||||||
|
bgColor = "#49cc90"
|
||||||
|
} else if (data.method === "PUT") {
|
||||||
|
bgColor = "#fca130"
|
||||||
|
} else if (data.method === "PATCH") {
|
||||||
|
bgColor = "#50e3c2"
|
||||||
|
} else if (data.method === "DELETE") {
|
||||||
|
bgColor = "#f93e3e"
|
||||||
|
} else if (data.method === "HEAD") {
|
||||||
|
bgColor = "#9012fe"
|
||||||
|
}
|
||||||
|
|
||||||
const url = data.url
|
const url = data.url
|
||||||
return (
|
return (
|
||||||
@@ -1085,7 +1114,16 @@ const AppCreator = (props) => {
|
|||||||
setUrlPath(data.url)
|
setUrlPath(data.url)
|
||||||
setActionsModalOpen(true)
|
setActionsModalOpen(true)
|
||||||
}}>
|
}}>
|
||||||
{data.method} - {url} - {data.name}
|
<div style={{display: "flex"}}>
|
||||||
|
<Chip
|
||||||
|
style={{backgroundColor: bgColor, color: "white", borderRadius: 5, minWidth: 80, marginRight: 10, marginTop: 2, cursor: "pointer", fontSize: 14,}}
|
||||||
|
label={data.method}
|
||||||
|
variant="contained"
|
||||||
|
/>
|
||||||
|
<span style={{fontSize: 16, marginTop: "auto", marginBottom: "auto",}}>
|
||||||
|
{url} - {data.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
+71
-34
@@ -43,13 +43,31 @@ const inputColor = "#383B40"
|
|||||||
|
|
||||||
// Parses JSON data into keys that can be used everywhere :)
|
// Parses JSON data into keys that can be used everywhere :)
|
||||||
export const GetParsedPaths = (inputdata, basekey) => {
|
export const GetParsedPaths = (inputdata, basekey) => {
|
||||||
const splitkey = " => "
|
const splitkey = " > "
|
||||||
var parsedValues = []
|
var parsedValues = []
|
||||||
for (const [key, value] of Object.entries(inputdata)) {
|
if (typeof(inputdata) !== "object") {
|
||||||
|
return parsedValues
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(inputdata)) {
|
||||||
// Check if loop or JSON
|
// Check if loop or JSON
|
||||||
const extra = basekey.length > 0 ? splitkey : ""
|
const extra = basekey.length > 0 ? splitkey : ""
|
||||||
const basekeyname = `${basekey.slice(1,basekey.length).split(".").join(splitkey)}${extra}${key}`
|
const basekeyname = `${basekey.slice(1, basekey.length).split(".").join(splitkey)}${extra}${key}`
|
||||||
|
|
||||||
|
// Handle direct loop!
|
||||||
|
if (!isNaN(key) && basekey === "") {
|
||||||
|
console.log("Handling direct loop.")
|
||||||
|
parsedValues.push({"type": "object", "name": "Node", "autocomplete": `${basekey}`})
|
||||||
|
parsedValues.push({"type": "list", "name": `${splitkey}list`, "autocomplete": `${basekey}.#`})
|
||||||
|
const returnValues = GetParsedPaths(value, `${basekey}.#`)
|
||||||
|
for (var subkey in returnValues) {
|
||||||
|
parsedValues.push(returnValues[subkey])
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedValues
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("KEY: ", key, "VALUE: ", value, "BASEKEY: ", basekeyname)
|
||||||
if (typeof(value) === 'object') {
|
if (typeof(value) === 'object') {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// Check if each item is object
|
// Check if each item is object
|
||||||
@@ -101,6 +119,7 @@ const Apps = (props) => {
|
|||||||
const [appSearchLoading, setAppSearchLoading] = React.useState(false)
|
const [appSearchLoading, setAppSearchLoading] = React.useState(false)
|
||||||
const [selectedAction, setSelectedAction] = React.useState({})
|
const [selectedAction, setSelectedAction] = React.useState({})
|
||||||
const [searchBackend, setSearchBackend] = React.useState(false)
|
const [searchBackend, setSearchBackend] = React.useState(false)
|
||||||
|
const [searchableApps, setSearchableApps] = React.useState([])
|
||||||
|
|
||||||
const [openApi, setOpenApi] = React.useState("")
|
const [openApi, setOpenApi] = React.useState("")
|
||||||
const [openApiData, setOpenApiData] = React.useState("")
|
const [openApiData, setOpenApiData] = React.useState("")
|
||||||
@@ -112,6 +131,7 @@ const Apps = (props) => {
|
|||||||
const [openApiError, setOpenApiError] = React.useState("")
|
const [openApiError, setOpenApiError] = React.useState("")
|
||||||
const [field1, setField1] = React.useState("")
|
const [field1, setField1] = React.useState("")
|
||||||
const [field2, setField2] = React.useState("")
|
const [field2, setField2] = React.useState("")
|
||||||
|
const [cursearch, setCursearch] = React.useState("")
|
||||||
const [sharingConfiguration, setSharingConfiguration] = React.useState("you")
|
const [sharingConfiguration, setSharingConfiguration] = React.useState("you")
|
||||||
|
|
||||||
const { start, stop } = useInterval({
|
const { start, stop } = useInterval({
|
||||||
@@ -165,6 +185,8 @@ const Apps = (props) => {
|
|||||||
maxHeight: 130,
|
maxHeight: 130,
|
||||||
minWidth: "100%",
|
minWidth: "100%",
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
|
marginBottom: 5,
|
||||||
|
borderRadius: 5,
|
||||||
color: "white",
|
color: "white",
|
||||||
backgroundColor: surfaceColor,
|
backgroundColor: surfaceColor,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
@@ -200,6 +222,8 @@ const Apps = (props) => {
|
|||||||
setSelectedAction({})
|
setSelectedAction({})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runAppSearch("")
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
alert.error(error.toString())
|
alert.error(error.toString())
|
||||||
@@ -277,6 +301,10 @@ const Apps = (props) => {
|
|||||||
boxColor = "green"
|
boxColor = "green"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.activated && data.generated) {
|
||||||
|
boxColor = "orange"
|
||||||
|
}
|
||||||
|
|
||||||
var imageline = data.large_image.length === 0 ?
|
var imageline = data.large_image.length === 0 ?
|
||||||
<img alt={data.title} style={{width: 100, height: 100}} />
|
<img alt={data.title} style={{width: 100, height: 100}} />
|
||||||
:
|
:
|
||||||
@@ -307,6 +335,7 @@ const Apps = (props) => {
|
|||||||
description = data.description.slice(0, maxDescLen)+"..."
|
description = data.description.slice(0, maxDescLen)+"..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const version = data.app_version
|
||||||
return (
|
return (
|
||||||
<Paper square key={data.id} style={paperAppStyle} onClick={() => {
|
<Paper square key={data.id} style={paperAppStyle} onClick={() => {
|
||||||
if (selectedApp.id !== data.id) {
|
if (selectedApp.id !== data.id) {
|
||||||
@@ -319,7 +348,6 @@ const Apps = (props) => {
|
|||||||
setSelectedAction({})
|
setSelectedAction({})
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Sharing: ", data.sharing_config)
|
|
||||||
if (data.sharing) {
|
if (data.sharing) {
|
||||||
setSharingConfiguration("everyone")
|
setSharingConfiguration("everyone")
|
||||||
}
|
}
|
||||||
@@ -375,9 +403,10 @@ const Apps = (props) => {
|
|||||||
|
|
||||||
const dividerColor = "rgb(225, 228, 232)"
|
const dividerColor = "rgb(225, 228, 232)"
|
||||||
const uploadViewPaperStyle = {
|
const uploadViewPaperStyle = {
|
||||||
minWidth: "100%",
|
minWidth: 662.5,
|
||||||
maxWidth: 662.5,
|
maxWidth: 662.5,
|
||||||
color: "white",
|
color: "white",
|
||||||
|
borderRadius: 5,
|
||||||
backgroundColor: surfaceColor,
|
backgroundColor: surfaceColor,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
@@ -430,7 +459,7 @@ const Apps = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Link> : null
|
</Link> : null
|
||||||
|
|
||||||
var activateButton = selectedApp.generated && !selectedApp.activated ?
|
const activateButton = selectedApp.generated && !selectedApp.activated ?
|
||||||
<div>
|
<div>
|
||||||
<Link to={activateUrl} style={{textDecoration: "none"}}>
|
<Link to={activateUrl} style={{textDecoration: "none"}}>
|
||||||
<Button
|
<Button
|
||||||
@@ -458,7 +487,12 @@ const Apps = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
var deleteButton = ((selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated) || (selectedApp.downloaded != undefined && selectedApp.downloaded == true)) && activateButton === null ?
|
const deleteButton = (
|
||||||
|
(selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated)
|
||||||
|
|| (selectedApp.downloaded !== undefined && selectedApp.downloaded == true)
|
||||||
|
|| (!selectedApp.generated)
|
||||||
|
)
|
||||||
|
&& activateButton === null ?
|
||||||
<Tooltip title={"Delete app"}>
|
<Tooltip title={"Delete app"}>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -507,10 +541,10 @@ const Apps = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{paths.map(data => {
|
{paths.map((data, index) => {
|
||||||
const circleSize = 10
|
const circleSize = 10
|
||||||
return (
|
return (
|
||||||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data} onClick={() => console.log(data.autocomplete)}>
|
<MenuItem key={index} style={{backgroundColor: inputColor, color: "white"}} value={data} onClick={() => console.log(data.autocomplete)}>
|
||||||
{data.name}
|
{data.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
@@ -549,12 +583,13 @@ const Apps = (props) => {
|
|||||||
{imageline}
|
{imageline}
|
||||||
</div>
|
</div>
|
||||||
<div style={{maxWidth: "75%", overflow: "hidden"}}>
|
<div style={{maxWidth: "75%", overflow: "hidden"}}>
|
||||||
<h2>{newAppname}</h2>
|
<h2 style={{marginTop: 20, marginBottom: 0, }}>{newAppname}</h2>
|
||||||
<p>{description}</p>
|
<p style={{marginTop: 5, marginBottom: 0,}}>Version {selectedApp.app_version}</p>
|
||||||
|
<p style={{marginTop: 5, marginBottom: 0}}>{description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{activateButton}
|
{activateButton}
|
||||||
{props.userdata.role === "admin" || props.userdata.id === selectedApp.owner ?
|
{(props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated ?
|
||||||
<div>
|
<div>
|
||||||
{downloadButton}
|
{downloadButton}
|
||||||
{editButton}
|
{editButton}
|
||||||
@@ -742,15 +777,17 @@ const Apps = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchfield = search.toLowerCase()
|
const searchfield = search.toLowerCase()
|
||||||
const newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield))
|
var newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield))
|
||||||
|
var tmpapps = searchableApps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield))
|
||||||
|
newapps.push(...tmpapps)
|
||||||
|
|
||||||
if ((newapps.length === 0 || searchBackend) && !appSearchLoading) {
|
setFilteredApps(newapps)
|
||||||
|
//if ((newapps.length === 0 || searchBackend) && !appSearchLoading) {
|
||||||
|
|
||||||
setAppSearchLoading(true)
|
// //setAppSearchLoading(true)
|
||||||
runAppSearch(searchfield)
|
// //runAppSearch(searchfield)
|
||||||
} else {
|
//} else {
|
||||||
setFilteredApps(newapps)
|
//}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const appView = isLoggedIn ?
|
const appView = isLoggedIn ?
|
||||||
@@ -778,17 +815,8 @@ const Apps = (props) => {
|
|||||||
<div style={{flex: 1, marginLeft: 10, marginRight: 10}}>
|
<div style={{flex: 1, marginLeft: 10, marginRight: 10}}>
|
||||||
<div style={{display: "flex"}}>
|
<div style={{display: "flex"}}>
|
||||||
<div style={{flex: 1}}>
|
<div style={{flex: 1}}>
|
||||||
<h2>All apps</h2>
|
<h2>Your apps ({apps.length+searchableApps.length})</h2>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? <CircularProgress style={{marginTop: 13, marginRight: 15}} /> : null}
|
|
||||||
<FormControlLabel
|
|
||||||
style={{color: "white", marginBottom: "0px", marginTop: "10px"}}
|
|
||||||
label={<div style={{color: "white"}}>Search OpenAPI</div>}
|
|
||||||
control={<Switch checked={searchBackend} onChange={() => {
|
|
||||||
handleSearchChange("")
|
|
||||||
setSearchBackend(!searchBackend)}
|
|
||||||
} />}
|
|
||||||
/>
|
|
||||||
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -833,6 +861,7 @@ const Apps = (props) => {
|
|||||||
placeholder={"Search apps"}
|
placeholder={"Search apps"}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
handleSearchChange(event.target.value)
|
handleSearchChange(event.target.value)
|
||||||
|
setCursearch(event.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div style={{marginTop: 15}}>
|
<div style={{marginTop: 15}}>
|
||||||
@@ -844,11 +873,18 @@ const Apps = (props) => {
|
|||||||
appPaper(app)
|
appPaper(app)
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
{cursearch.length > 0 ? null :
|
||||||
|
searchableApps.map(app => {
|
||||||
|
return (
|
||||||
|
appPaper(app)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<Paper square style={uploadViewPaperStyle}>
|
<Paper square style={uploadViewPaperStyle}>
|
||||||
<h4>
|
<h4 style={{margin: 10, }}>
|
||||||
Try a broader search term, e.g. "http", "alert", "ticket" etc.
|
Try a broader search term, e.g. http, alert, ticket etc.
|
||||||
</h4>
|
</h4>
|
||||||
<div/>
|
<div/>
|
||||||
|
|
||||||
@@ -859,7 +895,7 @@ const Apps = (props) => {
|
|||||||
</Paper>
|
</Paper>
|
||||||
:
|
:
|
||||||
<Paper square style={uploadViewPaperStyle}>
|
<Paper square style={uploadViewPaperStyle}>
|
||||||
<h4>
|
<h4 style={{margin: 10}}>
|
||||||
No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images.
|
No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images.
|
||||||
</h4>
|
</h4>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -939,6 +975,7 @@ const Apps = (props) => {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
alert.success("Hotloaded apps!")
|
alert.success("Hotloaded apps!")
|
||||||
|
getApps()
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
@@ -1045,10 +1082,10 @@ const Apps = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
//console.log(responseJson)
|
|
||||||
if (responseJson.success) {
|
if (responseJson.success) {
|
||||||
if (responseJson.reason !== undefined && responseJson.reason.length > 0) {
|
if (responseJson.reason !== undefined && responseJson.reason.length > 0) {
|
||||||
setFilteredApps(responseJson.reason)
|
setSearchableApps(responseJson.reason)
|
||||||
|
//setFilteredApps(responseJson.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -123,8 +123,7 @@ const Settings = (props) => {
|
|||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
console.log(responseJson)
|
|
||||||
setUserSettings(responseJson)
|
setUserSettings(responseJson)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
@@ -543,22 +543,24 @@ const Workflows = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container style={{maxWidth: 35, marginRight: 10,}}>
|
{data.actions !== undefined && data.actions !== null ?
|
||||||
<Tooltip title={`Actions: ${data.actions.length}`} placement="right">
|
<Grid container style={{maxWidth: 35, marginRight: 10,}}>
|
||||||
<AppsIcon style={{width: imgSize, height: imgSize}} />
|
<Tooltip title={`Actions: ${data.actions.length}`} placement="right">
|
||||||
</Tooltip>
|
<AppsIcon style={{width: imgSize, height: imgSize}} />
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
{webhooks > 0 ?
|
{webhooks > 0 ?
|
||||||
<Tooltip title={`Webhooks: ${webhooks}`} placement="right">
|
<Tooltip title={`Webhooks: ${webhooks}`} placement="right">
|
||||||
<img alt={data.title} style={{width: imgSize, height: imgSize, marginTop: 5}} src={webhookImg} />
|
<img alt={data.title} style={{width: imgSize, height: imgSize, marginTop: 5}} src={webhookImg} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
: null}
|
: null}
|
||||||
{schedules > 0 ?
|
{schedules > 0 ?
|
||||||
<Tooltip title={`Schedules: ${schedules}`} placement="right">
|
<Tooltip title={`Schedules: ${schedules}`} placement="right">
|
||||||
<img alt={data.title} style={{width: imgSize, height: imgSize, marginTop: 5}} src={scheduleImg} />
|
<img alt={data.title} style={{width: imgSize, height: imgSize, marginTop: 5}} src={scheduleImg} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
: null}
|
: null}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
: null}
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -587,7 +589,7 @@ const Workflows = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper key={data.name} square style={paperAppStyle} onClick={() => {
|
<Paper key={data.execution_id} square style={paperAppStyle} onClick={() => {
|
||||||
setSelectedExecution(data)
|
setSelectedExecution(data)
|
||||||
}}>
|
}}>
|
||||||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}} />
|
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}} />
|
||||||
@@ -682,7 +684,7 @@ const Workflows = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper key={data.name} square style={resultPaperAppStyle} onClick={() => {}}>
|
<Paper key={data.execution_id} square style={resultPaperAppStyle} onClick={() => {}}>
|
||||||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", marginRight: "5px", width: boxWidth, backgroundColor: boxColor}}>
|
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", marginRight: "5px", width: boxWidth, backgroundColor: boxColor}}>
|
||||||
</div>
|
</div>
|
||||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}}>
|
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}}>
|
||||||
@@ -715,9 +717,11 @@ const Workflows = (props) => {
|
|||||||
|
|
||||||
const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ?
|
const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ?
|
||||||
<div>
|
<div>
|
||||||
{selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map(data => {
|
{selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map((data, index) => {
|
||||||
return (
|
return (
|
||||||
resultsPaper(data)
|
<div key={index}>
|
||||||
|
{resultsPaper(data)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -912,6 +916,7 @@ const Workflows = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
const importFiles = (event) => {
|
const importFiles = (event) => {
|
||||||
|
console.log("Importing!")
|
||||||
const file = event.target.value
|
const file = event.target.value
|
||||||
if (event.target.files.length > 0) {
|
if (event.target.files.length > 0) {
|
||||||
for (var key in event.target.files) {
|
for (var key in event.target.files) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ import (
|
|||||||
var sleepTime = 3
|
var sleepTime = 3
|
||||||
|
|
||||||
// Timeout if something rashes
|
// Timeout if something rashes
|
||||||
var workerTimeout = 300
|
var workerTimeoutEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_TIMEOUT")
|
||||||
var appSdkVersion = os.Getenv("SHUFFLE_APP_SDK_VERSION")
|
var appSdkVersion = os.Getenv("SHUFFLE_APP_SDK_VERSION")
|
||||||
var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION")
|
var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION")
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ func getThisContainerId() {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
fCol = "3" // for backward-compatibility with production
|
fCol = "3" // for backward-compatibility with production
|
||||||
log.Printf("[WARNING] RUNNING_MODE not set, so I can't figure out the current container ID! Defaulting to Docker (not Kubernetes).")
|
log.Printf("[WARNING] RUNNING_MODE not set - defaulting to Docker (NOT Kubernetes).")
|
||||||
}
|
}
|
||||||
|
|
||||||
if fCol != "" {
|
if fCol != "" {
|
||||||
@@ -250,7 +251,6 @@ func initializeImages() {
|
|||||||
|
|
||||||
// Initial loop etc
|
// Initial loop etc
|
||||||
func main() {
|
func main() {
|
||||||
go zombiecheck()
|
|
||||||
log.Println("[INFO] Setting up execution environment")
|
log.Println("[INFO] Setting up execution environment")
|
||||||
|
|
||||||
//FIXME
|
//FIXME
|
||||||
@@ -264,6 +264,20 @@ func main() {
|
|||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workerTimeout := 600
|
||||||
|
if workerTimeoutEnv != "" {
|
||||||
|
tmpInt, err := strconv.Atoi(workerTimeoutEnv)
|
||||||
|
if err == nil {
|
||||||
|
workerTimeout = tmpInt
|
||||||
|
} else {
|
||||||
|
log.Printf("[WARNING] Env SHUFFLE_ORBORUS_EXECUTION_TIMEOUT must be a number, not %s", workerTimeoutEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Cleanup process running every %d seconds", workerTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
go zombiecheck(workerTimeout)
|
||||||
|
|
||||||
log.Printf("[INFO] Running towards %s with Org %s", baseUrl, orgId)
|
log.Printf("[INFO] Running towards %s with Org %s", baseUrl, orgId)
|
||||||
httpProxy := os.Getenv("HTTP_PROXY")
|
httpProxy := os.Getenv("HTTP_PROXY")
|
||||||
httpsProxy := os.Getenv("HTTPS_PROXY")
|
httpsProxy := os.Getenv("HTTPS_PROXY")
|
||||||
@@ -330,7 +344,7 @@ func main() {
|
|||||||
log.Printf("[WARNING] Failed making request: %s", err)
|
log.Printf("[WARNING] Failed making request: %s", err)
|
||||||
zombiecounter += 1
|
zombiecounter += 1
|
||||||
if zombiecounter*sleepTime > workerTimeout {
|
if zombiecounter*sleepTime > workerTimeout {
|
||||||
go zombiecheck()
|
go zombiecheck(workerTimeout)
|
||||||
zombiecounter = 0
|
zombiecounter = 0
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||||
@@ -351,7 +365,7 @@ func main() {
|
|||||||
log.Printf("[ERROR] Failed reading body: %s", err)
|
log.Printf("[ERROR] Failed reading body: %s", err)
|
||||||
zombiecounter += 1
|
zombiecounter += 1
|
||||||
if zombiecounter*sleepTime > workerTimeout {
|
if zombiecounter*sleepTime > workerTimeout {
|
||||||
go zombiecheck()
|
go zombiecheck(workerTimeout)
|
||||||
zombiecounter = 0
|
zombiecounter = 0
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||||
@@ -365,7 +379,7 @@ func main() {
|
|||||||
sleepTime = 10
|
sleepTime = 10
|
||||||
zombiecounter += 1
|
zombiecounter += 1
|
||||||
if zombiecounter*sleepTime > workerTimeout {
|
if zombiecounter*sleepTime > workerTimeout {
|
||||||
go zombiecheck()
|
go zombiecheck(workerTimeout)
|
||||||
zombiecounter = 0
|
zombiecounter = 0
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||||
@@ -380,7 +394,7 @@ func main() {
|
|||||||
if len(executionRequests.Data) == 0 {
|
if len(executionRequests.Data) == 0 {
|
||||||
zombiecounter += 1
|
zombiecounter += 1
|
||||||
if zombiecounter*sleepTime > workerTimeout {
|
if zombiecounter*sleepTime > workerTimeout {
|
||||||
go zombiecheck()
|
go zombiecheck(workerTimeout)
|
||||||
zombiecounter = 0
|
zombiecounter = 0
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||||
@@ -487,7 +501,7 @@ func main() {
|
|||||||
|
|
||||||
// FIXME - add this to remove exited workers
|
// FIXME - add this to remove exited workers
|
||||||
// Should it check what happened to the execution? idk
|
// Should it check what happened to the execution? idk
|
||||||
func zombiecheck() error {
|
func zombiecheck(workerTimeout int) error {
|
||||||
log.Println("[INFO] Looking for old containers")
|
log.Println("[INFO] Looking for old containers")
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|||||||
@@ -694,7 +694,15 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
// FIXME: Force killing a worker should result in a notification somewhere
|
// FIXME: Force killing a worker should result in a notification somewhere
|
||||||
if len(nextActions) == 0 {
|
if len(nextActions) == 0 {
|
||||||
log.Printf("No next action. Finished? Result vs Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
|
log.Printf("No next action. Finished? Result vs Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
|
||||||
if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) {
|
//exit := true
|
||||||
|
//for _, item := range workflowExecution.Results {
|
||||||
|
// if item == "EXECUTING" {
|
||||||
|
// exit = false
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
if exit && len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) {
|
||||||
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user