@@ -4,8 +4,13 @@ ENVIRONMENT_NAME=Shuffle
|
||||
|
||||
# Remote github config for first load
|
||||
APP_DOWNLOAD_LOCATION=https://github.com/frikky/shuffle-apps
|
||||
APP_DOWNLOAD_AUTH_USERNAME=""
|
||||
APP_DOWNLOAD_AUTH_PASSWORD=""
|
||||
APP_DOWNLOAD_AUTH_USERNAME=
|
||||
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 ~/
|
||||
APP_HOTLOAD_LOCATION=./shuffle-apps
|
||||
|
||||
@@ -18,4 +18,3 @@ functions/generated_apps
|
||||
|
||||
backend/onprem/app_sdk/apps
|
||||
*test.py
|
||||
shuffle-apps/*
|
||||
|
||||
+97
-39
@@ -173,7 +173,16 @@ class AppBase:
|
||||
if "split" in thistype:
|
||||
return data.split()
|
||||
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:
|
||||
splitvalues = []
|
||||
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"])
|
||||
for variable in execution_data["workflow"]["workflow_variables"]:
|
||||
variablename = variable["name"].replace(" ", "_", -1).lower()
|
||||
|
||||
|
||||
if variablename.lower() == actionname_lower:
|
||||
baseresult = variable["value"]
|
||||
break
|
||||
@@ -341,7 +350,7 @@ class AppBase:
|
||||
except TypeError as e:
|
||||
print("TypeError wf variables: %s" % e)
|
||||
pass
|
||||
|
||||
|
||||
print("BEFORE EXECUTION VAR")
|
||||
if len(baseresult) == 0:
|
||||
try:
|
||||
@@ -377,23 +386,44 @@ class AppBase:
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
return baseresult
|
||||
|
||||
# This whole thing should be recursive.
|
||||
try:
|
||||
cnt = 0
|
||||
for value in parsersplit[1:]:
|
||||
cnt += 1
|
||||
|
||||
print("VALUE: %s" % value)
|
||||
if value == "#":
|
||||
# FIXME - not recursive - should go deeper if there are more #
|
||||
print("HANDLE RECURSIVE LOOP ")
|
||||
print("HANDLE RECURSIVE LOOP OF %s" % basejson)
|
||||
returnlist = []
|
||||
for innervalue in basejson:
|
||||
#print("Value: %s" % value[parsersplit[cnt+1]])
|
||||
returnlist.append(innervalue[parsersplit[cnt+1]])
|
||||
try:
|
||||
for innervalue in basejson:
|
||||
print("Value: %s" % innervalue[parsersplit[cnt+1]])
|
||||
returnlist.append(innervalue[parsersplit[cnt+1]])
|
||||
except IndexError as e:
|
||||
print("Indexerror inner: %s" % e)
|
||||
# Basically means its a normal list, not a crazy one :)
|
||||
# Custom format for ${name[0,1,2,...]}$
|
||||
indexvalue = "${NO_SPLITTER%s}$" % json.dumps(basejson)
|
||||
if len(returnlist) > 0:
|
||||
indexvalue = "${NO_SPLITTER%s}$" % json.dumps(returnlist)
|
||||
|
||||
print("INDEXVAL: ", indexvalue)
|
||||
return indexvalue
|
||||
except TypeError as e:
|
||||
print("TypeError inner: %s" % e)
|
||||
|
||||
# Example format: ${[]}$
|
||||
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:
|
||||
print("BEFORE NORMAL VALUE: ", basejson, value)
|
||||
if len(value) == 0:
|
||||
return basejson
|
||||
|
||||
if isinstance(basejson[value], str):
|
||||
print(f"LOADING STRING '%s' AS JSON" % basejson[value])
|
||||
try:
|
||||
@@ -405,17 +435,19 @@ class AppBase:
|
||||
basejson = basejson[value]
|
||||
|
||||
except KeyError as e:
|
||||
return "KeyError: %s" % e
|
||||
except IndexError as e:
|
||||
return "IndexError: %s" % e
|
||||
|
||||
print("Lower keyerror: %s" % e)
|
||||
return "KeyError: Couldn't find key: %s" % e
|
||||
|
||||
return basejson
|
||||
|
||||
|
||||
# Parses parameters sent to it and returns whether it did it successfully with the values found
|
||||
def parse_params(action, fullexecution, parameter):
|
||||
# Skip if it starts with $?
|
||||
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
|
||||
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.info("STATIC PARSED: %s" % actualitem)
|
||||
if len(actualitem) > 0:
|
||||
print("ACTUAL: %s", actualitem)
|
||||
print("ACTUAL: ", actualitem)
|
||||
for replace in actualitem:
|
||||
try:
|
||||
to_be_replaced = replace[0]
|
||||
@@ -488,8 +520,10 @@ class AppBase:
|
||||
else:
|
||||
fullname += parameter["action_field"]
|
||||
|
||||
self.logger.info("PRE Fullname: %s" % fullname)
|
||||
|
||||
if parameter["value"].startswith(jsonparsevalue):
|
||||
fullname += parameter["value"][2:]
|
||||
fullname += parameter["value"][1:]
|
||||
#else:
|
||||
# fullname = "$%s" % parameter["action_field"]
|
||||
|
||||
@@ -539,6 +573,22 @@ class AppBase:
|
||||
elif check.lower() == "contains":
|
||||
if destinationvalue.lower() in sourcevalue.lower():
|
||||
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:
|
||||
self.logger.info("Condition: can't handle %s yet. Setting to true" % check)
|
||||
|
||||
@@ -574,24 +624,18 @@ class AppBase:
|
||||
|
||||
# Parse all values first here
|
||||
sourcevalue = condition["source"]["value"]
|
||||
if condition["source"]["variant"] == "" or condition["source"]["variant"]== "STATIC_VALUE":
|
||||
condition["source"]["variant"]= "STATIC_VALUE"
|
||||
else:
|
||||
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)
|
||||
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 = parse_wrapper_start(sourcevalue)
|
||||
destinationvalue = condition["destination"]["value"]
|
||||
|
||||
if condition["destination"]["variant"]== "" or condition["destination"]["variant"]== "STATIC_VALUE":
|
||||
condition["destination"]["variant"] = "STATIC_VALUE"
|
||||
else:
|
||||
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)
|
||||
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 = parse_wrapper_start(destinationvalue)
|
||||
@@ -634,7 +678,7 @@ class AppBase:
|
||||
if not branchcheck:
|
||||
self.logger.info("Failed one or more branch conditions.")
|
||||
action_result["result"] = tmpresult
|
||||
action_result["status"] = "SKIPPED"
|
||||
action_result["status"] = "FAILURE"
|
||||
try:
|
||||
ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result)
|
||||
self.logger.info("Result: %d" % ret.status_code)
|
||||
@@ -643,6 +687,7 @@ class AppBase:
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
self.logger.exception(e)
|
||||
|
||||
print("\n\nRETURNING BECAUSE A BRANCH FAILED\n\n")
|
||||
return
|
||||
|
||||
# Replace name cus there might be issues
|
||||
@@ -699,8 +744,10 @@ class AppBase:
|
||||
raise "Value check error: %s" % Exception(check)
|
||||
|
||||
# Custom format for ${name[0,1,2,...]}$
|
||||
#submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
|
||||
submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
|
||||
actualitem = re.findall(submatch, value, re.MULTILINE)
|
||||
print("Multicheck ", actualitem)
|
||||
if len(actualitem) > 0:
|
||||
multiexecution = True
|
||||
|
||||
@@ -717,9 +764,12 @@ class AppBase:
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
itemlist = json.loads(actualitem)
|
||||
if len(itemlist) > minlength:
|
||||
minlength = len(itemlist)
|
||||
try:
|
||||
itemlist = json.loads(actualitem)
|
||||
if len(itemlist) > minlength:
|
||||
minlength = len(itemlist)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
print("JSON Error: %s in %s" % (e, actualitem))
|
||||
|
||||
replacements[to_be_replaced] = actualitem
|
||||
|
||||
@@ -729,7 +779,13 @@ class AppBase:
|
||||
for i in range(0, minlength):
|
||||
tmpitem = json.loads(json.dumps(parameter["value"]))
|
||||
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)
|
||||
|
||||
resultarray.append(tmpitem)
|
||||
@@ -738,7 +794,7 @@ class AppBase:
|
||||
multi_parameters[parameter["name"]] = resultarray
|
||||
else:
|
||||
# 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)
|
||||
|
||||
params[parameter["name"]] = value
|
||||
@@ -758,8 +814,9 @@ class AppBase:
|
||||
except ValueError:
|
||||
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("POST NEWRES: ", newres)
|
||||
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
|
||||
# 2. Find the right value from the parsed multi_params
|
||||
|
||||
@@ -782,7 +839,8 @@ class AppBase:
|
||||
|
||||
#print("Running with params %s" % baseparams)
|
||||
ret = await func(**baseparams)
|
||||
print("Inner ret: %s" % ret)
|
||||
ret = ret.replace("\"", "\\\"", -1)
|
||||
print("Inner ret parsed: %s" % ret)
|
||||
|
||||
try:
|
||||
results.append(json.loads(ret))
|
||||
@@ -802,8 +860,7 @@ class AppBase:
|
||||
print("Normal result?")
|
||||
result = results
|
||||
|
||||
print("RESULT: %s" % result)
|
||||
|
||||
print("RESULT: %s" % result)
|
||||
action_result["status"] = "SUCCESS"
|
||||
action_result["result"] = str(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"Data: %s" % action_result)
|
||||
except TypeError as e:
|
||||
print("TypeError issue: %s" % e)
|
||||
action_result["status"] = "FAILURE"
|
||||
action_result["result"] = "TypeError: %s" % str(e)
|
||||
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")
|
||||
action_result["status"] = "FAILURE"
|
||||
action_result["result"] = "Function %s is not callable." % actionname
|
||||
|
||||
+441
-241
@@ -362,6 +362,7 @@ type HookAction struct {
|
||||
|
||||
type Hook struct {
|
||||
Id string `json:"id" datastore:"id"`
|
||||
Start string `json:"start" datastore:"start"`
|
||||
Info Info `json:"info" datastore:"info"`
|
||||
Actions []HookAction `json:"actions" datastore:"actions"`
|
||||
Type string `json:"type" datastore:"type"`
|
||||
@@ -812,8 +813,8 @@ func parseLoginParameters(resp http.ResponseWriter, request *http.Request) (logi
|
||||
// Removed for localhost
|
||||
func checkPasswordStrength(password string) error {
|
||||
// Check password strength here
|
||||
if len(password) < 10 {
|
||||
return errors.New("Minimum password length is 10.")
|
||||
if len(password) < 3 {
|
||||
return errors.New("Minimum password length is 3.")
|
||||
}
|
||||
|
||||
//if len(password) > 128 {
|
||||
@@ -838,6 +839,84 @@ func checkPasswordStrength(password string) error {
|
||||
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 :)
|
||||
func checkUsername(Username string) error {
|
||||
// Stupid first check of email loool
|
||||
@@ -845,8 +924,8 @@ func checkUsername(Username string) error {
|
||||
// return errors.New("Invalid Username")
|
||||
//}
|
||||
|
||||
if len(Username) < 4 {
|
||||
return errors.New("Minimum Username length is 4")
|
||||
if len(Username) < 3 {
|
||||
return errors.New("Minimum Username length is 3")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -995,6 +1074,119 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) {
|
||||
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) {
|
||||
cors := handleCors(resp, request)
|
||||
if cors {
|
||||
@@ -1030,128 +1222,21 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns false if there is an issue
|
||||
// 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
|
||||
role := "user"
|
||||
if count == 0 {
|
||||
newUser.Role = "admin"
|
||||
newUser.Roles = []string{"admin"}
|
||||
} else {
|
||||
newUser.Role = "user"
|
||||
newUser.Roles = []string{"user"}
|
||||
role = "admin"
|
||||
}
|
||||
|
||||
// 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)
|
||||
err = createNewUser(data.Username, data.Password, role, "")
|
||||
if err != nil {
|
||||
log.Printf("Error adding User %s: %s", data.Username, err)
|
||||
log.Printf("Failed registering user: %s", err)
|
||||
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
|
||||
}
|
||||
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.Write([]byte(`{"success": true}`))
|
||||
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 {
|
||||
@@ -1279,62 +1364,195 @@ func generateApikey(ctx context.Context, userInfo User) (User, error) {
|
||||
return userInfo, nil
|
||||
}
|
||||
|
||||
func handleApiGeneration(resp http.ResponseWriter, request *http.Request) {
|
||||
func handleUpdateUser(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := handleCors(resp, request)
|
||||
if cors {
|
||||
return
|
||||
}
|
||||
|
||||
c, err := request.Cookie("session_token")
|
||||
userInfo, err := handleApiAuthentication(resp, request)
|
||||
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.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
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()
|
||||
sessionToken := c.Value
|
||||
session, err := getSession(ctx, sessionToken)
|
||||
var t newUserStruct
|
||||
err = json.Unmarshal(body, &t)
|
||||
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.Write([]byte(`{"success": false, "reason": ""}`))
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unmarshaling. Missing field: user_id"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
// Get session first
|
||||
// Should basically never happen
|
||||
userInfo, err := getUser(ctx, session.Id)
|
||||
if userInfo.Role != "admin" {
|
||||
log.Printf("%s tried to update user %s", userInfo.Username, t.UserId)
|
||||
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 {
|
||||
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.Write([]byte(`{"success": false, "reason": ""}`))
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false}`)))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete old apikey from cache
|
||||
//memcache.Delete(ctx, userInfo.ApiKey)
|
||||
|
||||
if session.Session != userInfo.Session {
|
||||
log.Printf("Session %s is not the latest. %s", session.Username, err)
|
||||
if t.Role != "admin" && t.Role != "user" {
|
||||
log.Printf("%s tried and failed to update user %s", userInfo.Username, t.UserId)
|
||||
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
|
||||
} 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 {
|
||||
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.Write([]byte(`{"success": false, "reason": ""}`))
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false}`)))
|
||||
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.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
|
||||
}
|
||||
|
||||
c, err := request.Cookie("session_token")
|
||||
userInfo, err := handleApiAuthentication(resp, request)
|
||||
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.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
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": ""}`))
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1391,21 +1581,16 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Should compare with local storage first
|
||||
c, err := request.Cookie("session_token")
|
||||
userInfo, err := handleApiAuthentication(resp, request)
|
||||
if err != nil {
|
||||
log.Printf("Api authentication failed in handleInfo: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME - check memcache here
|
||||
// Get the item from the memcache
|
||||
ctx := context.Background()
|
||||
|
||||
sessionToken := c.Value
|
||||
//log.Printf("Found session %s", sessionToken)
|
||||
session, err := getSession(ctx, sessionToken)
|
||||
session, err := getSession(ctx, userInfo.Session)
|
||||
if err != nil {
|
||||
log.Printf("Session %#v doesn't exist: %s", session, err)
|
||||
resp.WriteHeader(401)
|
||||
@@ -1413,17 +1598,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get session first
|
||||
// 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
|
||||
// This is a long check to see if an inactive admin can access the site
|
||||
if !userInfo.Active {
|
||||
if userInfo.Role == "admin" {
|
||||
ctx := context.Background()
|
||||
@@ -1478,7 +1653,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
//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)
|
||||
log.Printf("Session %s is not the same as %s for %s. %s", userInfo.Session, session.Session, userInfo.Username, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
||||
return
|
||||
@@ -1765,7 +1940,7 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) {
|
||||
if len(users) != 1 {
|
||||
log.Printf(`Found multiple users with the same username: %s: %d`, t.Username, len(users))
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2059,7 +2234,8 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
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
|
||||
_, err = dbclient.GetAll(ctx, q, &users)
|
||||
if err != nil {
|
||||
@@ -2072,7 +2248,7 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) {
|
||||
if len(users) != 1 {
|
||||
log.Printf(`Found multiple users with the same username: %s: %d`, data.Username, len(users))
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2889,7 +3065,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
hookId = hookId[8:len(hookId)]
|
||||
|
||||
log.Printf("HookID: %s", hookId)
|
||||
//log.Printf("HookID: %s", hookId)
|
||||
hook, err := getHook(ctx, hookId)
|
||||
if err != nil {
|
||||
log.Printf("Failed getting hook: %s", err)
|
||||
@@ -2898,31 +3074,60 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("HOOK FOUND: %#v", hook)
|
||||
//log.Printf("HOOK FOUND: %#v", hook)
|
||||
// Execute the workflow
|
||||
//executeWorkflow(resp, request)
|
||||
|
||||
//resp.WriteHeader(200)
|
||||
//resp.Write([]byte(`{"success": true}`))
|
||||
if hook.Status == "stopped" {
|
||||
log.Printf("Not running because hook status is stopped")
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Click start to start it"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
if len(hook.Workflows) == 0 {
|
||||
log.Printf("Not running because hook isn't connected to any workflows")
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
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{
|
||||
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 {
|
||||
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"`
|
||||
Name string `json:"name"`
|
||||
Workflow string `json:"workflow"`
|
||||
Start string `json:"start"`
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
@@ -2971,7 +3177,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Data: %s", string(body))
|
||||
log.Printf("Data: %s", string(body))
|
||||
|
||||
ctx := context.Background()
|
||||
var requestdata requestData
|
||||
@@ -3021,6 +3227,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
hook := Hook{
|
||||
Id: newId,
|
||||
Start: requestdata.Start,
|
||||
Workflows: []string{requestdata.Workflow},
|
||||
Info: Info{
|
||||
Name: requestdata.Name,
|
||||
@@ -3041,33 +3248,6 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) {
|
||||
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.Running = true
|
||||
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.Println("Generating new hook")
|
||||
log.Println("Set up a new hook")
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(`{"success": true}`))
|
||||
}
|
||||
@@ -5966,18 +6146,17 @@ func runInit(ctx context.Context) {
|
||||
}
|
||||
|
||||
// Fix active users etc
|
||||
log.Printf("Reformatting users")
|
||||
q := datastore.NewQuery("Users").Filter("active =", true)
|
||||
var users []User
|
||||
_, err = dbclient.GetAll(ctx, q, &users)
|
||||
var activeusers []User
|
||||
_, err = dbclient.GetAll(ctx, q, &activeusers)
|
||||
if err != nil {
|
||||
log.Printf("Error getting users apikey (runinit): %s", err)
|
||||
log.Printf("Error getting users during init: %s", err)
|
||||
} 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")
|
||||
q := datastore.NewQuery("Users")
|
||||
var users []User
|
||||
_, err := dbclient.GetAll(ctx, q, &users)
|
||||
if err == nil {
|
||||
for _, user := range users {
|
||||
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)
|
||||
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 {
|
||||
log.Printf("Finished git clone. Looking for updates to the repo.")
|
||||
dir, err := fs.ReadDir("")
|
||||
@@ -6157,6 +6354,7 @@ func init() {
|
||||
r.HandleFunc("/functions/outlook/getFolders", handleGetOutlookFolders).Methods("GET", "OPTIONS")
|
||||
|
||||
// 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/logout", handleLogout).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/getinfo", handleInfo).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/passwordchange", handlePasswordChange).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/users", handleGetUsers).Methods("GET", "OPTIONS")
|
||||
|
||||
// General - duplicates and old.
|
||||
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/getinfo", handleInfo).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/setenvironments", handleSetEnvironments).Methods("PUT", "OPTIONS")
|
||||
|
||||
+40
-87
@@ -61,6 +61,7 @@ var shuffleTestPath = "./shuffle-test-258209-5a2e8d7e508a.json"
|
||||
type ExecutionRequest struct {
|
||||
ExecutionId string `json:"execution_id"`
|
||||
ExecutionArgument string `json:"execution_argument"`
|
||||
ExecutionSource string `json:"execution_source"`
|
||||
WorkflowId string `json:"workflow_id"`
|
||||
Environments []string `json:"environments"`
|
||||
Authorization string `json:"authorization"`
|
||||
@@ -153,6 +154,7 @@ type WorkflowExecution struct {
|
||||
Start string `json:"start" datastore:"start"`
|
||||
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"`
|
||||
ExecutionId string `json:"execution_id" datastore:"execution_id"`
|
||||
ExecutionSource string `json:"execution_source" datastore:"execution_source"`
|
||||
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
|
||||
LastNode string `json:"last_node" datastore:"last_node"`
|
||||
Authorization string `json:"authorization" datastore:"authorization"`
|
||||
@@ -449,9 +451,9 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
|
||||
// FIXME:
|
||||
// This may run multiple places if multiple servers,
|
||||
// but that's a future problem
|
||||
log.Printf("BODY: %s", string(body))
|
||||
//log.Printf("BODY: %s", string(body))
|
||||
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)
|
||||
job := func() {
|
||||
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
|
||||
workflowapps, err := getAllWorkflowApps(ctx)
|
||||
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 {
|
||||
if item.Name == "Testing" && item.AppVersion == "1.0.0" {
|
||||
nodeId := "40447f30-fa44-4a4f-a133-4ee710368737"
|
||||
@@ -1241,7 +1250,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
workflow.Actions = newActions
|
||||
workflow.IsValid = true
|
||||
workflow.Configuration.ExitOnError = true
|
||||
workflow.Configuration.ExitOnError = false
|
||||
|
||||
workflowjson, err := json.Marshal(workflow)
|
||||
if err != nil {
|
||||
@@ -1564,6 +1573,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
// doesn't check sharing=true
|
||||
// Have to do it like this to add the user's apps
|
||||
log.Println("Apps set starting")
|
||||
//log.Printf("EXIT ON ERROR: %#v", workflow.Configuration.ExitOnError)
|
||||
workflowApps := []WorkflowApp{}
|
||||
//memcacheName = "all_apps"
|
||||
//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
|
||||
//if app.Name == action.AppName && app.AppVersion == action.AppVersion {
|
||||
// curapp = app
|
||||
// break
|
||||
//}
|
||||
if app.Name == action.AppName && app.AppVersion == action.AppVersion {
|
||||
curapp = app
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
err = json.Unmarshal(body, &execution)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1985,10 +1995,15 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
}
|
||||
|
||||
// FIXME - this should have "execution_argument" from executeWorkflow frontend
|
||||
//log.Printf("EXEC: %#v", execution)
|
||||
if len(execution.ExecutionArgument) > 0 {
|
||||
workflowExecution.ExecutionArgument = execution.ExecutionArgument
|
||||
}
|
||||
|
||||
if len(execution.ExecutionSource) > 0 {
|
||||
workflowExecution.ExecutionSource = execution.ExecutionSource
|
||||
}
|
||||
|
||||
//log.Printf("Execution data: %#v", execution)
|
||||
if len(execution.Start) == 36 {
|
||||
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 {
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -2136,6 +2151,13 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
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
|
||||
// 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)
|
||||
|
||||
topic := "workflows"
|
||||
startFound := false
|
||||
// FIXME - remove this?
|
||||
newActions := []Action{}
|
||||
defaultResults := []ActionResult{}
|
||||
for _, action := range workflowExecution.Workflow.Actions {
|
||||
action.LargeImage = ""
|
||||
if action.ID == workflowExecution.Start {
|
||||
startFound = true
|
||||
}
|
||||
//log.Println(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
|
||||
workflowExecution.Results = defaultResults
|
||||
workflowExecution.Workflow.Actions = newActions
|
||||
@@ -2929,84 +2960,6 @@ func setWorkflow(ctx context.Context, workflow Workflow, id string) error {
|
||||
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) {
|
||||
cors := handleCors(resp, request)
|
||||
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
|
||||
- ${APP_HOTLOAD_LOCATION}:/shuffle-apps
|
||||
environment:
|
||||
- ORG_ID=${ORG_ID}
|
||||
- DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
||||
- APP_DOWNLOAD_LOCATION=${APP_DOWNLOAD_LOCATION}
|
||||
- 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
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
+77
-17
@@ -2,6 +2,8 @@ import React, { useEffect} from 'react';
|
||||
|
||||
import {Link} from 'react-router-dom';
|
||||
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 Divider from '@material-ui/core/Divider';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
@@ -70,7 +72,7 @@ const Admin = (props) => {
|
||||
|
||||
const onPasswordChange = () => {
|
||||
const data = {"username": selectedUser.username, "newpassword": newPassword}
|
||||
const url = globalUrl+'/api/v1/passwordchange';
|
||||
const url = globalUrl+'/api/v1/users/passwordchange';
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
@@ -92,7 +94,7 @@ const Admin = (props) => {
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
alert.error("Err: ", error.toString())
|
||||
alert.error("Err: "+error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ const Admin = (props) => {
|
||||
// Just use this one?
|
||||
var data = {"username": data.Username, "password": data.Password}
|
||||
var baseurl = globalUrl
|
||||
const url = baseurl+'/api/v1/register';
|
||||
const url = baseurl+'/api/v1/users/register';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
@@ -294,6 +296,7 @@ const Admin = (props) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log(responseJson)
|
||||
setUsers(responseJson)
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -319,14 +322,54 @@ const Admin = (props) => {
|
||||
modalUser[field] = value
|
||||
}
|
||||
|
||||
const generateApikey = () => {
|
||||
fetch(globalUrl+"/api/v1/generateapikey", {
|
||||
method: 'GET',
|
||||
const setUser = (userId, field, value) => {
|
||||
const data = {"user_id": userId}
|
||||
data[field] = value
|
||||
console.log("DATA: ", data)
|
||||
|
||||
fetch(globalUrl+"/api/v1/users/updateuser", {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': '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) => {
|
||||
if (response.status !== 200) {
|
||||
@@ -407,7 +450,7 @@ const Admin = (props) => {
|
||||
style={{}}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => generateApikey(selectedUser)}
|
||||
onClick={() => generateApikey(selectedUser.id)}
|
||||
>
|
||||
Get new API key
|
||||
</Button>
|
||||
@@ -540,10 +583,6 @@ const Admin = (props) => {
|
||||
primary="API key"
|
||||
style={{minWidth: 350, maxWidth: 350, overflow: "hidden"}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Password"
|
||||
style={{minWidth: 180, maxWidth: 180}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Role"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
@@ -569,11 +608,32 @@ const Admin = (props) => {
|
||||
style={{maxWidth: 350, minWidth: 350,}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="**************"
|
||||
style={{minWidth: 180, maxWidth: 180}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.role}
|
||||
primary=
|
||||
<Select
|
||||
PaperProps={{
|
||||
style: {
|
||||
}
|
||||
}}
|
||||
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}}
|
||||
/>
|
||||
<ListItemText
|
||||
|
||||
+284
-242
File diff suppressed because one or more lines are too long
+9
-2
@@ -59,7 +59,14 @@ const theme = createMuiTheme({
|
||||
},
|
||||
typography: {
|
||||
useNextVariants: true
|
||||
}
|
||||
},
|
||||
overrides: {
|
||||
MuiMenu: {
|
||||
list: {
|
||||
backgroundColor: inputColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -84,7 +91,7 @@ const App = (message, props) => {
|
||||
|
||||
const checkLogin = () => {
|
||||
var baseurl = globalUrl
|
||||
fetch(baseurl+"/api/v1/getinfo", {
|
||||
fetch(baseurl+"/api/v1/users/getinfo", {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -448,6 +448,12 @@ const Apps = (props) => {
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
{selectedAction.description !== undefined && selectedAction.description !== null ?
|
||||
<div>
|
||||
<b>Action Description</b><div/>
|
||||
{selectedAction.description}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
|
||||
@@ -92,7 +92,7 @@ const LoginDialog = props => {
|
||||
var data = {"username": username, "password": password}
|
||||
var baseurl = globalUrl
|
||||
if (register) {
|
||||
var url = baseurl+'/api/v1/login';
|
||||
var url = baseurl+'/api/v1/users/login';
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
@@ -123,7 +123,7 @@ const LoginDialog = props => {
|
||||
setLoginInfo("Error in userdata: " + error)
|
||||
});
|
||||
} else {
|
||||
url = baseurl+'/api/v1/register';
|
||||
url = baseurl+'/api/v1/users/register';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
|
||||
+13
-14
@@ -89,7 +89,6 @@ const Workflows = (props) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log(responseJson)
|
||||
setSelectedExecution({})
|
||||
setWorkflowExecutions([])
|
||||
|
||||
@@ -384,21 +383,21 @@ const Workflows = (props) => {
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||
setModalOpen(true)
|
||||
setEditingWorkflow(data)
|
||||
setNewWorkflowName(data.name)
|
||||
setNewWorkflowDescription(data.description)
|
||||
}} key={"change"}>{"Change name"}</MenuItem>
|
||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||
copyWorkflow(data)
|
||||
setOpen(false)
|
||||
}} key={"copy"}>{"Copy"}</MenuItem>
|
||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||
exportWorkflow(data)
|
||||
setOpen(false)
|
||||
}} key={"export"}>{"Export"}</MenuItem>
|
||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||
deleteWorkflow(data.id)
|
||||
setOpen(false)
|
||||
}} key={"delete"}>{"Delete"}</MenuItem>
|
||||
@@ -807,7 +806,7 @@ const Workflows = (props) => {
|
||||
const ret = setNewWorkflow(data.name, data.description, data, false)
|
||||
.then((response) => {
|
||||
if (response !== undefined) {
|
||||
alert.success("Successfully created "+data.name)
|
||||
alert.success("Successfully imported "+data.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1031,20 +1030,20 @@ const Workflows = (props) => {
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
response.text().then(function (text) {
|
||||
console.log("RETURN: ", text)
|
||||
alert.success("Loaded existing apps!")
|
||||
})
|
||||
alert.success("Successfully loaded workflows from "+downloadUrl)
|
||||
getAvailableWorkflows()
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log("DATA: ", responseJson)
|
||||
if (responseJson.reason !== undefined) {
|
||||
alert.error("Failed loading: "+responseJson.reason)
|
||||
} else {
|
||||
alert.error("Failed loading")
|
||||
if (!responseJson.success) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
alert.error("Failed loading: "+responseJson.reason)
|
||||
} else {
|
||||
alert.error("Failed loading")
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
@@ -24,6 +24,7 @@ const data = [{
|
||||
'curve-style': 'unbundled-bezier',
|
||||
'label': 'data(label)',
|
||||
'text-margin-y': '-15px',
|
||||
"color": "white",
|
||||
"line-fill": "linear-gradient",
|
||||
"line-gradient-stop-colors": ["cyan", "yellow"],
|
||||
"line-gradient-stop-positions": ["0.0", "100"],
|
||||
@@ -111,6 +112,8 @@ const data = [{
|
||||
selector: 'node:selected',
|
||||
css: {
|
||||
'background-color': '#77b0d0',
|
||||
'border-color': '#77b0d0',
|
||||
'border-width': '20px',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -432,7 +432,6 @@ func zombiecheck() error {
|
||||
stopContainers := []string{}
|
||||
removeContainers := []string{}
|
||||
for _, container := range containers {
|
||||
|
||||
// Skip random containers. Only handle things related to Shuffle.
|
||||
if !strings.Contains(container.Image, baseimagename) {
|
||||
shuffleFound := false
|
||||
@@ -447,6 +446,8 @@ func zombiecheck() error {
|
||||
if !shuffleFound {
|
||||
continue
|
||||
}
|
||||
//} else {
|
||||
// log.Printf("NAME: %s", container.Image)
|
||||
}
|
||||
|
||||
for _, name := range container.Names {
|
||||
@@ -455,13 +456,16 @@ func zombiecheck() error {
|
||||
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)
|
||||
containerNames[container.ID] = name
|
||||
}
|
||||
|
||||
// stopcontainer & removecontainer
|
||||
currenttime := time.Now().Unix()
|
||||
//log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout))
|
||||
if container.State == "running" && currenttime-container.Created > int64(workerTimeout) {
|
||||
stopContainers = append(stopContainers, container.ID)
|
||||
|
||||
@@ -423,6 +423,11 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
||||
|
||||
onpremApps := []string{}
|
||||
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)
|
||||
toExecuteOnprem := []string{}
|
||||
parents := map[string][]string{}
|
||||
@@ -660,7 +665,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
||||
// FIXME
|
||||
// Execute, as we don't really care if env is not set? IDK
|
||||
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
|
||||
}
|
||||
|
||||
@@ -995,6 +1000,7 @@ func main() {
|
||||
} else {
|
||||
authorization = os.Getenv("AUTHORIZATION")
|
||||
executionId = os.Getenv("EXECUTIONID")
|
||||
log.Printf("Running normal execution with auth %s and ID %s", authorization, executionId)
|
||||
}
|
||||
|
||||
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