@@ -4,8 +4,13 @@ ENVIRONMENT_NAME=Shuffle
|
|||||||
|
|
||||||
# Remote github config for first load
|
# Remote github config for first load
|
||||||
APP_DOWNLOAD_LOCATION=https://github.com/frikky/shuffle-apps
|
APP_DOWNLOAD_LOCATION=https://github.com/frikky/shuffle-apps
|
||||||
APP_DOWNLOAD_AUTH_USERNAME=""
|
APP_DOWNLOAD_AUTH_USERNAME=
|
||||||
APP_DOWNLOAD_AUTH_PASSWORD=""
|
APP_DOWNLOAD_AUTH_PASSWORD=
|
||||||
|
|
||||||
|
# User config for first load. Username & PW: min length 3
|
||||||
|
SHUFFLE_DEFAULT_USERNAME=
|
||||||
|
SHUFFLE_DEFAULT_PASSWORD=
|
||||||
|
SHUFFLE_DEFAULT_APIKEY=
|
||||||
|
|
||||||
# Local location of your app directory. Can't use ~/
|
# Local location of your app directory. Can't use ~/
|
||||||
APP_HOTLOAD_LOCATION=./shuffle-apps
|
APP_HOTLOAD_LOCATION=./shuffle-apps
|
||||||
|
|||||||
@@ -18,4 +18,3 @@ functions/generated_apps
|
|||||||
|
|
||||||
backend/onprem/app_sdk/apps
|
backend/onprem/app_sdk/apps
|
||||||
*test.py
|
*test.py
|
||||||
shuffle-apps/*
|
|
||||||
|
|||||||
+97
-39
@@ -173,7 +173,16 @@ class AppBase:
|
|||||||
if "split" in thistype:
|
if "split" in thistype:
|
||||||
return data.split()
|
return data.split()
|
||||||
if "len" in thistype or "length" in thistype:
|
if "len" in thistype or "length" in thistype:
|
||||||
return len(data)
|
tmp = ""
|
||||||
|
try:
|
||||||
|
tmp = json.loads(data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(tmp, list):
|
||||||
|
return str(len(tmp))
|
||||||
|
|
||||||
|
return str(len(data))
|
||||||
if "parse" in thistype:
|
if "parse" in thistype:
|
||||||
splitvalues = []
|
splitvalues = []
|
||||||
default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)"""
|
default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)"""
|
||||||
@@ -331,7 +340,7 @@ class AppBase:
|
|||||||
#print("WF Variables: %s" % execution_data["workflow"]["workflow_variables"])
|
#print("WF Variables: %s" % execution_data["workflow"]["workflow_variables"])
|
||||||
for variable in execution_data["workflow"]["workflow_variables"]:
|
for variable in execution_data["workflow"]["workflow_variables"]:
|
||||||
variablename = variable["name"].replace(" ", "_", -1).lower()
|
variablename = variable["name"].replace(" ", "_", -1).lower()
|
||||||
|
|
||||||
if variablename.lower() == actionname_lower:
|
if variablename.lower() == actionname_lower:
|
||||||
baseresult = variable["value"]
|
baseresult = variable["value"]
|
||||||
break
|
break
|
||||||
@@ -341,7 +350,7 @@ class AppBase:
|
|||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
print("TypeError wf variables: %s" % e)
|
print("TypeError wf variables: %s" % e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
print("BEFORE EXECUTION VAR")
|
print("BEFORE EXECUTION VAR")
|
||||||
if len(baseresult) == 0:
|
if len(baseresult) == 0:
|
||||||
try:
|
try:
|
||||||
@@ -377,23 +386,44 @@ class AppBase:
|
|||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
return baseresult
|
return baseresult
|
||||||
|
|
||||||
|
# This whole thing should be recursive.
|
||||||
try:
|
try:
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for value in parsersplit[1:]:
|
for value in parsersplit[1:]:
|
||||||
cnt += 1
|
cnt += 1
|
||||||
|
|
||||||
|
print("VALUE: %s" % value)
|
||||||
if value == "#":
|
if value == "#":
|
||||||
# FIXME - not recursive - should go deeper if there are more #
|
# FIXME - not recursive - should go deeper if there are more #
|
||||||
print("HANDLE RECURSIVE LOOP ")
|
print("HANDLE RECURSIVE LOOP OF %s" % basejson)
|
||||||
returnlist = []
|
returnlist = []
|
||||||
for innervalue in basejson:
|
try:
|
||||||
#print("Value: %s" % value[parsersplit[cnt+1]])
|
for innervalue in basejson:
|
||||||
returnlist.append(innervalue[parsersplit[cnt+1]])
|
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: ${[]}$
|
# Example format: ${[]}$
|
||||||
return "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist))
|
parseditem = "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist))
|
||||||
|
print("PARSED LOOP ITEM: %s" % parseditem)
|
||||||
|
return parseditem
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
print("BEFORE NORMAL VALUE: ", basejson, value)
|
||||||
|
if len(value) == 0:
|
||||||
|
return basejson
|
||||||
|
|
||||||
if isinstance(basejson[value], str):
|
if isinstance(basejson[value], str):
|
||||||
print(f"LOADING STRING '%s' AS JSON" % basejson[value])
|
print(f"LOADING STRING '%s' AS JSON" % basejson[value])
|
||||||
try:
|
try:
|
||||||
@@ -405,17 +435,19 @@ class AppBase:
|
|||||||
basejson = basejson[value]
|
basejson = basejson[value]
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
return "KeyError: %s" % e
|
print("Lower keyerror: %s" % e)
|
||||||
except IndexError as e:
|
return "KeyError: Couldn't find key: %s" % e
|
||||||
return "IndexError: %s" % e
|
|
||||||
|
|
||||||
return basejson
|
return basejson
|
||||||
|
|
||||||
|
# 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 = "$."
|
||||||
match = ".*([$]{1}([a-zA-Z0-9# _-]+\.?){1,})"
|
|
||||||
|
# Matches with space in the first part, but not in subsequent parts.
|
||||||
|
# JSON / yaml etc shouldn't have spaces in their fields anyway.
|
||||||
|
match = ".*?([$]{1}([a-zA-Z0-9 _-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,})"
|
||||||
|
|
||||||
# Regex to find all the things
|
# Regex to find all the things
|
||||||
if parameter["variant"] == "STATIC_VALUE":
|
if parameter["variant"] == "STATIC_VALUE":
|
||||||
@@ -424,7 +456,7 @@ class AppBase:
|
|||||||
#self.logger.debug(f"\n\nHandle static data with JSON: {data}\n\n")
|
#self.logger.debug(f"\n\nHandle static data with JSON: {data}\n\n")
|
||||||
#self.logger.info("STATIC PARSED: %s" % actualitem)
|
#self.logger.info("STATIC PARSED: %s" % actualitem)
|
||||||
if len(actualitem) > 0:
|
if len(actualitem) > 0:
|
||||||
print("ACTUAL: %s", actualitem)
|
print("ACTUAL: ", actualitem)
|
||||||
for replace in actualitem:
|
for replace in actualitem:
|
||||||
try:
|
try:
|
||||||
to_be_replaced = replace[0]
|
to_be_replaced = replace[0]
|
||||||
@@ -488,8 +520,10 @@ class AppBase:
|
|||||||
else:
|
else:
|
||||||
fullname += parameter["action_field"]
|
fullname += parameter["action_field"]
|
||||||
|
|
||||||
|
self.logger.info("PRE Fullname: %s" % fullname)
|
||||||
|
|
||||||
if parameter["value"].startswith(jsonparsevalue):
|
if parameter["value"].startswith(jsonparsevalue):
|
||||||
fullname += parameter["value"][2:]
|
fullname += parameter["value"][1:]
|
||||||
#else:
|
#else:
|
||||||
# fullname = "$%s" % parameter["action_field"]
|
# fullname = "$%s" % parameter["action_field"]
|
||||||
|
|
||||||
@@ -539,6 +573,22 @@ class AppBase:
|
|||||||
elif check.lower() == "contains":
|
elif check.lower() == "contains":
|
||||||
if destinationvalue.lower() in sourcevalue.lower():
|
if destinationvalue.lower() in sourcevalue.lower():
|
||||||
return True
|
return True
|
||||||
|
elif check.lower() == "larger than":
|
||||||
|
try:
|
||||||
|
if sourcevalue.isdigit() and destinationvalue.isdigit():
|
||||||
|
if int(sourcevalue) > int(destinationvalue):
|
||||||
|
return True
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger.error("Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e))
|
||||||
|
return False
|
||||||
|
elif check.lower() == "smaller than":
|
||||||
|
try:
|
||||||
|
if sourcevalue.isdigit() and destinationvalue.isdigit():
|
||||||
|
if int(sourcevalue) < int(destinationvalue):
|
||||||
|
return True
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger.error("Condition smaller than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e))
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
self.logger.info("Condition: can't handle %s yet. Setting to true" % check)
|
self.logger.info("Condition: can't handle %s yet. Setting to true" % check)
|
||||||
|
|
||||||
@@ -574,24 +624,18 @@ class AppBase:
|
|||||||
|
|
||||||
# Parse all values first here
|
# Parse all values first here
|
||||||
sourcevalue = condition["source"]["value"]
|
sourcevalue = condition["source"]["value"]
|
||||||
if condition["source"]["variant"] == "" or condition["source"]["variant"]== "STATIC_VALUE":
|
check, sourcevalue = parse_params(action, fullexecution, condition["source"])
|
||||||
condition["source"]["variant"]= "STATIC_VALUE"
|
if check:
|
||||||
else:
|
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
||||||
check, sourcevalue = parse_params(action, fullexecution, condition["source"])
|
|
||||||
if check:
|
|
||||||
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
|
||||||
|
|
||||||
|
|
||||||
#sourcevalue = sourcevalue.encode("utf-8")
|
#sourcevalue = sourcevalue.encode("utf-8")
|
||||||
sourcevalue = parse_wrapper_start(sourcevalue)
|
sourcevalue = parse_wrapper_start(sourcevalue)
|
||||||
destinationvalue = condition["destination"]["value"]
|
destinationvalue = condition["destination"]["value"]
|
||||||
|
|
||||||
if condition["destination"]["variant"]== "" or condition["destination"]["variant"]== "STATIC_VALUE":
|
check, destinationvalue = parse_params(action, fullexecution, condition["destination"])
|
||||||
condition["destination"]["variant"] = "STATIC_VALUE"
|
if check:
|
||||||
else:
|
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
||||||
check, destinationvalue = parse_params(action, fullexecution, condition["destination"])
|
|
||||||
if check:
|
|
||||||
return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)
|
|
||||||
|
|
||||||
#destinationvalue = destinationvalue.encode("utf-8")
|
#destinationvalue = destinationvalue.encode("utf-8")
|
||||||
destinationvalue = parse_wrapper_start(destinationvalue)
|
destinationvalue = parse_wrapper_start(destinationvalue)
|
||||||
@@ -634,7 +678,7 @@ class AppBase:
|
|||||||
if not branchcheck:
|
if not branchcheck:
|
||||||
self.logger.info("Failed one or more branch conditions.")
|
self.logger.info("Failed one or more branch conditions.")
|
||||||
action_result["result"] = tmpresult
|
action_result["result"] = tmpresult
|
||||||
action_result["status"] = "SKIPPED"
|
action_result["status"] = "FAILURE"
|
||||||
try:
|
try:
|
||||||
ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result)
|
ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result)
|
||||||
self.logger.info("Result: %d" % ret.status_code)
|
self.logger.info("Result: %d" % ret.status_code)
|
||||||
@@ -643,6 +687,7 @@ class AppBase:
|
|||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
self.logger.exception(e)
|
self.logger.exception(e)
|
||||||
|
|
||||||
|
print("\n\nRETURNING BECAUSE A BRANCH FAILED\n\n")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Replace name cus there might be issues
|
# Replace name cus there might be issues
|
||||||
@@ -699,8 +744,10 @@ class AppBase:
|
|||||||
raise "Value check error: %s" % Exception(check)
|
raise "Value check error: %s" % Exception(check)
|
||||||
|
|
||||||
# Custom format for ${name[0,1,2,...]}$
|
# Custom format for ${name[0,1,2,...]}$
|
||||||
|
#submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
|
||||||
submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
|
submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
|
||||||
actualitem = re.findall(submatch, value, re.MULTILINE)
|
actualitem = re.findall(submatch, value, re.MULTILINE)
|
||||||
|
print("Multicheck ", actualitem)
|
||||||
if len(actualitem) > 0:
|
if len(actualitem) > 0:
|
||||||
multiexecution = True
|
multiexecution = True
|
||||||
|
|
||||||
@@ -717,9 +764,12 @@ class AppBase:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
itemlist = json.loads(actualitem)
|
try:
|
||||||
if len(itemlist) > minlength:
|
itemlist = json.loads(actualitem)
|
||||||
minlength = len(itemlist)
|
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
|
replacements[to_be_replaced] = actualitem
|
||||||
|
|
||||||
@@ -729,7 +779,13 @@ class AppBase:
|
|||||||
for i in range(0, minlength):
|
for i in range(0, minlength):
|
||||||
tmpitem = json.loads(json.dumps(parameter["value"]))
|
tmpitem = json.loads(json.dumps(parameter["value"]))
|
||||||
for key, value in replacements.items():
|
for key, value in replacements.items():
|
||||||
replacement = json.loads(value)[i]
|
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)
|
tmpitem = tmpitem.replace(key, replacement, -1)
|
||||||
|
|
||||||
resultarray.append(tmpitem)
|
resultarray.append(tmpitem)
|
||||||
@@ -738,7 +794,7 @@ class AppBase:
|
|||||||
multi_parameters[parameter["name"]] = resultarray
|
multi_parameters[parameter["name"]] = resultarray
|
||||||
else:
|
else:
|
||||||
# Parses things like int(value)
|
# Parses things like int(value)
|
||||||
self.logger.info("Parsing wrapper data")
|
self.logger.info("Parsing wrapper data for %s" % value)
|
||||||
value = parse_wrapper_start(value)
|
value = parse_wrapper_start(value)
|
||||||
|
|
||||||
params[parameter["name"]] = value
|
params[parameter["name"]] = value
|
||||||
@@ -758,8 +814,9 @@ class AppBase:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres)
|
result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres)
|
||||||
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: ", newres)
|
||||||
else:
|
else:
|
||||||
print("APP_SDK DONE: Starting multi execution with", multi_parameters)
|
print("APP_SDK DONE: Starting MULTI execution with", multi_parameters)
|
||||||
# 1. Use number of executions based on longest array
|
# 1. Use number of executions based on longest array
|
||||||
# 2. Find the right value from the parsed multi_params
|
# 2. Find the right value from the parsed multi_params
|
||||||
|
|
||||||
@@ -782,7 +839,8 @@ class AppBase:
|
|||||||
|
|
||||||
#print("Running with params %s" % baseparams)
|
#print("Running with params %s" % baseparams)
|
||||||
ret = await func(**baseparams)
|
ret = await func(**baseparams)
|
||||||
print("Inner ret: %s" % ret)
|
ret = ret.replace("\"", "\\\"", -1)
|
||||||
|
print("Inner ret parsed: %s" % ret)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
results.append(json.loads(ret))
|
results.append(json.loads(ret))
|
||||||
@@ -802,8 +860,7 @@ class AppBase:
|
|||||||
print("Normal result?")
|
print("Normal result?")
|
||||||
result = results
|
result = results
|
||||||
|
|
||||||
print("RESULT: %s" % result)
|
print("RESULT: %s" % result)
|
||||||
|
|
||||||
action_result["status"] = "SUCCESS"
|
action_result["status"] = "SUCCESS"
|
||||||
action_result["result"] = str(result)
|
action_result["result"] = str(result)
|
||||||
if action_result["result"] == "":
|
if action_result["result"] == "":
|
||||||
@@ -812,10 +869,11 @@ class AppBase:
|
|||||||
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)
|
||||||
action_result["status"] = "FAILURE"
|
action_result["status"] = "FAILURE"
|
||||||
action_result["result"] = "TypeError: %s" % str(e)
|
action_result["result"] = "TypeError: %s" % str(e)
|
||||||
else:
|
else:
|
||||||
print("Not callable?")
|
print("Function %s doesn't exist?" % action["name"])
|
||||||
self.logger.error(f"App {self.__class__.__name__}.{action['name']} is not callable")
|
self.logger.error(f"App {self.__class__.__name__}.{action['name']} is not callable")
|
||||||
action_result["status"] = "FAILURE"
|
action_result["status"] = "FAILURE"
|
||||||
action_result["result"] = "Function %s is not callable." % actionname
|
action_result["result"] = "Function %s is not callable." % actionname
|
||||||
|
|||||||
+441
-241
@@ -362,6 +362,7 @@ type HookAction struct {
|
|||||||
|
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
Id string `json:"id" datastore:"id"`
|
Id string `json:"id" datastore:"id"`
|
||||||
|
Start string `json:"start" datastore:"start"`
|
||||||
Info Info `json:"info" datastore:"info"`
|
Info Info `json:"info" datastore:"info"`
|
||||||
Actions []HookAction `json:"actions" datastore:"actions"`
|
Actions []HookAction `json:"actions" datastore:"actions"`
|
||||||
Type string `json:"type" datastore:"type"`
|
Type string `json:"type" datastore:"type"`
|
||||||
@@ -812,8 +813,8 @@ func parseLoginParameters(resp http.ResponseWriter, request *http.Request) (logi
|
|||||||
// Removed for localhost
|
// Removed for localhost
|
||||||
func checkPasswordStrength(password string) error {
|
func checkPasswordStrength(password string) error {
|
||||||
// Check password strength here
|
// Check password strength here
|
||||||
if len(password) < 10 {
|
if len(password) < 3 {
|
||||||
return errors.New("Minimum password length is 10.")
|
return errors.New("Minimum password length is 3.")
|
||||||
}
|
}
|
||||||
|
|
||||||
//if len(password) > 128 {
|
//if len(password) > 128 {
|
||||||
@@ -838,6 +839,84 @@ func checkPasswordStrength(password string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteUser(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
cors := handleCors(resp, request)
|
||||||
|
if cors {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, userErr := handleApiAuthentication(resp, request)
|
||||||
|
if userErr != nil {
|
||||||
|
log.Printf("Api authentication failed in edit workflow: %s", userErr)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Role != "admin" {
|
||||||
|
log.Printf("Wrong user (%s) when deleting - must be admin", user.Username)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Must be admin"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
location := strings.Split(request.URL.String(), "/")
|
||||||
|
var userId string
|
||||||
|
if location[1] == "api" {
|
||||||
|
if len(location) <= 4 {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userId = location[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
if userId == user.Id {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Can't deactivate yourself"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
q := datastore.NewQuery("Users").Filter("id =", userId)
|
||||||
|
var users []User
|
||||||
|
_, err := dbclient.GetAll(ctx, q, &users)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting users apikey (deleteuser): %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Failed getting users for verification"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) != 1 {
|
||||||
|
log.Printf("Found too many users!")
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Backend error: too many or too few users with id %s: %d"}`, userId, len(users))))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert. No user deletion.
|
||||||
|
if users[0].Active {
|
||||||
|
users[0].Active = false
|
||||||
|
} else {
|
||||||
|
users[0].Active = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setUser(ctx, &users[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed swapping active for user %s (%s)", users[0].Username, users[0].Id)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Successfully inverted %s", users[0].Username)
|
||||||
|
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(`{"success": true}`))
|
||||||
|
}
|
||||||
|
|
||||||
// No more emails :)
|
// No more emails :)
|
||||||
func checkUsername(Username string) error {
|
func checkUsername(Username string) error {
|
||||||
// Stupid first check of email loool
|
// Stupid first check of email loool
|
||||||
@@ -845,8 +924,8 @@ func checkUsername(Username string) error {
|
|||||||
// return errors.New("Invalid Username")
|
// return errors.New("Invalid Username")
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if len(Username) < 4 {
|
if len(Username) < 3 {
|
||||||
return errors.New("Minimum Username length is 4")
|
return errors.New("Minimum Username length is 3")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -995,6 +1074,119 @@ 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 {
|
||||||
|
// Returns false if there is an issue
|
||||||
|
// Use this for register
|
||||||
|
err := checkPasswordStrength(password)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Bad password strength: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Bad Username strength: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
q := datastore.NewQuery("Users").Filter("Username =", username)
|
||||||
|
var users []User
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &users)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed getting user for registration: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) > 0 {
|
||||||
|
return errors.New(fmt.Sprintf("Username %s already exists", username))
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Wrong password for %s: %s", username, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newUser := new(User)
|
||||||
|
newUser.Username = username
|
||||||
|
newUser.Password = string(hashedPassword)
|
||||||
|
newUser.Verified = false
|
||||||
|
newUser.CreationTime = time.Now().Unix()
|
||||||
|
newUser.Active = true
|
||||||
|
newUser.Orgs = []string{"default"}
|
||||||
|
|
||||||
|
// FIXME - Remove this later
|
||||||
|
if role == "admin" {
|
||||||
|
newUser.Role = "admin"
|
||||||
|
newUser.Roles = []string{"admin"}
|
||||||
|
} else {
|
||||||
|
newUser.Role = "user"
|
||||||
|
newUser.Roles = []string{"user"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(apikey) > 0 {
|
||||||
|
newUser.ApiKey = apikey
|
||||||
|
}
|
||||||
|
|
||||||
|
// set limits
|
||||||
|
newUser.Limits.DailyApiUsage = 100
|
||||||
|
newUser.Limits.DailyWorkflowExecutions = 1000
|
||||||
|
newUser.Limits.DailyCloudExecutions = 100
|
||||||
|
newUser.Limits.DailyTriggers = 20
|
||||||
|
newUser.Limits.DailyMailUsage = 100
|
||||||
|
newUser.Limits.MaxTriggers = 10
|
||||||
|
newUser.Limits.MaxWorkflows = 10
|
||||||
|
|
||||||
|
// Set base info for the user
|
||||||
|
newUser.Executions.TotalApiUsage = 0
|
||||||
|
newUser.Executions.TotalWorkflowExecutions = 0
|
||||||
|
newUser.Executions.TotalAppExecutions = 0
|
||||||
|
newUser.Executions.TotalCloudExecutions = 0
|
||||||
|
newUser.Executions.TotalOnpremExecutions = 0
|
||||||
|
newUser.Executions.DailyApiUsage = 0
|
||||||
|
newUser.Executions.DailyWorkflowExecutions = 0
|
||||||
|
newUser.Executions.DailyAppExecutions = 0
|
||||||
|
newUser.Executions.DailyCloudExecutions = 0
|
||||||
|
newUser.Executions.DailyOnpremExecutions = 0
|
||||||
|
|
||||||
|
verifyToken := uuid.NewV4()
|
||||||
|
ID := uuid.NewV4()
|
||||||
|
newUser.Id = ID.String()
|
||||||
|
newUser.VerificationToken = verifyToken.String()
|
||||||
|
err = setUser(ctx, newUser)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error adding User %s: %s", username, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String())
|
||||||
|
const verifyMessage = `
|
||||||
|
Registration URL :)
|
||||||
|
|
||||||
|
%s
|
||||||
|
`
|
||||||
|
addr := newUser.Username
|
||||||
|
|
||||||
|
msg := &mail.Message{
|
||||||
|
Sender: "Shuffle <frikky@shuffler.io>",
|
||||||
|
To: []string{addr},
|
||||||
|
Subject: "Verify your username - Shuffle",
|
||||||
|
Body: fmt.Sprintf(verifyMessage, url),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(msg.Body)
|
||||||
|
if err := mail.Send(ctx, msg); err != nil {
|
||||||
|
log.Printf("Couldn't send email: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = increaseStatisticsField(ctx, "successful_register", username, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to increase total apps loaded stats: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleRegister(resp http.ResponseWriter, request *http.Request) {
|
func handleRegister(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -1030,128 +1222,21 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns false if there is an issue
|
role := "user"
|
||||||
// Use this for register
|
|
||||||
err = checkPasswordStrength(data.Password)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Bad password strength: %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkUsername(data.Username)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Bad Username strength: %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
q := datastore.NewQuery("Users").Filter("Username =", data.Username)
|
|
||||||
var users []User
|
|
||||||
_, err = dbclient.GetAll(ctx, q, &users)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed getting user for registration: %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting username"}`)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(users) > 0 {
|
|
||||||
log.Printf("Username %s exists and can't register", data.Username)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(data.Password), 8)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Wrong password for %s: %s", data.Username, err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
newUser := new(User)
|
|
||||||
newUser.Username = data.Username
|
|
||||||
newUser.Password = string(hashedPassword)
|
|
||||||
newUser.Verified = false
|
|
||||||
newUser.CreationTime = time.Now().Unix()
|
|
||||||
newUser.Active = true
|
|
||||||
newUser.Orgs = []string{"default"}
|
|
||||||
|
|
||||||
// FIXME - Remove this later
|
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
newUser.Role = "admin"
|
role = "admin"
|
||||||
newUser.Roles = []string{"admin"}
|
|
||||||
} else {
|
|
||||||
newUser.Role = "user"
|
|
||||||
newUser.Roles = []string{"user"}
|
|
||||||
}
|
}
|
||||||
|
err = createNewUser(data.Username, data.Password, role, "")
|
||||||
// set limits
|
|
||||||
newUser.Limits.DailyApiUsage = 100
|
|
||||||
newUser.Limits.DailyWorkflowExecutions = 1000
|
|
||||||
newUser.Limits.DailyCloudExecutions = 100
|
|
||||||
newUser.Limits.DailyTriggers = 20
|
|
||||||
newUser.Limits.DailyMailUsage = 100
|
|
||||||
newUser.Limits.MaxTriggers = 10
|
|
||||||
newUser.Limits.MaxWorkflows = 10
|
|
||||||
|
|
||||||
// Set base info for the user
|
|
||||||
newUser.Executions.TotalApiUsage = 0
|
|
||||||
newUser.Executions.TotalWorkflowExecutions = 0
|
|
||||||
newUser.Executions.TotalAppExecutions = 0
|
|
||||||
newUser.Executions.TotalCloudExecutions = 0
|
|
||||||
newUser.Executions.TotalOnpremExecutions = 0
|
|
||||||
newUser.Executions.DailyApiUsage = 0
|
|
||||||
newUser.Executions.DailyWorkflowExecutions = 0
|
|
||||||
newUser.Executions.DailyAppExecutions = 0
|
|
||||||
newUser.Executions.DailyCloudExecutions = 0
|
|
||||||
newUser.Executions.DailyOnpremExecutions = 0
|
|
||||||
|
|
||||||
addr := newUser.Username
|
|
||||||
|
|
||||||
verifyToken := uuid.NewV4()
|
|
||||||
ID := uuid.NewV4()
|
|
||||||
newUser.Id = ID.String()
|
|
||||||
newUser.VerificationToken = verifyToken.String()
|
|
||||||
err = setUser(ctx, newUser)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error adding User %s: %s", data.Username, err)
|
log.Printf("Failed registering user: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String())
|
|
||||||
const verifyMessage = `
|
|
||||||
Registration URL :)
|
|
||||||
|
|
||||||
%s
|
|
||||||
`
|
|
||||||
|
|
||||||
msg := &mail.Message{
|
|
||||||
Sender: "Shuffle <frikky@shuffler.io>",
|
|
||||||
To: []string{addr},
|
|
||||||
Subject: "Verify your username - Shuffle",
|
|
||||||
Body: fmt.Sprintf(verifyMessage, url),
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(msg.Body)
|
|
||||||
if err := mail.Send(ctx, msg); err != nil {
|
|
||||||
log.Printf("Couldn't send email: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(`{"success": true}`))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
log.Printf("%s Successfully registered.", data.Username)
|
log.Printf("%s Successfully registered.", data.Username)
|
||||||
|
|
||||||
err = increaseStatisticsField(ctx, "successful_register", data.Username, 1)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to increase total apps loaded stats: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCookie(request *http.Request) bool {
|
func handleCookie(request *http.Request) bool {
|
||||||
@@ -1279,62 +1364,195 @@ func generateApikey(ctx context.Context, userInfo User) (User, error) {
|
|||||||
return userInfo, nil
|
return userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleApiGeneration(resp http.ResponseWriter, request *http.Request) {
|
func handleUpdateUser(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := request.Cookie("session_token")
|
userInfo, err := handleApiAuthentication(resp, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("User doesn't have sessiontoken, on apigen: %s", err)
|
log.Printf("Api authentication failed in apigen: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed reading body")
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Missing field: user_id"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type newUserStruct struct {
|
||||||
|
Role string `json:"role"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
UserId string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sessionToken := c.Value
|
var t newUserStruct
|
||||||
session, err := getSession(ctx, sessionToken)
|
err = json.Unmarshal(body, &t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Session %#v doesn't exist (api gen): %s", session, err)
|
log.Printf("Failed unmarshaling userId: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unmarshaling. Missing field: user_id"}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get session first
|
if userInfo.Role != "admin" {
|
||||||
// Should basically never happen
|
log.Printf("%s tried to update user %s", userInfo.Username, t.UserId)
|
||||||
userInfo, err := getUser(ctx, session.Id)
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You need to be admin to change other users"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foundUser, err := getUser(ctx, t.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Username %s doesn't exist (apigen): %s", session.Username, err)
|
log.Printf("Can't find user %s (update user): %s", t.UserId, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete old apikey from cache
|
if t.Role != "admin" && t.Role != "user" {
|
||||||
//memcache.Delete(ctx, userInfo.ApiKey)
|
log.Printf("%s tried and failed to update user %s", userInfo.Username, t.UserId)
|
||||||
|
|
||||||
if session.Session != userInfo.Session {
|
|
||||||
log.Printf("Session %s is not the latest. %s", session.Username, err)
|
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can only change to role user and admin"}`)))
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
// Same user - can't edit yourself
|
||||||
|
if userInfo.Id == t.UserId {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't update the role of your own user"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Updated user %s from %s to %s", foundUser.Username, foundUser.Role, t.Role)
|
||||||
|
foundUser.Role = t.Role
|
||||||
|
foundUser.Roles = []string{t.Role}
|
||||||
}
|
}
|
||||||
|
|
||||||
newUserInfo, err := generateApikey(ctx, *userInfo)
|
if len(t.Username) > 0 {
|
||||||
|
q := datastore.NewQuery("Users").Filter("username =", t.Username)
|
||||||
|
var users []User
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &users)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": "Failed getting users when updating user"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, item := range users {
|
||||||
|
if item.Username == t.Username {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "User with username %s already exists"}`, t.Username)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foundUser.Username = t.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setUser(ctx, foundUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to generate apikey for user %s: %s", session.Username, err)
|
log.Printf("Error patching user %s: %s", foundUser.Username, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userInfo = &newUserInfo
|
|
||||||
|
|
||||||
//memcache.Delete(request.Context(), sessionToken)
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApiGeneration(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
log.Printf("APIGEN!")
|
||||||
|
cors := handleCors(resp, request)
|
||||||
|
if cors {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, err := handleApiAuthentication(resp, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Api authentication failed in apigen: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("APIKEY")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if request.Method == "GET" {
|
||||||
|
newUserInfo, err := generateApikey(ctx, userInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to generate apikey for user %s: %s", userInfo.Username, err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userInfo = newUserInfo
|
||||||
|
log.Printf("Updated apikey for user %s", userInfo.Username)
|
||||||
|
} else if request.Method == "POST" {
|
||||||
|
log.Printf("Handling post!")
|
||||||
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed reading body")
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Missing field: user_id"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type userId struct {
|
||||||
|
UserId string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var t userId
|
||||||
|
err = json.Unmarshal(body, &t)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed unmarshaling userId: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unmarshaling. Missing field: user_id"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if userInfo.Role != "admin" {
|
||||||
|
log.Printf("%s tried and failed to change apikey for %s", userInfo.Username, t.UserId)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You need to be admin to change others' apikey"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foundUser, err := getUser(ctx, t.UserId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Can't find user %s (apikey gen): %s", t.UserId, err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newUserInfo, err := generateApikey(ctx, *foundUser)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to generate apikey for user %s: %s", foundUser.Username, err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
foundUser = &newUserInfo
|
||||||
|
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, foundUser.Username, foundUser.Verified, foundUser.ApiKey)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Updated apikey for user %s", userInfo.Username)
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, userInfo.Username, userInfo.Verified, userInfo.ApiKey)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, userInfo.Username, userInfo.Verified, userInfo.ApiKey)))
|
||||||
}
|
}
|
||||||
@@ -1345,44 +1563,16 @@ func handleSettings(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := request.Cookie("session_token")
|
userInfo, err := handleApiAuthentication(resp, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("User doesn't have sessiontoken, on getsettings: %s", err)
|
log.Printf("Api authentication failed in apigen: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
sessionToken := c.Value
|
|
||||||
session, err := getSession(ctx, sessionToken)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Session %#v doesn't exist (settings): %s", session, err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get session first
|
|
||||||
// Should basically never happen
|
|
||||||
UserInfo, err := getUser(ctx, session.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Username %s doesn't exist (settings): %s", session.Username, err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//log.Printf("%s %s", session.Session, UserInfo.Session)
|
|
||||||
if session.Session != UserInfo.Session {
|
|
||||||
log.Printf("Session %s is not the latest. %s", session.Username, err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, UserInfo.Username, UserInfo.Verified, UserInfo.ApiKey)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "username": "%s", "verified": %t, "apikey": "%s"}`, userInfo.Username, userInfo.Verified, userInfo.ApiKey)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||||
@@ -1391,21 +1581,16 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should compare with local storage first
|
userInfo, err := handleApiAuthentication(resp, request)
|
||||||
c, err := request.Cookie("session_token")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("Api authentication failed in handleInfo: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - check memcache here
|
|
||||||
// Get the item from the memcache
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
session, err := getSession(ctx, userInfo.Session)
|
||||||
sessionToken := c.Value
|
|
||||||
//log.Printf("Found session %s", sessionToken)
|
|
||||||
session, err := getSession(ctx, sessionToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Session %#v doesn't exist: %s", session, err)
|
log.Printf("Session %#v doesn't exist: %s", session, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -1413,17 +1598,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get session first
|
// This is a long check to see if an inactive admin can access the site
|
||||||
// Should basically never happen
|
|
||||||
userInfo, err := getUser(ctx, session.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Username %s doesn't exist (info): %s", session.Username, err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a long check to see if an inactive admin can access the portal
|
|
||||||
if !userInfo.Active {
|
if !userInfo.Active {
|
||||||
if userInfo.Role == "admin" {
|
if userInfo.Role == "admin" {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -1478,7 +1653,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
//log.Printf("%s %s", session.Session, UserInfo.Session)
|
//log.Printf("%s %s", session.Session, UserInfo.Session)
|
||||||
if session.Session != userInfo.Session {
|
if session.Session != userInfo.Session {
|
||||||
log.Printf("Session %s is not the latest. %s", session.Username, err)
|
log.Printf("Session %s is not the same as %s for %s. %s", userInfo.Session, session.Session, userInfo.Username, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
||||||
return
|
return
|
||||||
@@ -1765,7 +1940,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 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 multiple users with the same username: %s"}`, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2059,7 +2234,8 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
q := datastore.NewQuery("Users").Filter("Username =", strings.ToLower(data.Username))
|
log.Printf("Username: %s", data.Username)
|
||||||
|
q := datastore.NewQuery("Users").Filter("Username =", data.Username)
|
||||||
var users []User
|
var users []User
|
||||||
_, err = dbclient.GetAll(ctx, q, &users)
|
_, err = dbclient.GetAll(ctx, q, &users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2072,7 +2248,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 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 multiple users with the same username: %s"}`, data.Username)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Found %d users with the same username: %s"}`, len(users), data.Username)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2889,7 +3065,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
hookId = hookId[8:len(hookId)]
|
hookId = hookId[8:len(hookId)]
|
||||||
|
|
||||||
log.Printf("HookID: %s", hookId)
|
//log.Printf("HookID: %s", hookId)
|
||||||
hook, err := getHook(ctx, hookId)
|
hook, err := getHook(ctx, hookId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed getting hook: %s", err)
|
log.Printf("Failed getting hook: %s", err)
|
||||||
@@ -2898,31 +3074,60 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("HOOK FOUND: %#v", hook)
|
//log.Printf("HOOK FOUND: %#v", hook)
|
||||||
// Execute the workflow
|
// Execute the workflow
|
||||||
//executeWorkflow(resp, request)
|
//executeWorkflow(resp, request)
|
||||||
|
|
||||||
//resp.WriteHeader(200)
|
//resp.WriteHeader(200)
|
||||||
//resp.Write([]byte(`{"success": true}`))
|
//resp.Write([]byte(`{"success": true}`))
|
||||||
if hook.Status == "stopped" {
|
if hook.Status == "stopped" {
|
||||||
|
log.Printf("Not running because hook status is stopped")
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Click start to start it"}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Click start to start it"}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hook.Workflows) == 0 {
|
if len(hook.Workflows) == 0 {
|
||||||
|
log.Printf("Not running because hook isn't connected to any workflows")
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range hook.Workflows {
|
for _, item := range hook.Workflows {
|
||||||
log.Printf("Running for workflow: %s", item)
|
log.Printf("Running for workflow %s with startnode %s", item, hook.Start)
|
||||||
workflow := Workflow{
|
workflow := Workflow{
|
||||||
ID: "",
|
ID: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
workflowExecution, executionResp, err := handleExecution(item, workflow, request)
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Body data error: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedBody := string(body)
|
||||||
|
parsedBody = strings.Replace(parsedBody, "\"", "\\\"", -1)
|
||||||
|
if len(parsedBody) > 0 {
|
||||||
|
if string(parsedBody[0]) == `"` && string(parsedBody[len(parsedBody)-1]) == "\"" {
|
||||||
|
parsedBody = parsedBody[1 : len(parsedBody)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_source": "webhook", "execution_argument": "%s"}`, hook.Start, string(parsedBody))
|
||||||
|
if len(hook.Start) == 0 {
|
||||||
|
log.Printf("No start node for hook %s - running with workflow default.", hook.Id)
|
||||||
|
bodyWrapper = string(parsedBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
newRequest := &http.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)),
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1)
|
err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1)
|
||||||
@@ -2961,6 +3166,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Workflow string `json:"workflow"`
|
Workflow string `json:"workflow"`
|
||||||
|
Start string `json:"start"`
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(request.Body)
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
@@ -2971,7 +3177,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Data: %s", string(body))
|
log.Printf("Data: %s", string(body))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
var requestdata requestData
|
var requestdata requestData
|
||||||
@@ -3021,6 +3227,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
hook := Hook{
|
hook := Hook{
|
||||||
Id: newId,
|
Id: newId,
|
||||||
|
Start: requestdata.Start,
|
||||||
Workflows: []string{requestdata.Workflow},
|
Workflows: []string{requestdata.Workflow},
|
||||||
Info: Info{
|
Info: Info{
|
||||||
Name: requestdata.Name,
|
Name: requestdata.Name,
|
||||||
@@ -3041,33 +3248,6 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
Running: false,
|
Running: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Hello")
|
|
||||||
|
|
||||||
// FIXME: Add cloud function execution?
|
|
||||||
//b, err := json.Marshal(hook)
|
|
||||||
//if err != nil {
|
|
||||||
// log.Printf("Failed marshalling: %s", err)
|
|
||||||
// resp.WriteHeader(401)
|
|
||||||
// resp.Write([]byte(`{"success": false}`))
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
|
|
||||||
//environmentVariables := map[string]string{
|
|
||||||
// "FUNCTION_APIKEY": user.ApiKey,
|
|
||||||
// "CALLBACKURL": "https://shuffler.io",
|
|
||||||
// "HOOKID": hook.Id,
|
|
||||||
//}
|
|
||||||
|
|
||||||
//applocation := fmt.Sprintf("gs://%s/triggers/webhook.zip", bucketName)
|
|
||||||
//hookname := fmt.Sprintf("webhook_%s", hook.Id)
|
|
||||||
//err = deployWebhookFunction(ctx, hookname, defaultLocation, applocation, environmentVariables)
|
|
||||||
//if err != nil {
|
|
||||||
// log.Printf("Error deploying hook: %s", err)
|
|
||||||
// resp.WriteHeader(401)
|
|
||||||
// resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Issue with starting hook. Please wait a second and try again"}`)))
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
|
|
||||||
hook.Status = "running"
|
hook.Status = "running"
|
||||||
hook.Running = true
|
hook.Running = true
|
||||||
err = setHook(ctx, hook)
|
err = setHook(ctx, hook)
|
||||||
@@ -3083,7 +3263,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("Failed to increase total workflows: %s", err)
|
log.Printf("Failed to increase total workflows: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Generating new hook")
|
log.Println("Set up a new hook")
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(`{"success": true}`))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
@@ -5966,18 +6146,17 @@ func runInit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix active users etc
|
// Fix active users etc
|
||||||
log.Printf("Reformatting users")
|
|
||||||
q := datastore.NewQuery("Users").Filter("active =", true)
|
q := datastore.NewQuery("Users").Filter("active =", true)
|
||||||
var users []User
|
var activeusers []User
|
||||||
_, err = dbclient.GetAll(ctx, q, &users)
|
_, err = dbclient.GetAll(ctx, q, &activeusers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting users apikey (runinit): %s", err)
|
log.Printf("Error getting users during init: %s", err)
|
||||||
} else {
|
} else {
|
||||||
if len(users) == 0 {
|
q := datastore.NewQuery("Users")
|
||||||
|
var users []User
|
||||||
|
_, err := dbclient.GetAll(ctx, q, &users)
|
||||||
|
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")
|
||||||
q := datastore.NewQuery("Users")
|
|
||||||
var users []User
|
|
||||||
_, err := dbclient.GetAll(ctx, q, &users)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
user.Active = true
|
user.Active = true
|
||||||
@@ -6006,6 +6185,24 @@ func runInit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if len(users) == 0 {
|
||||||
|
log.Printf("Trying to set up user based on environments SHUFFLE_DEFAULT_USERNAME & SHUFFLE_DEFAULT_PASSWORD")
|
||||||
|
username := os.Getenv("SHUFFLE_DEFAULT_USERNAME")
|
||||||
|
password := os.Getenv("SHUFFLE_DEFAULT_PASSWORD")
|
||||||
|
if len(username) == 0 || len(password) == 0 {
|
||||||
|
log.Printf("SHUFFLE_DEFAULT_USERNAME and SHUFFLE_DEFAULT_PASSWORD not defined as environments. Running without default user.")
|
||||||
|
} else {
|
||||||
|
apikey := os.Getenv("SHUFFLE_DEFAULT_APIKEY")
|
||||||
|
err = createNewUser(username, password, "admin", apikey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to create default user %s: %s", username, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully created user %s", username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//log.Printf("Found %d users.", len(users))
|
||||||
|
//log.Printf(users[0].Username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6119,7 +6316,7 @@ func runInit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
_, err = git.Clone(storer, fs, cloneOptions)
|
_, err = git.Clone(storer, fs, cloneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed loading repo %s into memory: %s", err)
|
log.Printf("Failed loading repo %s into memory: %s", apis, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Finished git clone. Looking for updates to the repo.")
|
log.Printf("Finished git clone. Looking for updates to the repo.")
|
||||||
dir, err := fs.ReadDir("")
|
dir, err := fs.ReadDir("")
|
||||||
@@ -6157,6 +6354,7 @@ func init() {
|
|||||||
r.HandleFunc("/functions/outlook/getFolders", handleGetOutlookFolders).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/login", handleLogin).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/login", handleLogin).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/logout", handleLogout).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/logout", handleLogout).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/register", handleRegister).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/register", handleRegister).Methods("POST", "OPTIONS")
|
||||||
@@ -6164,8 +6362,10 @@ func init() {
|
|||||||
r.HandleFunc("/api/v1/users/getusers", handleGetUsers).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/getusers", handleGetUsers).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/getsettings", handleSettings).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/getsettings", handleSettings).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/generateapikey", handleApiGeneration).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/updateuser", handleUpdateUser).Methods("PUT", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/{user}", deleteUser).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/users/{user}", deleteUser).Methods("DELETE", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/users/passwordchange", handlePasswordChange).Methods("POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/users", handleGetUsers).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
// General - duplicates and old.
|
// General - duplicates and old.
|
||||||
r.HandleFunc("/api/v1/login", handleLogin).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/login", handleLogin).Methods("POST", "OPTIONS")
|
||||||
@@ -6175,7 +6375,7 @@ func init() {
|
|||||||
r.HandleFunc("/api/v1/getusers", handleGetUsers).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/getusers", handleGetUsers).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/getsettings", handleSettings).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/getsettings", handleSettings).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/generateapikey", handleApiGeneration).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/generateapikey", handleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/getenvironments", handleGetEnvironments).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/getenvironments", handleGetEnvironments).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/setenvironments", handleSetEnvironments).Methods("PUT", "OPTIONS")
|
r.HandleFunc("/api/v1/setenvironments", handleSetEnvironments).Methods("PUT", "OPTIONS")
|
||||||
|
|||||||
+40
-87
@@ -61,6 +61,7 @@ var shuffleTestPath = "./shuffle-test-258209-5a2e8d7e508a.json"
|
|||||||
type ExecutionRequest struct {
|
type ExecutionRequest struct {
|
||||||
ExecutionId string `json:"execution_id"`
|
ExecutionId string `json:"execution_id"`
|
||||||
ExecutionArgument string `json:"execution_argument"`
|
ExecutionArgument string `json:"execution_argument"`
|
||||||
|
ExecutionSource string `json:"execution_source"`
|
||||||
WorkflowId string `json:"workflow_id"`
|
WorkflowId string `json:"workflow_id"`
|
||||||
Environments []string `json:"environments"`
|
Environments []string `json:"environments"`
|
||||||
Authorization string `json:"authorization"`
|
Authorization string `json:"authorization"`
|
||||||
@@ -153,6 +154,7 @@ type WorkflowExecution struct {
|
|||||||
Start string `json:"start" datastore:"start"`
|
Start string `json:"start" datastore:"start"`
|
||||||
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"`
|
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"`
|
||||||
ExecutionId string `json:"execution_id" datastore:"execution_id"`
|
ExecutionId string `json:"execution_id" datastore:"execution_id"`
|
||||||
|
ExecutionSource string `json:"execution_source" datastore:"execution_source"`
|
||||||
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
|
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
|
||||||
LastNode string `json:"last_node" datastore:"last_node"`
|
LastNode string `json:"last_node" datastore:"last_node"`
|
||||||
Authorization string `json:"authorization" datastore:"authorization"`
|
Authorization string `json:"authorization" datastore:"authorization"`
|
||||||
@@ -449,9 +451,9 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
|
|||||||
// FIXME:
|
// FIXME:
|
||||||
// This may run multiple places if multiple servers,
|
// This may run multiple places if multiple servers,
|
||||||
// but that's a future problem
|
// but that's a future problem
|
||||||
log.Printf("BODY: %s", string(body))
|
//log.Printf("BODY: %s", string(body))
|
||||||
parsedArgument := strings.Replace(string(body), "\"", "\\\"", -1)
|
parsedArgument := strings.Replace(string(body), "\"", "\\\"", -1)
|
||||||
bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_argument": "%s"}`, startNode, parsedArgument)
|
bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_source": "schedule", "execution_argument": "%s"}`, startNode, parsedArgument)
|
||||||
log.Printf("WRAPPER BODY: \n%s", bodyWrapper)
|
log.Printf("WRAPPER BODY: \n%s", bodyWrapper)
|
||||||
job := func() {
|
job := func() {
|
||||||
request := &http.Request{
|
request := &http.Request{
|
||||||
@@ -1205,6 +1207,13 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
// Adds the Testing app if it's a new workflow
|
// Adds the Testing app if it's a new workflow
|
||||||
workflowapps, err := getAllWorkflowApps(ctx)
|
workflowapps, err := getAllWorkflowApps(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
// FIXME: Add real env
|
||||||
|
//q := datastore.NewQuery("Environments").Limit(1)
|
||||||
|
//count, err := dbclient.Get(ctx, q)
|
||||||
|
//envName := "Shuffle"
|
||||||
|
//if err == nil {
|
||||||
|
//}
|
||||||
|
|
||||||
for _, item := range workflowapps {
|
for _, item := range workflowapps {
|
||||||
if item.Name == "Testing" && item.AppVersion == "1.0.0" {
|
if item.Name == "Testing" && item.AppVersion == "1.0.0" {
|
||||||
nodeId := "40447f30-fa44-4a4f-a133-4ee710368737"
|
nodeId := "40447f30-fa44-4a4f-a133-4ee710368737"
|
||||||
@@ -1241,7 +1250,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
workflow.Actions = newActions
|
workflow.Actions = newActions
|
||||||
workflow.IsValid = true
|
workflow.IsValid = true
|
||||||
workflow.Configuration.ExitOnError = true
|
workflow.Configuration.ExitOnError = false
|
||||||
|
|
||||||
workflowjson, err := json.Marshal(workflow)
|
workflowjson, err := json.Marshal(workflow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1564,6 +1573,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
// doesn't check sharing=true
|
// doesn't check sharing=true
|
||||||
// Have to do it like this to add the user's apps
|
// Have to do it like this to add the user's apps
|
||||||
log.Println("Apps set starting")
|
log.Println("Apps set starting")
|
||||||
|
//log.Printf("EXIT ON ERROR: %#v", workflow.Configuration.ExitOnError)
|
||||||
workflowApps := []WorkflowApp{}
|
workflowApps := []WorkflowApp{}
|
||||||
//memcacheName = "all_apps"
|
//memcacheName = "all_apps"
|
||||||
//if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss {
|
//if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss {
|
||||||
@@ -1627,10 +1637,10 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Has to NOT be generated
|
// Has to NOT be generated
|
||||||
//if app.Name == action.AppName && app.AppVersion == action.AppVersion {
|
if app.Name == action.AppName && app.AppVersion == action.AppVersion {
|
||||||
// curapp = app
|
curapp = app
|
||||||
// break
|
break
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if the whole app is valid
|
// Check to see if the whole app is valid
|
||||||
@@ -1976,7 +1986,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
var execution ExecutionRequest
|
var execution ExecutionRequest
|
||||||
err = json.Unmarshal(body, &execution)
|
err = json.Unmarshal(body, &execution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Printf("Failed execution POST unmarshaling - still continue: %s", err)
|
log.Printf("Failed execution POST unmarshaling - continuing anyway: %s", err)
|
||||||
//return WorkflowExecution{}, "", err
|
//return WorkflowExecution{}, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1985,10 +1995,15 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - this should have "execution_argument" from executeWorkflow frontend
|
// FIXME - this should have "execution_argument" from executeWorkflow frontend
|
||||||
|
//log.Printf("EXEC: %#v", execution)
|
||||||
if len(execution.ExecutionArgument) > 0 {
|
if len(execution.ExecutionArgument) > 0 {
|
||||||
workflowExecution.ExecutionArgument = execution.ExecutionArgument
|
workflowExecution.ExecutionArgument = execution.ExecutionArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(execution.ExecutionSource) > 0 {
|
||||||
|
workflowExecution.ExecutionSource = execution.ExecutionSource
|
||||||
|
}
|
||||||
|
|
||||||
//log.Printf("Execution data: %#v", execution)
|
//log.Printf("Execution data: %#v", execution)
|
||||||
if len(execution.Start) == 36 {
|
if len(execution.Start) == 36 {
|
||||||
log.Printf("SHOULD START ON NODE %s", execution.Start)
|
log.Printf("SHOULD START ON NODE %s", execution.Start)
|
||||||
@@ -2007,7 +2022,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
}
|
}
|
||||||
} else if len(execution.Start) > 0 {
|
} else if len(execution.Start) > 0 {
|
||||||
|
|
||||||
log.Printf("START ACTION %s IS WRONG ID LENGTH %d!", len(execution.Start))
|
log.Printf("START ACTION %s IS WRONG ID LENGTH %d!", execution.Start, len(execution.Start))
|
||||||
return WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", execution.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", execution.Start))
|
return WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", execution.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", execution.Start))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2136,6 +2151,13 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
workflowExecution.Status = "EXECUTING"
|
workflowExecution.Status = "EXECUTING"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(workflowExecution.ExecutionSource) == 0 {
|
||||||
|
log.Printf("No execution source specified. Setting to default")
|
||||||
|
workflowExecution.ExecutionSource = "default"
|
||||||
|
} else {
|
||||||
|
log.Printf("Execution source is %s for execution ID %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId)
|
||||||
|
}
|
||||||
|
|
||||||
workflowExecution.ExecutionVariables = workflow.ExecutionVariables
|
workflowExecution.ExecutionVariables = workflow.ExecutionVariables
|
||||||
// Local authorization for this single workflow used in workers.
|
// Local authorization for this single workflow used in workers.
|
||||||
|
|
||||||
@@ -2158,11 +2180,15 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
childNodes := findChildNodes(workflowExecution, workflowExecution.Start)
|
childNodes := findChildNodes(workflowExecution, workflowExecution.Start)
|
||||||
|
|
||||||
topic := "workflows"
|
topic := "workflows"
|
||||||
|
startFound := false
|
||||||
// FIXME - remove this?
|
// FIXME - remove this?
|
||||||
newActions := []Action{}
|
newActions := []Action{}
|
||||||
defaultResults := []ActionResult{}
|
defaultResults := []ActionResult{}
|
||||||
for _, action := range workflowExecution.Workflow.Actions {
|
for _, action := range workflowExecution.Workflow.Actions {
|
||||||
action.LargeImage = ""
|
action.LargeImage = ""
|
||||||
|
if action.ID == workflowExecution.Start {
|
||||||
|
startFound = true
|
||||||
|
}
|
||||||
//log.Println(action.Environment)
|
//log.Println(action.Environment)
|
||||||
|
|
||||||
if action.Environment == "" {
|
if action.Environment == "" {
|
||||||
@@ -2202,6 +2228,11 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !startFound {
|
||||||
|
log.Printf("Startnode %s doesn't exist!", workflowExecution.Start)
|
||||||
|
return WorkflowExecution{}, fmt.Sprintf("Workflow action %s doesn't exist in workflow", workflowExecution.Start), errors.New(fmt.Sprintf("Workflow start node %s doesn't exist. Exiting!", workflowExecution.Start))
|
||||||
|
}
|
||||||
|
|
||||||
// Verification for execution environments
|
// Verification for execution environments
|
||||||
workflowExecution.Results = defaultResults
|
workflowExecution.Results = defaultResults
|
||||||
workflowExecution.Workflow.Actions = newActions
|
workflowExecution.Workflow.Actions = newActions
|
||||||
@@ -2929,84 +2960,6 @@ func setWorkflow(ctx context.Context, workflow Workflow, id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteUser(resp http.ResponseWriter, request *http.Request) {
|
|
||||||
cors := handleCors(resp, request)
|
|
||||||
if cors {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, userErr := handleApiAuthentication(resp, request)
|
|
||||||
if userErr != nil {
|
|
||||||
log.Printf("Api authentication failed in edit workflow: %s", userErr)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Role != "admin" {
|
|
||||||
log.Printf("Wrong user (%s) when deleting - must be admin", user.Username)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Must be admin"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
location := strings.Split(request.URL.String(), "/")
|
|
||||||
var userId string
|
|
||||||
if location[1] == "api" {
|
|
||||||
if len(location) <= 4 {
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userId = location[4]
|
|
||||||
}
|
|
||||||
|
|
||||||
if userId == user.Id {
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Can't deactivate yourself"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
q := datastore.NewQuery("Users").Filter("id =", userId)
|
|
||||||
var users []User
|
|
||||||
_, err := dbclient.GetAll(ctx, q, &users)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error getting users apikey (deleteuser): %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Failed getting users for verification"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(users) != 1 {
|
|
||||||
log.Printf("Found too many users!")
|
|
||||||
resp.WriteHeader(500)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Backend error: too many users"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invert. No user deletion.
|
|
||||||
if users[0].Active {
|
|
||||||
users[0].Active = false
|
|
||||||
} else {
|
|
||||||
users[0].Active = true
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setUser(ctx, &users[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed swapping active for user %s (%s)", users[0].Username, users[0].Id)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true"}`)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Successfully inverted %s", users[0].Username)
|
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
|
||||||
resp.Write([]byte(`{"success": true}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|||||||
+69
-21
File diff suppressed because one or more lines are too long
@@ -1 +1,3 @@
|
|||||||
curl -H "Content-Type: application/json" localhost:5001/api/v1/triggers/9e845679-5843-4959-a76c-a6d664e9df35 -H "Authorization: Bearer 377469e8-dd5d-4521-8d9e-416d8d2f6fd4"
|
# curl -H "Content-Type: application/json" localhost:5001/api/v1/triggers/9e845679-5843-4959-a76c-a6d664e9df35 -H "Authorization: Bearer 377469e8-dd5d-4521-8d9e-416d8d2f6fd4"
|
||||||
|
|
||||||
|
curl -XPOST http://localhost:5001/api/v1/hooks/webhook_1b968d49-2d78-4132-bf91-0f3a1f6a79f3 -d '{}'
|
||||||
|
|||||||
+5
-2
@@ -27,10 +27,13 @@ services:
|
|||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ${APP_HOTLOAD_LOCATION}:/shuffle-apps
|
- ${APP_HOTLOAD_LOCATION}:/shuffle-apps
|
||||||
environment:
|
environment:
|
||||||
- ORG_ID=${ORG_ID}
|
|
||||||
- DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
- DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
||||||
- APP_DOWNLOAD_LOCATION=${APP_DOWNLOAD_LOCATION}
|
|
||||||
- APP_HOTLOAD_FOLDER=/shuffle-apps
|
- APP_HOTLOAD_FOLDER=/shuffle-apps
|
||||||
|
- ORG_ID=${ORG_ID}
|
||||||
|
- APP_DOWNLOAD_LOCATION=${APP_DOWNLOAD_LOCATION}
|
||||||
|
- SHUFFLE_DEFAULT_USERNAME=${SHUFFLE_DEFAULT_USERNAME}
|
||||||
|
- SHUFFLE_DEFAULT_PASSWORD=${SHUFFLE_DEFAULT_PASSWORD}
|
||||||
|
- SHUFFLE_DEFAULT_APIKEY=${SHUFFLE_DEFAULT_APIKEY}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
|
|||||||
+77
-17
@@ -2,6 +2,8 @@ import React, { useEffect} from 'react';
|
|||||||
|
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import Paper from '@material-ui/core/Paper';
|
import Paper from '@material-ui/core/Paper';
|
||||||
|
import Select from '@material-ui/core/Select';
|
||||||
|
import MenuItem from '@material-ui/core/MenuItem';
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import Divider from '@material-ui/core/Divider';
|
import Divider from '@material-ui/core/Divider';
|
||||||
import TextField from '@material-ui/core/TextField';
|
import TextField from '@material-ui/core/TextField';
|
||||||
@@ -70,7 +72,7 @@ const Admin = (props) => {
|
|||||||
|
|
||||||
const onPasswordChange = () => {
|
const onPasswordChange = () => {
|
||||||
const data = {"username": selectedUser.username, "newpassword": newPassword}
|
const data = {"username": selectedUser.username, "newpassword": newPassword}
|
||||||
const url = globalUrl+'/api/v1/passwordchange';
|
const url = globalUrl+'/api/v1/users/passwordchange';
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -92,7 +94,7 @@ const Admin = (props) => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
alert.error("Err: ", error.toString())
|
alert.error("Err: "+error.toString())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +135,7 @@ const Admin = (props) => {
|
|||||||
// Just use this one?
|
// Just use this one?
|
||||||
var data = {"username": data.Username, "password": data.Password}
|
var data = {"username": data.Username, "password": data.Password}
|
||||||
var baseurl = globalUrl
|
var baseurl = globalUrl
|
||||||
const url = baseurl+'/api/v1/register';
|
const url = baseurl+'/api/v1/users/register';
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
@@ -294,6 +296,7 @@ const Admin = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
|
console.log(responseJson)
|
||||||
setUsers(responseJson)
|
setUsers(responseJson)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -319,14 +322,54 @@ const Admin = (props) => {
|
|||||||
modalUser[field] = value
|
modalUser[field] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateApikey = () => {
|
const setUser = (userId, field, value) => {
|
||||||
fetch(globalUrl+"/api/v1/generateapikey", {
|
const data = {"user_id": userId}
|
||||||
method: 'GET',
|
data[field] = value
|
||||||
|
console.log("DATA: ", data)
|
||||||
|
|
||||||
|
fetch(globalUrl+"/api/v1/users/updateuser", {
|
||||||
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: "include",
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
|
||||||
|
} else {
|
||||||
|
getUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (!responseJson.success && responseJson.reason !== undefined) {
|
||||||
|
alert.error("Failed setting user: "+responseJson.reason)
|
||||||
|
} else {
|
||||||
|
alert.success("Set the user field "+field+" to "+value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const generateApikey = (userId) => {
|
||||||
|
const data = {"user_id": userId}
|
||||||
|
|
||||||
|
fetch(globalUrl+"/api/v1/generateapikey", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
@@ -407,7 +450,7 @@ const Admin = (props) => {
|
|||||||
style={{}}
|
style={{}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => generateApikey(selectedUser)}
|
onClick={() => generateApikey(selectedUser.id)}
|
||||||
>
|
>
|
||||||
Get new API key
|
Get new API key
|
||||||
</Button>
|
</Button>
|
||||||
@@ -540,10 +583,6 @@ const Admin = (props) => {
|
|||||||
primary="API key"
|
primary="API key"
|
||||||
style={{minWidth: 350, maxWidth: 350, overflow: "hidden"}}
|
style={{minWidth: 350, maxWidth: 350, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
|
||||||
primary="Password"
|
|
||||||
style={{minWidth: 180, maxWidth: 180}}
|
|
||||||
/>
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Role"
|
primary="Role"
|
||||||
style={{minWidth: 150, maxWidth: 150}}
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
@@ -569,11 +608,32 @@ const Admin = (props) => {
|
|||||||
style={{maxWidth: 350, minWidth: 350,}}
|
style={{maxWidth: 350, minWidth: 350,}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="**************"
|
primary=
|
||||||
style={{minWidth: 180, maxWidth: 180}}
|
<Select
|
||||||
/>
|
PaperProps={{
|
||||||
<ListItemText
|
style: {
|
||||||
primary={data.role}
|
}
|
||||||
|
}}
|
||||||
|
SelectDisplayProps={{
|
||||||
|
style: {
|
||||||
|
marginLeft: 10,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={data.role}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e) => {
|
||||||
|
console.log("VALUE: ", e.target.value)
|
||||||
|
setUser(data.id, "role", e.target.value)
|
||||||
|
}}
|
||||||
|
style={{backgroundColor: surfaceColor, color: "white", height: "50px"}}
|
||||||
|
>
|
||||||
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={"admin"}>
|
||||||
|
Admin
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={"user"}>
|
||||||
|
User
|
||||||
|
</MenuItem>
|
||||||
|
</Select>
|
||||||
style={{minWidth: 150, maxWidth: 150}}
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
|
|||||||
+284
-242
File diff suppressed because one or more lines are too long
+9
-2
@@ -59,7 +59,14 @@ const theme = createMuiTheme({
|
|||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
useNextVariants: true
|
useNextVariants: true
|
||||||
}
|
},
|
||||||
|
overrides: {
|
||||||
|
MuiMenu: {
|
||||||
|
list: {
|
||||||
|
backgroundColor: inputColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +91,7 @@ const App = (message, props) => {
|
|||||||
|
|
||||||
const checkLogin = () => {
|
const checkLogin = () => {
|
||||||
var baseurl = globalUrl
|
var baseurl = globalUrl
|
||||||
fetch(baseurl+"/api/v1/getinfo", {
|
fetch(baseurl+"/api/v1/users/getinfo", {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -448,6 +448,12 @@ const Apps = (props) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
|
{selectedAction.description !== undefined && selectedAction.description !== null ?
|
||||||
|
<div>
|
||||||
|
<b>Action Description</b><div/>
|
||||||
|
{selectedAction.description}
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ const LoginDialog = props => {
|
|||||||
var data = {"username": username, "password": password}
|
var data = {"username": username, "password": password}
|
||||||
var baseurl = globalUrl
|
var baseurl = globalUrl
|
||||||
if (register) {
|
if (register) {
|
||||||
var url = baseurl+'/api/v1/login';
|
var url = baseurl+'/api/v1/users/login';
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -123,7 +123,7 @@ const LoginDialog = props => {
|
|||||||
setLoginInfo("Error in userdata: " + error)
|
setLoginInfo("Error in userdata: " + error)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
url = baseurl+'/api/v1/register';
|
url = baseurl+'/api/v1/users/register';
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
|
|||||||
+13
-14
@@ -89,7 +89,6 @@ const Workflows = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
console.log(responseJson)
|
|
||||||
setSelectedExecution({})
|
setSelectedExecution({})
|
||||||
setWorkflowExecutions([])
|
setWorkflowExecutions([])
|
||||||
|
|
||||||
@@ -384,21 +383,21 @@ const Workflows = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||||
setModalOpen(true)
|
setModalOpen(true)
|
||||||
setEditingWorkflow(data)
|
setEditingWorkflow(data)
|
||||||
setNewWorkflowName(data.name)
|
setNewWorkflowName(data.name)
|
||||||
setNewWorkflowDescription(data.description)
|
setNewWorkflowDescription(data.description)
|
||||||
}} key={"change"}>{"Change name"}</MenuItem>
|
}} key={"change"}>{"Change name"}</MenuItem>
|
||||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||||
copyWorkflow(data)
|
copyWorkflow(data)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}} key={"copy"}>{"Copy"}</MenuItem>
|
}} key={"copy"}>{"Copy"}</MenuItem>
|
||||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||||
exportWorkflow(data)
|
exportWorkflow(data)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}} key={"export"}>{"Export"}</MenuItem>
|
}} key={"export"}>{"Export"}</MenuItem>
|
||||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||||
deleteWorkflow(data.id)
|
deleteWorkflow(data.id)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}} key={"delete"}>{"Delete"}</MenuItem>
|
}} key={"delete"}>{"Delete"}</MenuItem>
|
||||||
@@ -807,7 +806,7 @@ const Workflows = (props) => {
|
|||||||
const ret = setNewWorkflow(data.name, data.description, data, false)
|
const ret = setNewWorkflow(data.name, data.description, data, false)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response !== undefined) {
|
if (response !== undefined) {
|
||||||
alert.success("Successfully created "+data.name)
|
alert.success("Successfully imported "+data.name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1031,20 +1030,20 @@ const Workflows = (props) => {
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
response.text().then(function (text) {
|
alert.success("Successfully loaded workflows from "+downloadUrl)
|
||||||
console.log("RETURN: ", text)
|
getAvailableWorkflows()
|
||||||
alert.success("Loaded existing apps!")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
console.log("DATA: ", responseJson)
|
console.log("DATA: ", responseJson)
|
||||||
if (responseJson.reason !== undefined) {
|
if (!responseJson.success) {
|
||||||
alert.error("Failed loading: "+responseJson.reason)
|
if (responseJson.reason !== undefined) {
|
||||||
} else {
|
alert.error("Failed loading: "+responseJson.reason)
|
||||||
alert.error("Failed loading")
|
} else {
|
||||||
|
alert.error("Failed loading")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const data = [{
|
|||||||
'curve-style': 'unbundled-bezier',
|
'curve-style': 'unbundled-bezier',
|
||||||
'label': 'data(label)',
|
'label': 'data(label)',
|
||||||
'text-margin-y': '-15px',
|
'text-margin-y': '-15px',
|
||||||
|
"color": "white",
|
||||||
"line-fill": "linear-gradient",
|
"line-fill": "linear-gradient",
|
||||||
"line-gradient-stop-colors": ["cyan", "yellow"],
|
"line-gradient-stop-colors": ["cyan", "yellow"],
|
||||||
"line-gradient-stop-positions": ["0.0", "100"],
|
"line-gradient-stop-positions": ["0.0", "100"],
|
||||||
@@ -111,6 +112,8 @@ const data = [{
|
|||||||
selector: 'node:selected',
|
selector: 'node:selected',
|
||||||
css: {
|
css: {
|
||||||
'background-color': '#77b0d0',
|
'background-color': '#77b0d0',
|
||||||
|
'border-color': '#77b0d0',
|
||||||
|
'border-width': '20px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -432,7 +432,6 @@ func zombiecheck() error {
|
|||||||
stopContainers := []string{}
|
stopContainers := []string{}
|
||||||
removeContainers := []string{}
|
removeContainers := []string{}
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
|
|
||||||
// Skip random containers. Only handle things related to Shuffle.
|
// Skip random containers. Only handle things related to Shuffle.
|
||||||
if !strings.Contains(container.Image, baseimagename) {
|
if !strings.Contains(container.Image, baseimagename) {
|
||||||
shuffleFound := false
|
shuffleFound := false
|
||||||
@@ -447,6 +446,8 @@ func zombiecheck() error {
|
|||||||
if !shuffleFound {
|
if !shuffleFound {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
//} else {
|
||||||
|
// log.Printf("NAME: %s", container.Image)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range container.Names {
|
for _, name := range container.Names {
|
||||||
@@ -455,13 +456,16 @@ func zombiecheck() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.State != "running" {
|
log.Printf("NAME: %s", name)
|
||||||
|
|
||||||
|
// Need to check time here too because a container can be removed the same instant as its created
|
||||||
|
currenttime := time.Now().Unix()
|
||||||
|
if container.State != "running" && currenttime-container.Created > int64(workerTimeout) {
|
||||||
removeContainers = append(removeContainers, container.ID)
|
removeContainers = append(removeContainers, container.ID)
|
||||||
containerNames[container.ID] = name
|
containerNames[container.ID] = name
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopcontainer & removecontainer
|
// stopcontainer & removecontainer
|
||||||
currenttime := time.Now().Unix()
|
|
||||||
//log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout))
|
//log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout))
|
||||||
if container.State == "running" && currenttime-container.Created > int64(workerTimeout) {
|
if container.State == "running" && currenttime-container.Created > int64(workerTimeout) {
|
||||||
stopContainers = append(stopContainers, container.ID)
|
stopContainers = append(stopContainers, container.ID)
|
||||||
|
|||||||
@@ -423,6 +423,11 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
|
|
||||||
onpremApps := []string{}
|
onpremApps := []string{}
|
||||||
startAction := workflowExecution.Start
|
startAction := workflowExecution.Start
|
||||||
|
if len(startAction) == 0 {
|
||||||
|
log.Printf("Didn't find execution start action. Setting it to workflow start action.")
|
||||||
|
startAction = workflowExecution.Workflow.Start
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Startaction: %s", startAction)
|
log.Printf("Startaction: %s", startAction)
|
||||||
toExecuteOnprem := []string{}
|
toExecuteOnprem := []string{}
|
||||||
parents := map[string][]string{}
|
parents := map[string][]string{}
|
||||||
@@ -660,7 +665,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
// FIXME
|
// FIXME
|
||||||
// Execute, as we don't really care if env is not set? IDK
|
// Execute, as we don't really care if env is not set? IDK
|
||||||
if action.Environment != environment { //&& action.Environment != "" {
|
if action.Environment != environment { //&& action.Environment != "" {
|
||||||
log.Printf("Bad environment: %s", action.Environment)
|
log.Printf("Bad environment for node: %s. Want %s", action.Environment, environment)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -995,6 +1000,7 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
authorization = os.Getenv("AUTHORIZATION")
|
authorization = os.Getenv("AUTHORIZATION")
|
||||||
executionId = os.Getenv("EXECUTIONID")
|
executionId = os.Getenv("EXECUTIONID")
|
||||||
|
log.Printf("Running normal execution with auth %s and ID %s", authorization, executionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(authorization) == 0 {
|
if len(authorization) == 0 {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Shuffle Apps
|
||||||
|
* This folder is by default meant to be empty
|
||||||
|
* This folder is meant for quick development of apps (single button hot-loading)
|
||||||
|
* Some shuffle apps can be found at https://github.com/frikky/shuffle-apps
|
||||||
|
|
||||||
Reference in New Issue
Block a user