Merge pull request #82 from frikky/1.0.0

0.5.5
This commit is contained in:
Frikky
2020-06-29 02:43:43 +09:00
committed by GitHub
18 changed files with 1075 additions and 675 deletions
+7 -2
View File
@@ -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
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
File diff suppressed because one or more lines are too long
+9 -2
View File
@@ -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',
+6
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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 => {
+3
View File
@@ -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',
}, },
}, },
{ {
+7 -3
View File
@@ -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)
+7 -1
View File
@@ -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 {
+5
View File
@@ -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