@@ -35,6 +35,6 @@ SHUFFLE_HTTP_PROXY=
|
|||||||
SHUFFLE_HTTPS_PROXY=
|
SHUFFLE_HTTPS_PROXY=
|
||||||
SHUFFLE_PASS_WORKER_PROXY=TRUE
|
SHUFFLE_PASS_WORKER_PROXY=TRUE
|
||||||
|
|
||||||
SHUFFLE_BASE_IMAGE_REGISTRY=docker.io
|
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
|
||||||
SHUFFLE_BASE_IMAGE_NAME=frikky/shuffle
|
SHUFFLE_BASE_IMAGE_NAME=frikky
|
||||||
SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-0.6.0"
|
SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-0.8.0"
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ class AppBase:
|
|||||||
|
|
||||||
#Actionname: Start_node
|
#Actionname: Start_node
|
||||||
|
|
||||||
print(f"Actionname: {actionname}")
|
print(f"Actionname: {actionname_lower}")
|
||||||
|
|
||||||
# 1. Find the action
|
# 1. Find the action
|
||||||
baseresult = ""
|
baseresult = ""
|
||||||
@@ -556,14 +556,14 @@ class AppBase:
|
|||||||
return ""+appendresult, False
|
return ""+appendresult, False
|
||||||
|
|
||||||
if len(parsersplit) == 1:
|
if len(parsersplit) == 1:
|
||||||
return baseresult+appendresult, False
|
return str(baseresult)+str(appendresult), False
|
||||||
|
|
||||||
baseresult = baseresult.replace("\'", "\"")
|
baseresult = baseresult.replace("\'", "\"")
|
||||||
basejson = {}
|
basejson = {}
|
||||||
try:
|
try:
|
||||||
basejson = json.loads(baseresult)
|
basejson = json.loads(baseresult)
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
return baseresult+appendresult, False
|
return str(baseresult)+str(appendresult), False
|
||||||
|
|
||||||
data, is_loop = recurse_json(basejson, parsersplit[1:])
|
data, is_loop = recurse_json(basejson, parsersplit[1:])
|
||||||
parseditem = data
|
parseditem = data
|
||||||
@@ -577,7 +577,7 @@ class AppBase:
|
|||||||
print("SET DATA WRAPPER TO %s!" % parsersplit[-1])
|
print("SET DATA WRAPPER TO %s!" % parsersplit[-1])
|
||||||
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
|
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
|
||||||
|
|
||||||
return parseditem+appendresult, is_loop
|
return str(parseditem)+str(appendresult), is_loop
|
||||||
|
|
||||||
# Parses parameters sent to it and returns whether it did it successfully with the values found
|
# Parses parameters sent to it and returns whether it did it successfully with the values found
|
||||||
def parse_params(action, fullexecution, parameter):
|
def parse_params(action, fullexecution, parameter):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
NAME=app_sdk
|
NAME=shuffle-app_sdk
|
||||||
VERSION=0.7.6
|
VERSION=0.8.0
|
||||||
|
|
||||||
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
||||||
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|||||||
+48
-46
@@ -397,10 +397,12 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
|
|||||||
verifyAddin,
|
verifyAddin,
|
||||||
)
|
)
|
||||||
|
|
||||||
if strings.Contains(functionname, "api_dumps_delete") {
|
/*
|
||||||
log.Println(data)
|
if strings.Contains(functionname, "search") {
|
||||||
log.Printf("Queries: %s", queryString)
|
log.Println(data)
|
||||||
}
|
log.Printf("Queries: %s", queryString)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
//log.Printf(data)
|
//log.Printf(data)
|
||||||
return functionname, data
|
return functionname, data
|
||||||
@@ -1031,6 +1033,12 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Value.Required {
|
||||||
|
action.Parameters = append(action.Parameters, curParam)
|
||||||
|
} else {
|
||||||
|
optionalParameters = append(optionalParameters, curParam)
|
||||||
|
}
|
||||||
|
|
||||||
if param.Value.In == "path" {
|
if param.Value.In == "path" {
|
||||||
parameters = append(parameters, curParam.Name)
|
parameters = append(parameters, curParam.Name)
|
||||||
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
||||||
@@ -1059,12 +1067,6 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [
|
|||||||
firstQuery = false
|
firstQuery = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Value.Required {
|
|
||||||
action.Parameters = append(action.Parameters, curParam)
|
|
||||||
} else {
|
|
||||||
optionalParameters = append(optionalParameters, curParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1165,6 +1167,12 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Value.Required {
|
||||||
|
action.Parameters = append(action.Parameters, curParam)
|
||||||
|
} else {
|
||||||
|
optionalParameters = append(optionalParameters, curParam)
|
||||||
|
}
|
||||||
|
|
||||||
if param.Value.In == "path" {
|
if param.Value.In == "path" {
|
||||||
parameters = append(parameters, curParam.Name)
|
parameters = append(parameters, curParam.Name)
|
||||||
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
||||||
@@ -1193,11 +1201,6 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
|
|||||||
}
|
}
|
||||||
firstQuery = false
|
firstQuery = false
|
||||||
}
|
}
|
||||||
if param.Value.Required {
|
|
||||||
action.Parameters = append(action.Parameters, curParam)
|
|
||||||
} else {
|
|
||||||
optionalParameters = append(optionalParameters, curParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1299,6 +1302,12 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Value.Required {
|
||||||
|
action.Parameters = append(action.Parameters, curParam)
|
||||||
|
} else {
|
||||||
|
optionalParameters = append(optionalParameters, curParam)
|
||||||
|
}
|
||||||
|
|
||||||
if param.Value.In == "path" {
|
if param.Value.In == "path" {
|
||||||
parameters = append(parameters, curParam.Name)
|
parameters = append(parameters, curParam.Name)
|
||||||
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
||||||
@@ -1326,13 +1335,6 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
|
|||||||
}
|
}
|
||||||
firstQuery = false
|
firstQuery = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Value.Required {
|
|
||||||
action.Parameters = append(action.Parameters, curParam)
|
|
||||||
} else {
|
|
||||||
optionalParameters = append(optionalParameters, curParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1433,6 +1435,12 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Value.Required {
|
||||||
|
action.Parameters = append(action.Parameters, curParam)
|
||||||
|
} else {
|
||||||
|
optionalParameters = append(optionalParameters, curParam)
|
||||||
|
}
|
||||||
|
|
||||||
if param.Value.In == "path" {
|
if param.Value.In == "path" {
|
||||||
parameters = append(parameters, curParam.Name)
|
parameters = append(parameters, curParam.Name)
|
||||||
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
||||||
@@ -1461,12 +1469,6 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []
|
|||||||
firstQuery = false
|
firstQuery = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Value.Required {
|
|
||||||
action.Parameters = append(action.Parameters, curParam)
|
|
||||||
} else {
|
|
||||||
optionalParameters = append(optionalParameters, curParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1566,6 +1568,12 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Value.Required {
|
||||||
|
action.Parameters = append(action.Parameters, curParam)
|
||||||
|
} else {
|
||||||
|
optionalParameters = append(optionalParameters, curParam)
|
||||||
|
}
|
||||||
|
|
||||||
if param.Value.In == "path" {
|
if param.Value.In == "path" {
|
||||||
parameters = append(parameters, curParam.Name)
|
parameters = append(parameters, curParam.Name)
|
||||||
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
||||||
@@ -1593,12 +1601,6 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
|
|||||||
}
|
}
|
||||||
firstQuery = false
|
firstQuery = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Value.Required {
|
|
||||||
action.Parameters = append(action.Parameters, curParam)
|
|
||||||
} else {
|
|
||||||
optionalParameters = append(optionalParameters, curParam)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1699,6 +1701,12 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Value.Required {
|
||||||
|
action.Parameters = append(action.Parameters, curParam)
|
||||||
|
} else {
|
||||||
|
optionalParameters = append(optionalParameters, curParam)
|
||||||
|
}
|
||||||
|
|
||||||
if param.Value.In == "path" {
|
if param.Value.In == "path" {
|
||||||
parameters = append(parameters, curParam.Name)
|
parameters = append(parameters, curParam.Name)
|
||||||
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
||||||
@@ -1726,12 +1734,6 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W
|
|||||||
}
|
}
|
||||||
firstQuery = false
|
firstQuery = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Value.Required {
|
|
||||||
action.Parameters = append(action.Parameters, curParam)
|
|
||||||
} else {
|
|
||||||
optionalParameters = append(optionalParameters, curParam)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1832,6 +1834,12 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Value.Required {
|
||||||
|
action.Parameters = append(action.Parameters, curParam)
|
||||||
|
} else {
|
||||||
|
optionalParameters = append(optionalParameters, curParam)
|
||||||
|
}
|
||||||
|
|
||||||
if param.Value.In == "path" {
|
if param.Value.In == "path" {
|
||||||
parameters = append(parameters, param.Value.Name)
|
parameters = append(parameters, param.Value.Name)
|
||||||
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
||||||
@@ -1860,12 +1868,6 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
|
|||||||
firstQuery = false
|
firstQuery = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.Value.Required {
|
|
||||||
action.Parameters = append(action.Parameters, curParam)
|
|
||||||
} else {
|
|
||||||
optionalParameters = append(optionalParameters, curParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+757
-113
File diff suppressed because it is too large
Load Diff
+433
-39
@@ -44,6 +44,7 @@ var baseEnvironment = "onprem"
|
|||||||
var cloudname = "cloud"
|
var cloudname = "cloud"
|
||||||
var defaultLocation = "europe-west2"
|
var defaultLocation = "europe-west2"
|
||||||
var scheduledJobs = map[string]*newscheduler.Job{}
|
var scheduledJobs = map[string]*newscheduler.Job{}
|
||||||
|
var scheduledOrgs = map[string]*newscheduler.Job{}
|
||||||
|
|
||||||
// To test out firestore before potential merge
|
// To test out firestore before potential merge
|
||||||
//var upgrader = websocket.Upgrader{
|
//var upgrader = websocket.Upgrader{
|
||||||
@@ -55,27 +56,44 @@ var scheduledJobs = map[string]*newscheduler.Job{}
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
type ExecutionRequest struct {
|
type ExecutionRequest struct {
|
||||||
ExecutionId string `json:"execution_id"`
|
ExecutionId string `json:"execution_id,omitempty"`
|
||||||
ExecutionArgument string `json:"execution_argument"`
|
ExecutionArgument string `json:"execution_argument,omitempty"`
|
||||||
ExecutionSource string `json:"execution_source"`
|
ExecutionSource string `json:"execution_source,omitempty"`
|
||||||
WorkflowId string `json:"workflow_id"`
|
WorkflowId string `json:"workflow_id,omitempty"`
|
||||||
Environments []string `json:"environments"`
|
Environments []string `json:"environments,omitempty"`
|
||||||
Authorization string `json:"authorization"`
|
Authorization string `json:"authorization,omitempty"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status,omitempty"`
|
||||||
Start string `json:"start"`
|
Start string `json:"start,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyncFeatures struct {
|
type SyncFeatures struct {
|
||||||
Apps SyncData `json:"apps" datastore:"apps"`
|
Webhook SyncData `json:"webhook" datastore:"webhook"`
|
||||||
Workflows SyncData `json:"workflows" datastore:"workflows"`
|
Schedules SyncData `json:"schedules" datastore:"schedules"`
|
||||||
Schedules SyncData `json:"schedules" datastore:"schedules"`
|
UserInput SyncData `json:"user_input" datastore:"user_input"`
|
||||||
Autocomplete SyncData `json:"autocomplete" datastore:"autocomplete"`
|
SendMail SyncData `json:"send_mail" datastore:"send_mail"`
|
||||||
Authentication SyncData `json:"authentication" datastore:"authentication"`
|
SendSms SyncData `json:"send_sms" datastore:"send_sms"`
|
||||||
|
Updates SyncData `json:"updates" datastore:"updates"`
|
||||||
|
Notifications SyncData `json:"notifications" datastore:"notifications"`
|
||||||
|
EmailTrigger SyncData `json:"email_trigger" datastore:"email_trigger"`
|
||||||
|
AppExecutions SyncData `json:"app_executions" datastore:"app_executions"`
|
||||||
|
WorkflowExecutions SyncData `json:"workflow_executions" datastore:"workflow_executions"`
|
||||||
|
Apps SyncData `json:"apps" datastore:"apps"`
|
||||||
|
Workflows SyncData `json:"workflows" datastore:"workflows"`
|
||||||
|
Autocomplete SyncData `json:"autocomplete" datastore:"autocomplete"`
|
||||||
|
Authentication SyncData `json:"authentication" datastore:"authentication"`
|
||||||
|
Schedule SyncData `json:"schedule" datastore:"schedule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyncData struct {
|
type SyncData struct {
|
||||||
Active bool `json:"active" datastore:"active"`
|
Active bool `json:"active" datastore:"active"`
|
||||||
|
Type string `json:"type" datastore:"type"`
|
||||||
|
Name string `json:"name" datastore:"name"`
|
||||||
|
Description string `json:"description" datastore:"description"`
|
||||||
|
Limit int64 `json:"limit" datastore:"limit"`
|
||||||
|
StartDate int64 `json:"start_date" datastore:"start_date"`
|
||||||
|
EndDate int64 `json:"end_date" datastore:"end_date"`
|
||||||
|
DataCollection int64 `json:"data_collection" datastore:"data_collection"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyncConfig struct {
|
type SyncConfig struct {
|
||||||
@@ -86,6 +104,8 @@ type SyncConfig struct {
|
|||||||
// Role is just used for feedback for a user
|
// Role is just used for feedback for a user
|
||||||
type Org struct {
|
type Org struct {
|
||||||
Name string `json:"name" datastore:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
|
Description string `json:"description" datastore:"description"`
|
||||||
|
Image string `json:"image" datastore:"image,noindex"`
|
||||||
Id string `json:"id" datastore:"id"`
|
Id string `json:"id" datastore:"id"`
|
||||||
Org string `json:"org" datastore:"org"`
|
Org string `json:"org" datastore:"org"`
|
||||||
Users []User `json:"users" datastore:"users"`
|
Users []User `json:"users" datastore:"users"`
|
||||||
@@ -307,6 +327,7 @@ type Schedule struct {
|
|||||||
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"`
|
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"`
|
||||||
Id string `json:"id" datastore:"id"`
|
Id string `json:"id" datastore:"id"`
|
||||||
OrgId string `json:"org_id" datastore:"org_id"`
|
OrgId string `json:"org_id" datastore:"org_id"`
|
||||||
|
Environment string `json:"environment" datastore:"environment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Workflow struct {
|
type Workflow struct {
|
||||||
@@ -329,6 +350,7 @@ type Workflow struct {
|
|||||||
Sharing string `json:"sharing" datastore:"sharing"`
|
Sharing string `json:"sharing" datastore:"sharing"`
|
||||||
Org []Org `json:"org,omitempty" datastore:"org"`
|
Org []Org `json:"org,omitempty" datastore:"org"`
|
||||||
ExecutingOrg Org `json:"execution_org,omitempty" datastore:"execution_org"`
|
ExecutingOrg Org `json:"execution_org,omitempty" datastore:"execution_org"`
|
||||||
|
OrgId string `json:"org_id,omitempty" datastore:"org_id"`
|
||||||
WorkflowVariables []struct {
|
WorkflowVariables []struct {
|
||||||
Description string `json:"description" datastore:"description,noindex"`
|
Description string `json:"description" datastore:"description,noindex"`
|
||||||
ID string `json:"id" datastore:"id"`
|
ID string `json:"id" datastore:"id"`
|
||||||
@@ -341,6 +363,7 @@ type Workflow struct {
|
|||||||
Name string `json:"name" datastore:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
Value string `json:"value" datastore:"value,noindex"`
|
Value string `json:"value" datastore:"value,noindex"`
|
||||||
} `json:"execution_variables,omitempty" datastore:"execution_variables"`
|
} `json:"execution_variables,omitempty" datastore:"execution_variables"`
|
||||||
|
ExecutionEnvironment string `json:"execution_environment" datastore:"execution_environment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionResult struct {
|
type ActionResult struct {
|
||||||
@@ -559,6 +582,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
|
|||||||
LastModificationtime: timeNow,
|
LastModificationtime: timeNow,
|
||||||
LastRuntime: timeNow,
|
LastRuntime: timeNow,
|
||||||
Org: orgId,
|
Org: orgId,
|
||||||
|
Environment: "onprem",
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setSchedule(ctx, schedule)
|
err = setSchedule(ctx, schedule)
|
||||||
@@ -664,7 +688,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte("OK"))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Authenticate this one? Can org ID be auth enough?
|
// FIXME: Authenticate this one? Can org ID be auth enough?
|
||||||
@@ -878,6 +902,58 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" {
|
||||||
|
log.Printf("SHOULD WAIT A BIT AND RUN CLOUD STUFF WITH USER INPUT! WAITING!")
|
||||||
|
|
||||||
|
var trigger Trigger
|
||||||
|
err = json.Unmarshal([]byte(actionResult.Result), &trigger)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed unmarshaling actionresult for user input: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgId := workflowExecution.ExecutionOrg
|
||||||
|
if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 {
|
||||||
|
orgId = workflowExecution.Workflow.OrgId
|
||||||
|
}
|
||||||
|
|
||||||
|
err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed userinput handler: %s", err)
|
||||||
|
actionResult.Result = fmt.Sprintf("Cloud error: %s", err)
|
||||||
|
workflowExecution.Results = append(workflowExecution.Results, actionResult)
|
||||||
|
workflowExecution.Status = "ABORTED"
|
||||||
|
err = setWorkflowExecution(ctx, *workflowExecution)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed ")
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully set the execution to waiting.")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err)))
|
||||||
|
} else {
|
||||||
|
log.Printf("Successful userinput handler")
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`)))
|
||||||
|
|
||||||
|
actionResult.Result = "Waiting for user feedback based on configuration"
|
||||||
|
|
||||||
|
workflowExecution.Results = append(workflowExecution.Results, actionResult)
|
||||||
|
workflowExecution.Status = actionResult.Status
|
||||||
|
err = setWorkflowExecution(ctx, *workflowExecution)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed ")
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully set the execution to waiting.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if actionResult.Status == "ABORTED" || actionResult.Status == "FAILURE" {
|
if actionResult.Status == "ABORTED" || actionResult.Status == "FAILURE" {
|
||||||
log.Printf("Actionresult is %s. Should set workflowExecution and exit all running functions", actionResult.Status)
|
log.Printf("Actionresult is %s. Should set workflowExecution and exit all running functions", actionResult.Status)
|
||||||
|
|
||||||
@@ -956,7 +1032,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
for _, result := range workflowExecution.Results {
|
for _, result := range workflowExecution.Results {
|
||||||
if result.Status == "EXECUTING" {
|
if result.Status == "EXECUTING" {
|
||||||
result.Status = actionResult.Status
|
result.Status = actionResult.Status
|
||||||
result.Result = "Aborted because of an unknown error"
|
result.Result = "Aborted because of error in another node"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Result) > 0 {
|
if len(result.Result) > 0 {
|
||||||
@@ -1120,7 +1196,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
// Handles extra statistics stuff when it's done
|
// Handles extra statistics stuff when it's done
|
||||||
// Does autocomplete magic with JSON
|
// Does autocomplete magic with JSON
|
||||||
go handleExecutionStatistics(*workflowExecution)
|
handleExecutionStatistics(*workflowExecution)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1180,10 +1256,10 @@ func handleExecutionStatistics(execution WorkflowExecution) {
|
|||||||
for _, result := range execution.Results {
|
for _, result := range execution.Results {
|
||||||
resultCheck := JSONCheck(result.Result)
|
resultCheck := JSONCheck(result.Result)
|
||||||
if !resultCheck {
|
if !resultCheck {
|
||||||
log.Printf("Result is NOT JSON!")
|
//log.Printf("Result is NOT JSON!")
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Result IS JSON!")
|
//log.Printf("Result IS JSON!")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,7 +1324,7 @@ func handleExecutionStatistics(execution WorkflowExecution) {
|
|||||||
|
|
||||||
log.Printf("Added %d exampleresults to backend", successful)
|
log.Printf("Added %d exampleresults to backend", successful)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("No examplresults necessary to be added for execution %s", execution.ExecutionId)
|
log.Printf("No example results necessary to be added for execution %s", execution.ExecutionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1283,7 +1359,7 @@ func getWorkflows(resp http.ResponseWriter, request *http.Request) {
|
|||||||
// With user, do a search for workflows with user or user's org attached
|
// With user, do a search for workflows with user or user's org attached
|
||||||
q := datastore.NewQuery("workflow").Filter("owner =", user.Id)
|
q := datastore.NewQuery("workflow").Filter("owner =", user.Id)
|
||||||
if user.Role == "admin" {
|
if user.Role == "admin" {
|
||||||
q = datastore.NewQuery("workflow")
|
q = datastore.NewQuery("workflow").Filter("org_id =", user.ActiveOrg.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
var workflows []Workflow
|
var workflows []Workflow
|
||||||
@@ -1363,6 +1439,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
workflow.Owner = user.Id
|
workflow.Owner = user.Id
|
||||||
workflow.Sharing = "private"
|
workflow.Sharing = "private"
|
||||||
workflow.ExecutingOrg = user.ActiveOrg
|
workflow.ExecutingOrg = user.ActiveOrg
|
||||||
|
workflow.OrgId = user.ActiveOrg.Id
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name)
|
log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name)
|
||||||
@@ -1447,7 +1524,27 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("Has %d actions already", len(newActions))
|
log.Printf("Has %d actions already", len(newActions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, item := range workflow.Actions {
|
||||||
|
item.ID = uuid.NewV4().String()
|
||||||
|
newActions = append(newActions, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
newTriggers := []Trigger{}
|
||||||
|
for _, item := range workflow.Triggers {
|
||||||
|
item.Status = "uninitialized"
|
||||||
|
item.ID = uuid.NewV4().String()
|
||||||
|
newTriggers = append(newTriggers, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSchedules := []Schedule{}
|
||||||
|
for _, item := range workflow.Schedules {
|
||||||
|
item.Id = uuid.NewV4().String()
|
||||||
|
newSchedules = append(newSchedules, item)
|
||||||
|
}
|
||||||
|
|
||||||
workflow.Actions = newActions
|
workflow.Actions = newActions
|
||||||
|
workflow.Triggers = newTriggers
|
||||||
|
workflow.Schedules = newSchedules
|
||||||
workflow.IsValid = true
|
workflow.IsValid = true
|
||||||
workflow.Configuration.ExitOnError = false
|
workflow.Configuration.ExitOnError = false
|
||||||
|
|
||||||
@@ -1779,6 +1876,53 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
} else if hook.Id == "" {
|
} else if hook.Id == "" {
|
||||||
trigger.Status = "stopped"
|
trigger.Status = "stopped"
|
||||||
}
|
}
|
||||||
|
} else if trigger.TriggerType == "USERINPUT" {
|
||||||
|
// E.g. check email
|
||||||
|
sms := ""
|
||||||
|
email := ""
|
||||||
|
triggerType := ""
|
||||||
|
triggerInformation := ""
|
||||||
|
for _, item := range trigger.Parameters {
|
||||||
|
if item.Name == "alertinfo" {
|
||||||
|
triggerInformation = item.Value
|
||||||
|
} else if item.Name == "type" {
|
||||||
|
triggerType = item.Value
|
||||||
|
} else if item.Name == "email" {
|
||||||
|
email = item.Value
|
||||||
|
} else if item.Name == "sms" {
|
||||||
|
sms = item.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(triggerType) == 0 {
|
||||||
|
log.Printf("No type specified for user input node")
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No contact option specified in user input"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms
|
||||||
|
_ = triggerInformation
|
||||||
|
if strings.Contains(triggerType, "email") {
|
||||||
|
if email == "test@test.com" {
|
||||||
|
log.Printf("Email isn't specified during save.")
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Email field in user input can't be empty"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Should send email to %s during execution.", email)
|
||||||
|
}
|
||||||
|
if strings.Contains(triggerType, "sms") {
|
||||||
|
if sms == "0000000" {
|
||||||
|
log.Printf("Email isn't specified during save.")
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "SMS field in user input can't be empty"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Should send SMS to %s during execution.", sms)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Println("TRIGGERS")
|
//log.Println("TRIGGERS")
|
||||||
@@ -2201,7 +2345,7 @@ func abortExecution(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("API key %s is correct to abort %s", parsedKey, executionId)
|
log.Printf("API key to abort/finish execution %s is correct.", executionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" || workflowExecution.Status == "FINISHED" {
|
if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" || workflowExecution.Status == "FINISHED" {
|
||||||
@@ -2222,7 +2366,7 @@ func abortExecution(resp http.ResponseWriter, request *http.Request) {
|
|||||||
for _, result := range workflowExecution.Results {
|
for _, result := range workflowExecution.Results {
|
||||||
if result.Status == "EXECUTING" {
|
if result.Status == "EXECUTING" {
|
||||||
result.Status = "ABORTED"
|
result.Status = "ABORTED"
|
||||||
result.Result = "Aborted because of an unknown error"
|
result.Result = "Aborted because of error in another node"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Result) > 0 {
|
if len(result.Result) > 0 {
|
||||||
@@ -2301,7 +2445,7 @@ func cleanupExecutions(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte("OK"))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleExecution(id string, workflow Workflow, request *http.Request) (WorkflowExecution, string, error) {
|
func handleExecution(id string, workflow Workflow, request *http.Request) (WorkflowExecution, string, error) {
|
||||||
@@ -2362,6 +2506,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This one doesn't really matter.
|
// This one doesn't really matter.
|
||||||
|
log.Printf("Running POST execution with data %s", body)
|
||||||
var execution ExecutionRequest
|
var execution ExecutionRequest
|
||||||
err = json.Unmarshal(body, &execution)
|
err = json.Unmarshal(body, &execution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2624,7 +2769,17 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
// If the node is NOT found, it's supposed to be set to SKIPPED,
|
// If the node is NOT found, it's supposed to be set to SKIPPED,
|
||||||
// as it's not a childnode of the startnode
|
// as it's not a childnode of the startnode
|
||||||
// This is a configuration item for the workflow itself.
|
// This is a configuration item for the workflow itself.
|
||||||
if !workflowExecution.Workflow.Configuration.StartFromTop {
|
if len(workflowExecution.Results) > 0 {
|
||||||
|
defaultResults = []ActionResult{}
|
||||||
|
for _, result := range workflowExecution.Results {
|
||||||
|
if result.Status == "WAITING" {
|
||||||
|
result.Status = "FINISHED"
|
||||||
|
result.Result = "Continuing"
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultResults = append(defaultResults, result)
|
||||||
|
}
|
||||||
|
} else if len(workflowExecution.Results) == 0 && !workflowExecution.Workflow.Configuration.StartFromTop {
|
||||||
found := false
|
found := false
|
||||||
for _, nodeId := range childNodes {
|
for _, nodeId := range childNodes {
|
||||||
if nodeId == action.ID {
|
if nodeId == action.ID {
|
||||||
@@ -2652,9 +2807,19 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, trigger := range workflowExecution.Workflow.Triggers {
|
||||||
|
log.Printf("ID: %s vs %s", trigger.ID, workflowExecution.Start)
|
||||||
|
if trigger.ID == workflowExecution.Start {
|
||||||
|
if trigger.AppName == "User Input" {
|
||||||
|
startFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !startFound {
|
if !startFound {
|
||||||
log.Printf("Startnode %s doesn't exist!", workflowExecution.Start)
|
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))
|
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
|
||||||
@@ -3011,10 +3176,61 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
schedule, err := getSchedule(ctx, scheduleId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed finding schedule %s", scheduleId)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Schedule: %#v", schedule)
|
||||||
|
|
||||||
|
if schedule.Environment == "cloud" {
|
||||||
|
log.Printf("[INFO] Should STOP a cloud schedule for workflow %s with schedule ID %s", fileId, scheduleId)
|
||||||
|
// https://shuffler.io/v1/hooks/webhook_80184973-3e82-4852-842e-0290f7f34d7c
|
||||||
|
org, err := getOrg(ctx, user.ActiveOrg.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed finding org %s: %s", org.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Send request to cloud
|
||||||
|
// 2. Remove schedule if success
|
||||||
|
action := CloudSyncJob{
|
||||||
|
Type: "schedule",
|
||||||
|
Action: "stop",
|
||||||
|
OrgId: org.Id,
|
||||||
|
PrimaryItemId: scheduleId,
|
||||||
|
SecondaryItem: schedule.Frequency,
|
||||||
|
ThirdItem: workflow.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = executeCloudAction(action, org.SyncConfig.Apikey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed cloud action STOP schedule", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully ran cloud action STOP schedule")
|
||||||
|
err = DeleteKey(ctx, "schedules", scheduleId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed deleting cloud schedule onprem..")
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed deleting cloud schedule"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = deleteSchedule(ctx, scheduleId)
|
err = deleteSchedule(ctx, scheduleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed deleting schedule: %s", err)
|
log.Printf("Failed deleting schedule: %s", err)
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "Job not found") {
|
if strings.Contains(err.Error(), "Job not found") {
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
@@ -3292,6 +3508,68 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if schedule.Environment == "cloud" {
|
||||||
|
log.Printf("[INFO] Should START a cloud schedule for workflow %s with schedule ID %s", workflow.ID, schedule.Id)
|
||||||
|
// https://shuffler.io/v1/hooks/webhook_80184973-3e82-4852-842e-0290f7f34d7c
|
||||||
|
org, err := getOrg(ctx, user.ActiveOrg.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed finding org %s: %s", org.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 = scheduleId
|
||||||
|
// 2 = schedule (cron, frequency)
|
||||||
|
// 3 = workflowId
|
||||||
|
// 4 = execution argument
|
||||||
|
action := CloudSyncJob{
|
||||||
|
Type: "schedule",
|
||||||
|
Action: "start",
|
||||||
|
OrgId: org.Id,
|
||||||
|
PrimaryItemId: schedule.Id,
|
||||||
|
SecondaryItem: schedule.Frequency,
|
||||||
|
ThirdItem: workflow.ID,
|
||||||
|
FourthItem: schedule.ExecutionArgument,
|
||||||
|
FifthItem: startNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
timeNow := int64(time.Now().Unix())
|
||||||
|
newSchedule := ScheduleOld{
|
||||||
|
Id: schedule.Id,
|
||||||
|
WorkflowId: workflow.ID,
|
||||||
|
StartNode: startNode,
|
||||||
|
Argument: string(schedule.ExecutionArgument),
|
||||||
|
WrappedArgument: parsedBody,
|
||||||
|
CreationTime: timeNow,
|
||||||
|
LastModificationtime: timeNow,
|
||||||
|
LastRuntime: timeNow,
|
||||||
|
Org: org.Id,
|
||||||
|
Frequency: schedule.Frequency,
|
||||||
|
Environment: "cloud",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setSchedule(ctx, newSchedule)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed setting cloud schedule: returning", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Action: %#v", action)
|
||||||
|
err = executeCloudAction(action, org.SyncConfig.Apikey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed cloud action START schedule", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully set up cloud action schedule")
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Done"}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Schedulearg: %s", parsedBody)
|
log.Printf("Schedulearg: %s", parsedBody)
|
||||||
|
|
||||||
err = createSchedule(
|
err = createSchedule(
|
||||||
@@ -3501,9 +3779,9 @@ func getWorkflow(ctx context.Context, id string) (*Workflow, error) {
|
|||||||
return workflow, nil
|
return workflow, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnvironments(ctx context.Context, OrgId string) ([]Environment, error) {
|
func getEnvironments(ctx context.Context, orgId string) ([]Environment, error) {
|
||||||
var environments []Environment
|
var environments []Environment
|
||||||
q := datastore.NewQuery("Environments").Filter("org_id =", OrgId)
|
q := datastore.NewQuery("Environments").Filter("org_id =", orgId)
|
||||||
|
|
||||||
_, err := dbclient.GetAll(ctx, q, &environments)
|
_, err := dbclient.GetAll(ctx, q, &environments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3513,9 +3791,9 @@ func getEnvironments(ctx context.Context, OrgId string) ([]Environment, error) {
|
|||||||
return environments, nil
|
return environments, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAllWorkflows(ctx context.Context) ([]Workflow, error) {
|
func getAllWorkflows(ctx context.Context, orgId string) ([]Workflow, error) {
|
||||||
var allworkflows []Workflow
|
var allworkflows []Workflow
|
||||||
q := datastore.NewQuery("workflow")
|
q := datastore.NewQuery("workflow").Filter("org_id = ", orgId)
|
||||||
|
|
||||||
_, err := dbclient.GetAll(ctx, q, &allworkflows)
|
_, err := dbclient.GetAll(ctx, q, &allworkflows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3656,7 +3934,7 @@ func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
|||||||
private = true
|
private = true
|
||||||
}
|
}
|
||||||
|
|
||||||
q := datastore.NewQuery("workflow")
|
q := datastore.NewQuery("workflow").Filter("org_id = ", user.ActiveOrg.Id)
|
||||||
var workflows []Workflow
|
var workflows []Workflow
|
||||||
_, err = dbclient.GetAll(ctx, q, &workflows)
|
_, err = dbclient.GetAll(ctx, q, &workflows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3833,8 +4111,7 @@ func addAppAuthentication(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - need to be logged in?
|
user, userErr := handleApiAuthentication(resp, request)
|
||||||
_, userErr := handleApiAuthentication(resp, request)
|
|
||||||
if userErr != nil {
|
if userErr != nil {
|
||||||
log.Printf("Api authentication failed in get all apps: %s", userErr)
|
log.Printf("Api authentication failed in get all apps: %s", userErr)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -3903,6 +4180,7 @@ func addAppAuthentication(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appAuth.OrgId = user.ActiveOrg.Id
|
||||||
err = setWorkflowAppAuthDatastore(ctx, appAuth, appAuth.Id)
|
err = setWorkflowAppAuthDatastore(ctx, appAuth, appAuth.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed setting up app auth %s: %s", appAuth.Id, err)
|
log.Printf("Failed setting up app auth %s: %s", appAuth.Id, err)
|
||||||
@@ -4982,7 +5260,7 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if appCounter > 0 {
|
if appCounter > 0 {
|
||||||
log.Printf("Preloaded %d OpenApi apps in %s!", appCounter, extra)
|
log.Printf("Preloaded %d OpenApi apps in folder %s!", appCounter, extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -5467,9 +5745,13 @@ func getWorkflowExecutions(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write(newjson)
|
resp.Write(newjson)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAllSchedules(ctx context.Context) ([]ScheduleOld, error) {
|
func getAllSchedules(ctx context.Context, orgId string) ([]ScheduleOld, error) {
|
||||||
var schedules []ScheduleOld
|
var schedules []ScheduleOld
|
||||||
q := datastore.NewQuery("schedules")
|
|
||||||
|
q := datastore.NewQuery("schedules").Filter("org = ", orgId)
|
||||||
|
if orgId == "ALL" {
|
||||||
|
q = datastore.NewQuery("schedules")
|
||||||
|
}
|
||||||
|
|
||||||
_, err := dbclient.GetAll(ctx, q, &schedules)
|
_, err := dbclient.GetAll(ctx, q, &schedules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -5655,7 +5937,7 @@ func handleDeleteHook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Id != hook.Owner && user.Role != "admin" {
|
if user.Id != hook.Owner && user.Role != "admin" && user.ActiveOrg.Id != hook.OrgId {
|
||||||
log.Printf("Wrong user (%s) for workflow %s", user.Username, hook.Id)
|
log.Printf("Wrong user (%s) for workflow %s", user.Username, hook.Id)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false}`))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
@@ -5678,6 +5960,36 @@ func handleDeleteHook(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("Hook: %#v", hook)
|
||||||
|
if hook.Environment == "cloud" {
|
||||||
|
log.Printf("[INFO] Should STOP cloud webhook https://shuffler.io/api/v1/hooks/webhook_%s", hook.Id)
|
||||||
|
org, err := getOrg(ctx, user.ActiveOrg.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed finding org %s: %s", org.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
action := CloudSyncJob{
|
||||||
|
Type: "webhook",
|
||||||
|
Action: "stop",
|
||||||
|
OrgId: org.Id,
|
||||||
|
PrimaryItemId: hook.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hook.Workflows) > 0 {
|
||||||
|
action.SecondaryItem = hook.Workflows[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = executeCloudAction(action, org.SyncConfig.Apikey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed cloud action STOP execution", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// https://shuffler.io/v1/hooks/webhook_80184973-3e82-4852-842e-0290f7f34d7c
|
||||||
|
}
|
||||||
|
|
||||||
// This is here to force stop and remove the old webhook
|
// This is here to force stop and remove the old webhook
|
||||||
//image := "webhook"
|
//image := "webhook"
|
||||||
//err = removeWebhookFunction(ctx, fileId)
|
//err = removeWebhookFunction(ctx, fileId)
|
||||||
@@ -5837,3 +6149,85 @@ func removeOutlookTriggerFunction(ctx context.Context, triggerId string) error {
|
|||||||
_ = resp
|
_ = resp
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleUserInput(trigger Trigger, organizationId string, workflowId string, referenceExecution string) error {
|
||||||
|
// E.g. check email
|
||||||
|
sms := ""
|
||||||
|
email := ""
|
||||||
|
triggerType := ""
|
||||||
|
triggerInformation := ""
|
||||||
|
for _, item := range trigger.Parameters {
|
||||||
|
if item.Name == "alertinfo" {
|
||||||
|
triggerInformation = item.Value
|
||||||
|
} else if item.Name == "type" {
|
||||||
|
triggerType = item.Value
|
||||||
|
} else if item.Name == "email" {
|
||||||
|
email = item.Value
|
||||||
|
} else if item.Name == "sms" {
|
||||||
|
sms = item.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(triggerType) == 0 {
|
||||||
|
log.Printf("No type specified for user input node")
|
||||||
|
return errors.New("No type specified for user input node")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms
|
||||||
|
ctx := context.Background()
|
||||||
|
startNode := trigger.ID
|
||||||
|
if strings.Contains(triggerType, "email") {
|
||||||
|
action := CloudSyncJob{
|
||||||
|
Type: "user_input",
|
||||||
|
Action: "send_email",
|
||||||
|
OrgId: organizationId,
|
||||||
|
PrimaryItemId: workflowId,
|
||||||
|
SecondaryItem: startNode,
|
||||||
|
ThirdItem: triggerInformation,
|
||||||
|
FourthItem: email,
|
||||||
|
FifthItem: referenceExecution,
|
||||||
|
}
|
||||||
|
|
||||||
|
org, err := getOrg(ctx, organizationId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed email send to cloud (1): %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = executeCloudAction(action, org.SyncConfig.Apikey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed email send to cloud (2): %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Should send email to %s during execution.", email)
|
||||||
|
}
|
||||||
|
if strings.Contains(triggerType, "sms") {
|
||||||
|
action := CloudSyncJob{
|
||||||
|
Type: "user_input",
|
||||||
|
Action: "send_sms",
|
||||||
|
OrgId: organizationId,
|
||||||
|
PrimaryItemId: workflowId,
|
||||||
|
SecondaryItem: startNode,
|
||||||
|
ThirdItem: triggerInformation,
|
||||||
|
FourthItem: sms,
|
||||||
|
FifthItem: referenceExecution,
|
||||||
|
}
|
||||||
|
|
||||||
|
org, err := getOrg(ctx, organizationId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed sms send to cloud (3): %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = executeCloudAction(action, org.SyncConfig.Apikey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed sms send to cloud (4): %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Should send SMS to %s during execution.", sms)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+1
-11
@@ -23,14 +23,4 @@
|
|||||||
#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_3ceff795-ce9a-43a2-a2f5-d4401a6e772d" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut'
|
#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_3ceff795-ce9a-43a2-a2f5-d4401a6e772d" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut'
|
||||||
#curl POST "https://europe-west1-shuffler.cloudfunctions.net/outlooktrigger_be4dbb0a-d396-4544-bc36-e57d1bdb2e40" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut' -vvv
|
#curl POST "https://europe-west1-shuffler.cloudfunctions.net/outlooktrigger_be4dbb0a-d396-4544-bc36-e57d1bdb2e40" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut' -vvv
|
||||||
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
curl -X POST "http://localhost:5002/api/v1/hooks/webhook_f22b5e54-e55d-48e5-a1d1-f40453513fd3" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}'
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
curl -X POST "http://localhost:5001/api/v1/hooks/webhook_e38e0768-191c-42ec-a021-cf17a4339473" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}' -v
|
|
||||||
|
|||||||
+6
-8
@@ -2,7 +2,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
#build: ./frontend
|
#build: ./frontend
|
||||||
image: frikky/shuffle:frontend
|
image: ghcr.io/frikky/shuffle-frontend:0.8.0
|
||||||
container_name: shuffle-frontend
|
container_name: shuffle-frontend
|
||||||
hostname: shuffle-frontend
|
hostname: shuffle-frontend
|
||||||
ports:
|
ports:
|
||||||
@@ -16,8 +16,8 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
backend:
|
backend:
|
||||||
build: ./backend
|
#build: ./backend
|
||||||
image: frikky/shuffle:backend
|
image: ghcr.io/frikky/shuffle-backend:0.8.0
|
||||||
container_name: shuffle-backend
|
container_name: shuffle-backend
|
||||||
hostname: ${BACKEND_HOSTNAME}
|
hostname: ${BACKEND_HOSTNAME}
|
||||||
# Here for debugging:
|
# Here for debugging:
|
||||||
@@ -43,7 +43,7 @@ services:
|
|||||||
- database
|
- database
|
||||||
orborus:
|
orborus:
|
||||||
#build: ./functions/onprem/orborus
|
#build: ./functions/onprem/orborus
|
||||||
image: frikky/shuffle:orborus
|
image: ghcr.io/frikky/shuffle-orborus:0.8.0
|
||||||
container_name: shuffle-orborus
|
container_name: shuffle-orborus
|
||||||
hostname: shuffle-orborus
|
hostname: shuffle-orborus
|
||||||
networks:
|
networks:
|
||||||
@@ -51,10 +51,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
- SHUFFLE_APP_SDK_VERSION=0.6.0
|
- SHUFFLE_APP_SDK_VERSION=0.8.0
|
||||||
- SHUFFLE_APP_SDK_KALI_VERSION=0.6.0
|
- SHUFFLE_WORKER_VERSION=0.8.0
|
||||||
- SHUFFLE_APP_SDK_BLACKARCH_VERSION=0.6.0
|
|
||||||
- SHUFFLE_WORKER_VERSION=0.6.0
|
|
||||||
- ORG_ID=${ORG_ID}
|
- ORG_ID=${ORG_ID}
|
||||||
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
||||||
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
|
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import SettingsPage from "./views/SettingsPage";
|
|||||||
|
|
||||||
import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles';
|
import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles';
|
||||||
|
|
||||||
import AlertTemplate from "react-alert-template-basic";
|
import AlertTemplate from "./components/AlertTemplate";
|
||||||
import { positions, Provider } from "react-alert";
|
import { positions, Provider } from "react-alert";
|
||||||
|
|
||||||
// Production - backend proxy forwarding in nginx
|
// Production - backend proxy forwarding in nginx
|
||||||
@@ -39,6 +39,7 @@ var globalUrl = window.location.origin
|
|||||||
// CORS used for testing purposes. Should only happen with specific port and http
|
// CORS used for testing purposes. Should only happen with specific port and http
|
||||||
if (window.location.protocol == "http:" && window.location.port === "3000") {
|
if (window.location.protocol == "http:" && window.location.port === "3000") {
|
||||||
globalUrl = "http://localhost:5001"
|
globalUrl = "http://localhost:5001"
|
||||||
|
//globalUrl = "http://localhost:5002"
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = createMuiTheme({
|
const theme = createMuiTheme({
|
||||||
@@ -99,7 +100,7 @@ const App = (message, props) => {
|
|||||||
//console.log(responseJson.success)
|
//console.log(responseJson.success)
|
||||||
setUserData(responseJson)
|
setUserData(responseJson)
|
||||||
setIsLoggedIn(true)
|
setIsLoggedIn(true)
|
||||||
console.log("Cookies: ", cookies)
|
//console.log("Cookies: ", cookies)
|
||||||
|
|
||||||
// Updating cookie every request
|
// Updating cookie every request
|
||||||
for (var key in responseJson["cookies"]) {
|
for (var key in responseJson["cookies"]) {
|
||||||
@@ -116,8 +117,8 @@ const App = (message, props) => {
|
|||||||
// Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies)
|
// Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies)
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
timeout: 5000,
|
timeout: 9000,
|
||||||
position: positions.BOTTOM_CENTER
|
position: positions.BOTTOM_LEFT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const includedData = window.location.pathname === "/home" || window.location.pathname === "/features" ?
|
const includedData = window.location.pathname === "/home" || window.location.pathname === "/features" ?
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import InfoIcon from './icons/InfoIcon'
|
import InfoIcon from '@material-ui/icons/Info';
|
||||||
import SuccessIcon from './icons/SuccessIcon'
|
import CheckIcon from '@material-ui/icons/Check';
|
||||||
import ErrorIcon from './icons/ErrorIcon'
|
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
|
||||||
import CloseIcon from './icons/CloseIcon'
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
|
||||||
const alertStyle = {
|
const alertStyle = {
|
||||||
backgroundColor: '#151515',
|
backgroundColor: 'rgba(0,0,0,0.9)',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
padding: '10px',
|
padding: 15,
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
borderRadius: '3px',
|
borderRadius: '3px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
boxShadow: '0px 2px 2px 2px rgba(0, 0, 0, 0.03)',
|
boxShadow: '0px 2px 2px 2px rgba(0, 0, 0, 0.03)',
|
||||||
fontFamily: 'Arial',
|
width: 400,
|
||||||
width: '300px',
|
boxSizing: 'border-box',
|
||||||
boxSizing: 'border-box'
|
zIndex: 100001,
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonStyle = {
|
const buttonStyle = {
|
||||||
@@ -30,10 +31,10 @@ const buttonStyle = {
|
|||||||
const AlertTemplate = ({ message, options, style, close }) => {
|
const AlertTemplate = ({ message, options, style, close }) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ ...alertStyle, ...style }}>
|
<div style={{ ...alertStyle, ...style }}>
|
||||||
{options.type === 'info' && <InfoIcon />}
|
{options.type === 'info' && <InfoIcon style={{color: "white"}} />}
|
||||||
{options.type === 'success' && <SuccessIcon />}
|
{options.type === 'success' && <CheckIcon style={{color: "green", }}/>}
|
||||||
{options.type === 'error' && <ErrorIcon />}
|
{options.type === 'error' && <ErrorOutlineIcon style={{color: "red"}} />}
|
||||||
<span style={{ flex: 2 }}>{message}</span>
|
<Typography style={{marginLeft: 15, flex: 2 }}>{message}</Typography>
|
||||||
<button onClick={close} style={buttonStyle}>
|
<button onClick={close} style={buttonStyle}>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
|||||||
|
import { createBrowserHistory } from 'history';
|
||||||
|
|
||||||
|
var localExport
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localExport = createBrowserHistory({forceRefresh: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default localExport
|
||||||
+488
-62
@@ -1,7 +1,10 @@
|
|||||||
import React, { useEffect} from 'react';
|
import React, { useEffect} from 'react';
|
||||||
|
|
||||||
|
import { makeStyles } from '@material-ui/styles';
|
||||||
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 Card from '@material-ui/core/Card';
|
||||||
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Switch from '@material-ui/core/Switch';
|
import Switch from '@material-ui/core/Switch';
|
||||||
@@ -20,12 +23,13 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
|||||||
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
|
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
|
import Zoom from '@material-ui/core/Zoom';
|
||||||
|
|
||||||
import { useAlert } from "react-alert";
|
import { useAlert } from "react-alert";
|
||||||
|
|
||||||
import { Dialog, DialogTitle, DialogActions, DialogContent } from '@material-ui/core';
|
import { Dialog, DialogTitle, DialogActions, DialogContent } from '@material-ui/core';
|
||||||
import { useTheme } from '@material-ui/core/styles';
|
import { useTheme } from '@material-ui/core/styles';
|
||||||
|
import HandlePayment from './HandlePayment'
|
||||||
|
import OrgHeader from '../components/OrgHeader'
|
||||||
|
|
||||||
import PolymerIcon from '@material-ui/icons/Polymer';
|
import PolymerIcon from '@material-ui/icons/Polymer';
|
||||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
||||||
@@ -41,10 +45,19 @@ import ScheduleIcon from '@material-ui/icons/Schedule';
|
|||||||
import CloudIcon from '@material-ui/icons/Cloud';
|
import CloudIcon from '@material-ui/icons/Cloud';
|
||||||
import BusinessIcon from '@material-ui/icons/Business';
|
import BusinessIcon from '@material-ui/icons/Business';
|
||||||
|
|
||||||
const Admin = (props) => {
|
|
||||||
const { globalUrl } = props;
|
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
notchedOutline: {
|
||||||
|
borderColor: "#f85a3e !important"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Admin = (props) => {
|
||||||
|
const { globalUrl, userdata } = props;
|
||||||
|
|
||||||
|
var upload = ""
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const classes = useStyles();
|
||||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||||
const [modalUser, setModalUser] = React.useState({});
|
const [modalUser, setModalUser] = React.useState({});
|
||||||
const [modalOpen, setModalOpen] = React.useState(false);
|
const [modalOpen, setModalOpen] = React.useState(false);
|
||||||
@@ -54,11 +67,13 @@ const Admin = (props) => {
|
|||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||||
|
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
|
||||||
const [loginInfo, setLoginInfo] = React.useState("");
|
const [loginInfo, setLoginInfo] = React.useState("");
|
||||||
const [curTab, setCurTab] = React.useState(0);
|
const [curTab, setCurTab] = React.useState(0);
|
||||||
const [users, setUsers] = React.useState([]);
|
const [users, setUsers] = React.useState([]);
|
||||||
const [organizations, setOrganizations] = React.useState([]);
|
const [organizations, setOrganizations] = React.useState([]);
|
||||||
const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
|
const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
|
||||||
|
const [userSettings, setUserSettings] = React.useState({});
|
||||||
|
|
||||||
const [environments, setEnvironments] = React.useState([]);
|
const [environments, setEnvironments] = React.useState([]);
|
||||||
const [authentication, setAuthentication] = React.useState([]);
|
const [authentication, setAuthentication] = React.useState([]);
|
||||||
@@ -70,6 +85,7 @@ const Admin = (props) => {
|
|||||||
const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false)
|
const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false)
|
||||||
const [showArchived, setShowArchived] = React.useState(false)
|
const [showArchived, setShowArchived] = React.useState(false)
|
||||||
|
|
||||||
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
|
||||||
const getApps = () => {
|
const getApps = () => {
|
||||||
fetch(globalUrl+"/api/v1/workflows/apps", {
|
fetch(globalUrl+"/api/v1/workflows/apps", {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -157,7 +173,7 @@ const Admin = (props) => {
|
|||||||
alert.error("Failed stopping schedule")
|
alert.error("Failed stopping schedule")
|
||||||
} else {
|
} else {
|
||||||
getAppAuthentication()
|
getAppAuthentication()
|
||||||
alert.success("Successfully stopped schedule!")
|
alert.success("Successfully deleted authentication!")
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -237,13 +253,17 @@ const Admin = (props) => {
|
|||||||
getOrgs()
|
getOrgs()
|
||||||
if (disableSync) {
|
if (disableSync) {
|
||||||
alert.success("Successfully disabled sync!")
|
alert.success("Successfully disabled sync!")
|
||||||
|
setOrgSyncResponse("Successfully disabled syncronization")
|
||||||
} else {
|
} else {
|
||||||
alert.success("Sync successfully set up!")
|
alert.success("Cloud Syncronization successfully set up!")
|
||||||
|
setOrgSyncResponse("Successfully started syncronization. Cloud features you now have access to can be seen below.")
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync
|
selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync
|
||||||
setSelectedOrganization(selectedOrganization)
|
setSelectedOrganization(selectedOrganization)
|
||||||
setCloudSyncApikey("")
|
setCloudSyncApikey("")
|
||||||
|
|
||||||
|
handleGetOrg(userdata.active_org.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -252,6 +272,10 @@ 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/users/passwordchange';
|
const url = globalUrl + '/api/v1/users/passwordchange';
|
||||||
@@ -312,6 +336,58 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleGetOrg = (orgId) => {
|
||||||
|
// Just use this one?
|
||||||
|
var baseurl = globalUrl
|
||||||
|
const url = baseurl + '/api/v1/orgs/'+orgId
|
||||||
|
fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.status === 401) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then(responseJson => {
|
||||||
|
if (responseJson["success"] === false) {
|
||||||
|
alert.error("Failed getting org: ", responseJson.readon)
|
||||||
|
} else {
|
||||||
|
setSelectedOrganization(responseJson)
|
||||||
|
var lists = {
|
||||||
|
"active": {
|
||||||
|
"triggers": [],
|
||||||
|
"features": [],
|
||||||
|
"sync": [],
|
||||||
|
},
|
||||||
|
"inactive": {
|
||||||
|
"triggers": [],
|
||||||
|
"features": [],
|
||||||
|
"sync": [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: Set up features
|
||||||
|
Object.keys(responseJson.sync_features).map(function(key, index) {
|
||||||
|
//console.log(responseJson.sync_features[key])
|
||||||
|
})
|
||||||
|
|
||||||
|
//setOrgName(responseJson.name)
|
||||||
|
//setOrgDescription(responseJson.description)
|
||||||
|
setOrganizationFeatures(lists)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log("Error getting org: ", error)
|
||||||
|
alert.error("Error getting current organization")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const submitUser = (data) => {
|
const submitUser = (data) => {
|
||||||
console.log("INPUT: ", data)
|
console.log("INPUT: ", data)
|
||||||
|
|
||||||
@@ -492,6 +568,7 @@ const Admin = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
|
console.log(responseJson)
|
||||||
setSchedules(responseJson)
|
setSchedules(responseJson)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -518,7 +595,7 @@ const Admin = (props) => {
|
|||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson.success) {
|
if (responseJson.success) {
|
||||||
console.log(responseJson.data)
|
//console.log(responseJson.data)
|
||||||
setAuthentication(responseJson.data)
|
setAuthentication(responseJson.data)
|
||||||
} else {
|
} else {
|
||||||
alert.error("Failed getting authentications")
|
alert.error("Failed getting authentications")
|
||||||
@@ -604,9 +681,59 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSettings = () => {
|
||||||
|
fetch(globalUrl+"/api/v1/getsettings", {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 when getting settings :O!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
setUserSettings(responseJson)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (firstRequest) {
|
if (firstRequest) {
|
||||||
setFirstRequest(false)
|
setFirstRequest(false)
|
||||||
getUsers()
|
if (!isCloud) {
|
||||||
|
getUsers()
|
||||||
|
} else {
|
||||||
|
getSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
const views = {
|
||||||
|
"organization": 0,
|
||||||
|
"users": 1,
|
||||||
|
"app_auth": 2,
|
||||||
|
"environments": 3,
|
||||||
|
"schedules": 4,
|
||||||
|
"categories": 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.match.params.key !== undefined) {
|
||||||
|
const tmpitem = views[props.match.params.key]
|
||||||
|
if (tmpitem !== undefined) {
|
||||||
|
setCurTab(tmpitem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedOrganization.id === undefined && userdata !== undefined && userdata.active_org !== undefined) {
|
||||||
|
//setSelectedOrganization(userdata.active_org)
|
||||||
|
handleGetOrg(userdata.active_org.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const paperStyle = {
|
const paperStyle = {
|
||||||
@@ -822,6 +949,8 @@ const Admin = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
const GridItem = (props) => {
|
const GridItem = (props) => {
|
||||||
|
const [expanded, setExpanded] = React.useState(false)
|
||||||
|
|
||||||
const primary = props.data.primary
|
const primary = props.data.primary
|
||||||
const secondary = props.data.secondary
|
const secondary = props.data.secondary
|
||||||
const primaryIcon = props.data.icon
|
const primaryIcon = props.data.icon
|
||||||
@@ -831,19 +960,36 @@ const Admin = (props) => {
|
|||||||
<CloseIcon style={{color: "red"}} />
|
<CloseIcon style={{color: "red"}} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid item xs={6}>
|
<Grid item xs={4} style={{cursor: "pointer"}} onClick={() => {
|
||||||
<ListItem>
|
setExpanded(!expanded)
|
||||||
<ListItemAvatar>
|
}}>
|
||||||
<Avatar>
|
<Card style={{margin: 4, backgroundColor: theme.palette.inputColor, color: "white", minHeight: expanded ? 200 : "inherit", maxHeight: expanded ? 200 : "inherit",}}>
|
||||||
{primaryIcon}
|
<ListItem>
|
||||||
</Avatar>
|
<ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<Avatar>
|
||||||
<ListItemText
|
{primaryIcon}
|
||||||
primary={primary}
|
</Avatar>
|
||||||
secondary={secondary}
|
</ListItemAvatar>
|
||||||
/>
|
<ListItemText
|
||||||
{secondaryIcon}
|
style={{textTransform: "capitalize"}}
|
||||||
</ListItem>
|
primary={primary}
|
||||||
|
/>
|
||||||
|
{secondaryIcon}
|
||||||
|
</ListItem>
|
||||||
|
{expanded ?
|
||||||
|
<div style={{padding: 15}}>
|
||||||
|
<Typography>
|
||||||
|
Usage: {props.data.limit === 0 ? "Infinite" : <span>{props.data.usage} / {props.data.limit}</span>}
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
Data sharing: {props.data.data_collection}
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
Description: {secondary}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -853,15 +999,21 @@ const Admin = (props) => {
|
|||||||
{
|
{
|
||||||
"primary": "Workflows",
|
"primary": "Workflows",
|
||||||
"secondary": "",
|
"secondary": "",
|
||||||
"active": false,
|
"active": true,
|
||||||
"icon": <PolymerIcon style={{color: itemColor}}/>,
|
"icon": <PolymerIcon style={{color: itemColor}}/>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"primary": "Apps",
|
"primary": "Apps",
|
||||||
"secondary": "",
|
"secondary": "",
|
||||||
"active": false,
|
"active": true,
|
||||||
"icon": <AppsIcon style={{color: itemColor}}/>,
|
"icon": <AppsIcon style={{color: itemColor}}/>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"primary": "Organization",
|
||||||
|
"secondary": "",
|
||||||
|
"active": true,
|
||||||
|
"icon": <BusinessIcon style={{color: itemColor}}/>,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const cloudSyncModal =
|
const cloudSyncModal =
|
||||||
@@ -881,7 +1033,7 @@ const Admin = (props) => {
|
|||||||
Enable cloud features
|
Enable cloud features
|
||||||
</span></DialogTitle>
|
</span></DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
What does <a href="https://shuffler.io/docs/hybrid#cloud_sync" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
|
What does <a href="https://shuffler.io/docs/organizations#cloud_sync" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
|
||||||
<div style={{display: "flex", marginBottom: 20, }}>
|
<div style={{display: "flex", marginBottom: 20, }}>
|
||||||
<TextField
|
<TextField
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -905,7 +1057,7 @@ const Admin = (props) => {
|
|||||||
setCloudSyncApikey(event.target.value)
|
setCloudSyncApikey(event.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
|
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{ marginLeft: 15, height: 50, borderRadius: "0px" }} onClick={() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
enableCloudSync(
|
enableCloudSync(
|
||||||
cloudSyncApikey,
|
cloudSyncApikey,
|
||||||
@@ -934,13 +1086,236 @@ const Admin = (props) => {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
* New triggers (userinput, hotmail realtime)<div/>
|
* New triggers (userinput, hotmail realtime)<div/>
|
||||||
* Execute in the cloud rather than onprem<div/>
|
* Execute in the cloud rather than onprem<div/>
|
||||||
* Apps can be built in the cloud<div/>
|
* Apps can be built in the cloud<div/>
|
||||||
* Easily share apps and workflows<div/>
|
* Easily share apps and workflows<div/>
|
||||||
* Access to powerful cloud search
|
* Access to powerful cloud search
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
const cancelSubscriptions = (subscription_id) => {
|
||||||
|
console.log(selectedOrganization)
|
||||||
|
const data = {
|
||||||
|
"subscription_id": subscription_id,
|
||||||
|
"action": "cancel",
|
||||||
|
"org_id": selectedOrganization.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
|
||||||
|
fetch(url, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: 'include',
|
||||||
|
crossDomain: true,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Error in response")
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGetOrg(selectedOrganization.id)
|
||||||
|
return response.json();
|
||||||
|
}).then(function(responseJson) {
|
||||||
|
if (responseJson.success !== undefined && responseJson.success) {
|
||||||
|
alert.success("Successfully stopped subscription!")
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert.error("Failed stopping subscription. Please contact us.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.log("Error: ", error)
|
||||||
|
alert.error("Failed stopping subscription. Please contact us.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const organizationView = curTab === 0 && selectedOrganization.id !== undefined ?
|
||||||
|
<div>
|
||||||
|
<div style={{ marginTop: 20, marginBottom: 20, }}>
|
||||||
|
<h2 style={{ display: "inline", }}>Organization overview</h2>
|
||||||
|
<span style={{ marginLeft: 25 }}>
|
||||||
|
On this page you can configure individual parts of your organization. <a target="_blank" href="https://shuffler.io/docs/organizations#organization" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{selectedOrganization.id === undefined ?
|
||||||
|
<div style={{height: 250}}/>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
{selectedOrganization.name.length > 0 ?
|
||||||
|
<OrgHeader setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
|
||||||
|
: null}
|
||||||
|
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
||||||
|
<Typography variant="h6" style={{marginBottom: "10px", color: "white"}}>Cloud syncronization</Typography>
|
||||||
|
What does <a href="https://shuffler.io/docs/organizations#cloud_sync" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do? Cloud syncronization is a way of getting more out of Shuffle. Shuffle will <b>ALWAYS</b> make every option open source, but features relying on other users can't be done without a collaborative approach.
|
||||||
|
|
||||||
|
{isCloud ?
|
||||||
|
<div style={{marginTop: 15, display: "flex"}}>
|
||||||
|
<div style={{flex: 1}}>
|
||||||
|
<Typography style={{}}>
|
||||||
|
Currently syncronizing: {selectedOrganization.cloud_sync_active === true ? "True" : "False"}
|
||||||
|
</Typography>
|
||||||
|
{selectedOrganization.cloud_sync_active ?
|
||||||
|
<Typography style={{}}>
|
||||||
|
Syncronization interval: {selectedOrganization.sync_config.interval === 0 ? "60" : selectedOrganization.sync_config.interval}
|
||||||
|
</Typography>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
<Typography style={{whiteSpace: "nowrap", marginTop: 25, marginRight: 10}}>
|
||||||
|
Your Apikey
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
color="primary"
|
||||||
|
style={{backgroundColor: theme.palette.inputColor, }}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: "50px",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth={true}
|
||||||
|
disabled={true}
|
||||||
|
autoComplete="cloud apikey"
|
||||||
|
id="apikey_field"
|
||||||
|
margin="normal"
|
||||||
|
placeholder="Cloud Apikey"
|
||||||
|
variant="outlined"
|
||||||
|
defaultValue={userSettings.apikey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
<div style={{display: "flex", marginBottom: 20, }}>
|
||||||
|
<TextField
|
||||||
|
color="primary"
|
||||||
|
style={{backgroundColor: theme.palette.inputColor, marginRight: 10, }}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: "50px",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth={true}
|
||||||
|
disabled={selectedOrganization.cloud_sync}
|
||||||
|
autoComplete="cloud apikey"
|
||||||
|
id="apikey_field"
|
||||||
|
margin="normal"
|
||||||
|
placeholder="Cloud Apikey"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(event) => {
|
||||||
|
setCloudSyncApikey(event.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{marginTop: 15, height: 50, width: 150,}} onClick={() => {
|
||||||
|
setLoading(true)
|
||||||
|
enableCloudSync(
|
||||||
|
cloudSyncApikey,
|
||||||
|
selectedOrganization,
|
||||||
|
selectedOrganization.cloud_sync,
|
||||||
|
)
|
||||||
|
}} color="primary">
|
||||||
|
{selectedOrganization.cloud_sync ?
|
||||||
|
"Stop sync"
|
||||||
|
:
|
||||||
|
"Start sync"
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{orgSyncResponse.length > 0 ?
|
||||||
|
<Typography style={{marginTop: 5, marginBottom: 10}}>
|
||||||
|
Message from Shuffle: <b>{orgSyncResponse}</b>
|
||||||
|
</Typography>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>Cloud sync features</Typography>
|
||||||
|
<Grid container style={{width: "100%", marginBottom: 15, }}>
|
||||||
|
{Object.keys(selectedOrganization.sync_features).map(function(key, index) {
|
||||||
|
if (key === "schedule") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = selectedOrganization.sync_features[key]
|
||||||
|
const newkey = key.replace("_", " ")
|
||||||
|
const griditem = {
|
||||||
|
"primary": newkey,
|
||||||
|
"secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
|
||||||
|
"limit": item.limit,
|
||||||
|
"usage": 0,
|
||||||
|
"data_collection": "None",
|
||||||
|
"active": item.active,
|
||||||
|
"icon": <PolymerIcon style={{color: itemColor}}/>,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Zoom key={index} >
|
||||||
|
<GridItem data={griditem} />
|
||||||
|
</Zoom>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
||||||
|
{isCloud && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ?
|
||||||
|
<div style={{marginTop: 30, marginBottom: 20}}>
|
||||||
|
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>
|
||||||
|
Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""}
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={3} style={{marginTop: 15}}>
|
||||||
|
{selectedOrganization.subscriptions.reverse().map((sub, index) => {
|
||||||
|
return (
|
||||||
|
<Grid item key={index} xs={4}>
|
||||||
|
<Card elevation={6} style={{backgroundColor: theme.palette.inputColor, color: "white", padding: 25, textAlign: "left",}}>
|
||||||
|
<b>Type</b>: {sub.level}<div/>
|
||||||
|
<b>Recurrence</b>: {sub.recurrence}<div/>
|
||||||
|
{sub.active ?
|
||||||
|
<div>
|
||||||
|
<b>Started</b>: {new Date(sub.startdate*1000).toISOString()}<div/>
|
||||||
|
<Button variant="outlined" color="primary" style={{marginTop: 15}} onClick={() => {
|
||||||
|
cancelSubscriptions(sub.reference)
|
||||||
|
}}>
|
||||||
|
Cancel subscription
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
<b>Cancelled</b>: {new Date(sub.cancellationdate*1000).toISOString()}<div/>
|
||||||
|
<Typography color="textSecondary">
|
||||||
|
<b>Status</b>: Deactivated
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
<Divider style={{ marginTop: 20, backgroundColor: theme.palette.inputColor }} />
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style={{backgroundColor: "#1f2023", paddingTop: 25,}}>
|
||||||
|
<HandlePayment stripeKey={props.stripeKey} userdata={userdata} globalUrl={globalUrl} {...props} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
|
||||||
const modalView =
|
const modalView =
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -956,10 +1331,10 @@ const Admin = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle><span style={{ color: "white" }}>
|
<DialogTitle><span style={{ color: "white" }}>
|
||||||
{curTab === 0 ? "Add user" : "Add environment"}
|
{curTab === 1 ? "Add user" : "Add environment"}
|
||||||
</span></DialogTitle>
|
</span></DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{curTab === 0 ?
|
{curTab === 1 ?
|
||||||
<div>
|
<div>
|
||||||
Username
|
Username
|
||||||
<TextField
|
<TextField
|
||||||
@@ -1004,7 +1379,7 @@ const Admin = (props) => {
|
|||||||
onChange={(event) => changeModalData("Password", event.target.value)}
|
onChange={(event) => changeModalData("Password", event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
: curTab === 2 ?
|
: curTab === 3 ?
|
||||||
<div>
|
<div>
|
||||||
Environment Name
|
Environment Name
|
||||||
<TextField
|
<TextField
|
||||||
@@ -1035,9 +1410,9 @@ const Admin = (props) => {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" style={{ borderRadius: "0px" }} onClick={() => {
|
<Button variant="contained" style={{ borderRadius: "0px" }} onClick={() => {
|
||||||
if (curTab === 0) {
|
if (curTab === 1) {
|
||||||
submitUser(modalUser)
|
submitUser(modalUser)
|
||||||
} else if (curTab === 2) {
|
} else if (curTab === 3) {
|
||||||
submitEnvironment(modalUser)
|
submitEnvironment(modalUser)
|
||||||
}
|
}
|
||||||
}} color="primary">
|
}} color="primary">
|
||||||
@@ -1046,11 +1421,11 @@ const Admin = (props) => {
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
const usersView = curTab === 0 ?
|
const usersView = curTab === 1 ?
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginTop: 20, marginBottom: 20, }}>
|
<div style={{ marginTop: 20, marginBottom: 20, }}>
|
||||||
<h2 style={{ display: "inline", }}>User management</h2>
|
<h2 style={{ display: "inline", }}>User management</h2>
|
||||||
<span style={{ marginLeft: 25 }}>Add, edit, block or change passwords</span>
|
<span style={{ marginLeft: 25 }}>Add, edit, block or change passwords. <a target="_blank" href="https://shuffler.io/docs/organizations#user_management" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a></span>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<Button
|
<Button
|
||||||
@@ -1068,10 +1443,12 @@ const Admin = (props) => {
|
|||||||
primary="Username"
|
primary="Username"
|
||||||
style={{ minWidth: 200, maxWidth: 200 }}
|
style={{ minWidth: 200, maxWidth: 200 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="API key"
|
primary="API key"
|
||||||
style={{ minWidth: 350, maxWidth: 350, overflow: "hidden" }}
|
style={{ minWidth: 350, maxWidth: 350, overflow: "hidden" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Role"
|
primary="Role"
|
||||||
style={{ minWidth: 150, maxWidth: 150 }}
|
style={{ minWidth: 150, maxWidth: 150 }}
|
||||||
@@ -1092,10 +1469,12 @@ const Admin = (props) => {
|
|||||||
primary={data.username}
|
primary={data.username}
|
||||||
style={{ minWidth: 200, maxWidth: 200 }}
|
style={{ minWidth: 200, maxWidth: 200 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={data.apikey === undefined || data.apikey.length === 0 ? "" : data.apikey}
|
primary={data.apikey === undefined || data.apikey.length === 0 ? "" : data.apikey}
|
||||||
style={{ maxWidth: 350, minWidth: 350, }}
|
style={{ maxWidth: 350, minWidth: 350, }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary=
|
primary=
|
||||||
{<Select
|
{<Select
|
||||||
@@ -1137,7 +1516,15 @@ const Admin = (props) => {
|
|||||||
>
|
>
|
||||||
Edit user
|
Edit user
|
||||||
</Button>
|
</Button>
|
||||||
</ListItemText>
|
<Button
|
||||||
|
style={{}}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => generateApikey(data.id)}
|
||||||
|
>
|
||||||
|
Get new API key
|
||||||
|
</Button>
|
||||||
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -1145,37 +1532,53 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const schedulesView = curTab === 3 ?
|
const schedulesView = curTab === 4 ?
|
||||||
<div>
|
<div>
|
||||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||||
<h2 style={{display: "inline",}}>Schedules</h2>
|
<h2 style={{display: "inline",}}>Schedules</h2>
|
||||||
<span style={{marginLeft: 25}}>Schedules used in Workflows. Makes locating and control easier.</span>
|
<span style={{marginLeft: 25}}>Schedules used in Workflows. Makes locating and control easier. <a target="_blank" href="https://shuffler.io/docs/organizations#schedules" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a></span>
|
||||||
</div>
|
</div>
|
||||||
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
|
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
|
||||||
<List>
|
<List>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Interval (seconds)"
|
primary="Interval"
|
||||||
style={{maxWidth: 200}}
|
style={{maxWidth: 200, minWidth: 200}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Environment"
|
||||||
|
style={{maxWidth: 150, minWidth: 150}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Workflow"
|
||||||
|
style={{maxWidth: 315, minWidth: 315}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Argument"
|
primary="Argument"
|
||||||
style={{maxWidth: 400, overflow: "hidden"}}
|
style={{minWidth: 300, maxWidth: 300, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Actions"
|
primary="Actions"
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{schedules === undefined || schedules === null ? null : schedules.map(schedule => {
|
{schedules === undefined || schedules === null ? null : schedules.map((schedule, index) => {
|
||||||
return (
|
return (
|
||||||
<ListItem>
|
<ListItem key={index}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{maxWidth: 200}}
|
style={{maxWidth: 200, minWidth: 200}}
|
||||||
primary={schedule.seconds}
|
primary={schedule.environment === "cloud" ? schedule.frequency : <span>{schedule.seconds} seconds</span>}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
style={{maxWidth: 150, minWidth: 150}}
|
||||||
|
primary={schedule.environment}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
style={{maxWidth: 315, minWidth: 315}}
|
||||||
|
primary={<a style={{textDecoration: "none", color: "#f85a3e"}} href={`/workflows/${schedule.workflow_id}`} target="_blank">{schedule.workflow_id}</a>}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={schedule.argument}
|
primary={schedule.argument}
|
||||||
style={{maxWidth: 400, overflow: "hidden"}}
|
style={{minWidth: 300, maxWidth: 300, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
<ListItemText>
|
<ListItemText>
|
||||||
<Button
|
<Button
|
||||||
@@ -1184,7 +1587,7 @@ const Admin = (props) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => deleteSchedule(schedule)}
|
onClick={() => deleteSchedule(schedule)}
|
||||||
>
|
>
|
||||||
Delete
|
Stop schedule
|
||||||
</Button>
|
</Button>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@@ -1194,7 +1597,7 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const appCategoryView = curTab === 6 ?
|
const appCategoryView = curTab === 7 ?
|
||||||
<div>
|
<div>
|
||||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||||
<h2 style={{display: "inline",}}>Categories</h2>
|
<h2 style={{display: "inline",}}>Categories</h2>
|
||||||
@@ -1270,11 +1673,12 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const authenticationView = curTab === 1 ?
|
const authenticationView = curTab === 2 ?
|
||||||
<div>
|
<div>
|
||||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||||
<h2 style={{display: "inline",}}>App Authentication</h2>
|
<h2 style={{display: "inline",}}>App Authentication</h2>
|
||||||
<span style={{marginLeft: 25}}>Control the authentication options for individual apps. <b>Actions can be destructive!</b></span>
|
<span style={{marginLeft: 25}}>Control the authentication options for individual apps. <b>Actions can be destructive!</b></span>
|
||||||
|
. <a target="_blank" href="https://shuffler.io/docs/organizations#app_authentication" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a>
|
||||||
</div>
|
</div>
|
||||||
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
|
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
|
||||||
<List>
|
<List>
|
||||||
@@ -1307,9 +1711,9 @@ const Admin = (props) => {
|
|||||||
primary="Actions"
|
primary="Actions"
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{authentication === undefined ? null : authentication.map(data => {
|
{authentication === undefined ? null : authentication.map((data, index) => {
|
||||||
return (
|
return (
|
||||||
<ListItem>
|
<ListItem key={index}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary=<img alt="" src={data.app.large_image} style={{maxWidth: 50,}} />
|
primary=<img alt="" src={data.app.large_image} style={{maxWidth: 50,}} />
|
||||||
style={{minWidth: 150, maxWidth: 150}}
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
@@ -1355,11 +1759,11 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const environmentView = curTab === 2 ?
|
const environmentView = curTab === 3 ?
|
||||||
<div>
|
<div>
|
||||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||||
<h2 style={{display: "inline",}}>Environments</h2>
|
<h2 style={{display: "inline",}}>Environments</h2>
|
||||||
<span style={{marginLeft: 25}}>Decides what Orborus environment to execute an action in a workflow in.</span>
|
<span style={{marginLeft: 25}}>Decides what Orborus environment to execute an action in a workflow in.<a target="_blank" href="https://shuffler.io/docs/organizations#environments" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a></span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
style={{}}
|
style={{}}
|
||||||
@@ -1423,7 +1827,7 @@ const Admin = (props) => {
|
|||||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={"TBD"}
|
primary={environment.Type === "cloud" ? "N/A" : "TBD"}
|
||||||
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
|
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
@@ -1443,7 +1847,7 @@ const Admin = (props) => {
|
|||||||
<ListItemText
|
<ListItemText
|
||||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||||
>
|
>
|
||||||
<Button variant="outlined" style={{borderRadius: "0px"}} onClick={() => deleteEnvironment(environment.Name)} color="primary">Delete</Button>
|
<Button disabled={environment.archived} variant="outlined" style={{borderRadius: "0px"}} onClick={() => deleteEnvironment(environment.Name)} color="primary">Archive</Button>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||||
@@ -1456,7 +1860,7 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const organizationsTab = curTab === 5 ?
|
const organizationsTab = curTab === 6 ?
|
||||||
<div>
|
<div>
|
||||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||||
<h2 style={{display: "inline",}}>Organizations</h2>
|
<h2 style={{display: "inline",}}>Organizations</h2>
|
||||||
@@ -1537,7 +1941,7 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const hybridTab = curTab === 4 ?
|
const hybridTab = curTab === 5 ?
|
||||||
<div>
|
<div>
|
||||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||||
<h2 style={{display: "inline",}}>Hybrid</h2>
|
<h2 style={{display: "inline",}}>Hybrid</h2>
|
||||||
@@ -1581,12 +1985,14 @@ const Admin = (props) => {
|
|||||||
|
|
||||||
const setConfig = (event, newValue) => {
|
const setConfig = (event, newValue) => {
|
||||||
if (newValue === 1) {
|
if (newValue === 1) {
|
||||||
getAppAuthentication()
|
getUsers()
|
||||||
} else if (newValue === 2) {
|
} else if (newValue === 2) {
|
||||||
getEnvironments()
|
getAppAuthentication()
|
||||||
} else if (newValue === 3) {
|
} else if (newValue === 3) {
|
||||||
|
getEnvironments()
|
||||||
|
} else if (newValue === 4) {
|
||||||
getSchedules()
|
getSchedules()
|
||||||
} else if (newValue === 5) {
|
} else if (newValue === 6) {
|
||||||
getOrgs()
|
getOrgs()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1594,6 +2000,24 @@ const Admin = (props) => {
|
|||||||
console.log("Should get apps for categories.")
|
console.log("Should get apps for categories.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const views = {
|
||||||
|
0: "organization",
|
||||||
|
1: "users",
|
||||||
|
2: "app_auth",
|
||||||
|
3: "environments",
|
||||||
|
4: "schedules",
|
||||||
|
5: "categories",
|
||||||
|
}
|
||||||
|
|
||||||
|
//var theURL = window.location.pathname
|
||||||
|
//FIXME: Add url edits
|
||||||
|
//var theURL = window.location
|
||||||
|
//theURL.replace(`/${views[curTab]}`, `/${views[newValue]}`)
|
||||||
|
//window.history.pushState({"html":response.html,"pageTitle":response.pageTitle},"", urlPath);
|
||||||
|
|
||||||
|
//console.log(newpath)
|
||||||
|
//window.location.pathame = newpath
|
||||||
|
|
||||||
setModalUser({})
|
setModalUser({})
|
||||||
setCurTab(newValue)
|
setCurTab(newValue)
|
||||||
}
|
}
|
||||||
@@ -1608,16 +2032,18 @@ const Admin = (props) => {
|
|||||||
onChange={setConfig}
|
onChange={setConfig}
|
||||||
aria-label="disabled tabs example"
|
aria-label="disabled tabs example"
|
||||||
>
|
>
|
||||||
<Tab label=<span><AccessibilityNewIcon style={iconStyle} />Users</span> />
|
<Tab label=<span><BusinessIcon style={iconStyle} /> Organization</span>/>
|
||||||
<Tab label=<span><LockIcon style={iconStyle} />App Authentication</span>/>
|
{isCloud ? null : <Tab label=<span><AccessibilityNewIcon style={iconStyle} />Users</span> />}
|
||||||
<Tab label=<span><EcoIcon style={iconStyle} />Environments</span>/>
|
{isCloud ? null : <Tab label=<span><LockIcon style={iconStyle} />App Authentication</span>/>}
|
||||||
<Tab label=<span><ScheduleIcon style={iconStyle} />Schedules</span> />
|
{isCloud ? null : <Tab label=<span><EcoIcon style={iconStyle} />Environments</span>/>}
|
||||||
|
{isCloud ? null : <Tab label=<span><ScheduleIcon style={iconStyle} />Schedules</span> />}
|
||||||
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null}
|
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null}
|
||||||
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/> : null}
|
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/> : null}
|
||||||
{window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null}
|
{window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Divider style={{marginTop: 0, marginBottom: 10, backgroundColor: "rgb(91, 96, 100)"}} />
|
<Divider style={{marginTop: 0, marginBottom: 10, backgroundColor: "rgb(91, 96, 100)"}} />
|
||||||
<div style={{padding: 15}}>
|
<div style={{padding: 15}}>
|
||||||
|
{organizationView}
|
||||||
{authenticationView}
|
{authenticationView}
|
||||||
{appCategoryView}
|
{appCategoryView}
|
||||||
{usersView}
|
{usersView}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -884,6 +884,7 @@ const AppCreator = (props) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
setAppBuilding(false)
|
||||||
setErrorCode(error.toString())
|
setErrorCode(error.toString())
|
||||||
alert.error(error.toString())
|
alert.error(error.toString())
|
||||||
});
|
});
|
||||||
@@ -1568,7 +1569,7 @@ const AppCreator = (props) => {
|
|||||||
required
|
required
|
||||||
style={{flex: "1", marginRight: "15px", marginTop: "5px", backgroundColor: inputColor}}
|
style={{flex: "1", marginRight: "15px", marginTop: "5px", backgroundColor: inputColor}}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
placeholder={"Accept application/json\r\nContent-Type application/json"}
|
placeholder={"Accept: application/json\r\nContent-Type: application/json"}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
id="standard-required"
|
id="standard-required"
|
||||||
@@ -1576,7 +1577,7 @@ const AppCreator = (props) => {
|
|||||||
multiline
|
multiline
|
||||||
rows="5"
|
rows="5"
|
||||||
onChange={e => setActionField("headers", e.target.value)}
|
onChange={e => setActionField("headers", e.target.value)}
|
||||||
helperText={<span style={{color:"white", marginBottom: "2px",}}>Headers that are part of the request</span>}
|
helperText={<span style={{color:"white", marginBottom: "2px",}}>Headers that are part of the request. Default: EMPTY</span>}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
classes: {
|
classes: {
|
||||||
notchedOutline: classes.notchedOutline,
|
notchedOutline: classes.notchedOutline,
|
||||||
|
|||||||
+36
-31
@@ -137,6 +137,7 @@ const Apps = (props) => {
|
|||||||
|
|
||||||
const [isDropzone, setIsDropzone] = React.useState(false);
|
const [isDropzone, setIsDropzone] = React.useState(false);
|
||||||
const upload = React.useRef(null);
|
const upload = React.useRef(null);
|
||||||
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" ? true : false
|
||||||
|
|
||||||
const { start, stop } = useInterval({
|
const { start, stop } = useInterval({
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
@@ -592,7 +593,7 @@ const Apps = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{activateButton}
|
{activateButton}
|
||||||
{(props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated ?
|
{(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ?
|
||||||
<div>
|
<div>
|
||||||
{downloadButton}
|
{downloadButton}
|
||||||
{editButton}
|
{editButton}
|
||||||
@@ -618,7 +619,7 @@ const Apps = (props) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
{props.userdata.id === selectedApp.owner ?
|
{props.userdata !== undefined && props.userdata.id === selectedApp.owner ?
|
||||||
<div style={{marginTop: 15}}>
|
<div style={{marginTop: 15}}>
|
||||||
{/*<p><b>ID:</b> {selectedApp.id}</p>*/}
|
{/*<p><b>ID:</b> {selectedApp.id}</p>*/}
|
||||||
<b style={{marginRight: 15}}>Sharing:</b>
|
<b style={{marginRight: 15}}>Sharing:</b>
|
||||||
@@ -794,7 +795,7 @@ const Apps = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const uploadFile = (e) => {
|
const uploadFile = (e) => {
|
||||||
const isDropzone = e.dataTransfer?.files.length > 0;
|
const isDropzone = e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
|
||||||
const files = isDropzone ? e.dataTransfer.files : e.target.files;
|
const files = isDropzone ? e.dataTransfer.files : e.target.files;
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@@ -846,37 +847,41 @@ const Apps = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<Divider style={{marginBottom: 10, marginTop: 10, height: "100%", width: 1, backgroundColor: dividerColor}}/>
|
<Divider style={{marginBottom: 10, marginTop: 10, height: "100%", width: 1, backgroundColor: dividerColor}}/>
|
||||||
<div style={{flex: 1, marginLeft: 10, marginRight: 10}}>
|
<div style={{flex: 1, marginLeft: 10, marginRight: 10}}>
|
||||||
<div style={{display: "flex"}}>
|
<div style={{display: "flex", minHeight: 84.81}}>
|
||||||
<div style={{flex: 1}}>
|
<div style={{flex: 1}}>
|
||||||
<h2>Your apps ({apps.length+searchableApps.length})</h2>
|
<h2>Your apps ({apps.length+searchableApps.length})</h2>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
{isCloud ? null :
|
||||||
<Button
|
<span>
|
||||||
variant="outlined"
|
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
||||||
component="label"
|
<Button
|
||||||
color="primary"
|
variant="outlined"
|
||||||
style={{margin: 5, maxHeight: 50, marginTop: 10}}
|
component="label"
|
||||||
onClick={() => {
|
color="primary"
|
||||||
hotloadApps()
|
style={{margin: 5, maxHeight: 50, marginTop: 10}}
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
hotloadApps()
|
||||||
<CachedIcon />
|
}}
|
||||||
</Button>
|
>
|
||||||
</Tooltip>
|
<CachedIcon />
|
||||||
<Tooltip title={"Download from Github"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
</Button>
|
||||||
<Button
|
</Tooltip>
|
||||||
variant="outlined"
|
<Tooltip title={"Download from Github"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
||||||
component="label"
|
<Button
|
||||||
color="primary"
|
variant="outlined"
|
||||||
style={{margin: 5, maxHeight: 50, marginTop: 10}}
|
component="label"
|
||||||
onClick={() => {
|
color="primary"
|
||||||
setOpenApi(baseRepository)
|
style={{margin: 5, maxHeight: 50, marginTop: 10}}
|
||||||
setLoadAppsModalOpen(true)
|
onClick={() => {
|
||||||
}}
|
setOpenApi(baseRepository)
|
||||||
>
|
setLoadAppsModalOpen(true)
|
||||||
<CloudDownloadIcon />
|
}}
|
||||||
</Button>
|
>
|
||||||
</Tooltip>
|
<CloudDownloadIcon />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<TextField
|
<TextField
|
||||||
style={{backgroundColor: inputColor}}
|
style={{backgroundColor: inputColor}}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ const Body = {
|
|||||||
const dividerColor = "rgb(225, 228, 232)"
|
const dividerColor = "rgb(225, 228, 232)"
|
||||||
|
|
||||||
const SideBar = {
|
const SideBar = {
|
||||||
maxWidth: "250px",
|
maxWidth: 250,
|
||||||
flex: "1",
|
flex: "1",
|
||||||
|
position: "fixed",
|
||||||
}
|
}
|
||||||
|
|
||||||
const hrefStyle = {
|
const hrefStyle = {
|
||||||
@@ -165,6 +166,7 @@ const Docs = (props) => {
|
|||||||
maxWidth: 750,
|
maxWidth: 750,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
paddingBottom: 200,
|
paddingBottom: 200,
|
||||||
|
marginLeft: 250,
|
||||||
}
|
}
|
||||||
|
|
||||||
function OuterLink(props) {
|
function OuterLink(props) {
|
||||||
@@ -212,11 +214,11 @@ const Docs = (props) => {
|
|||||||
<div style={Body}>
|
<div style={Body}>
|
||||||
<div style={SideBar}>
|
<div style={SideBar}>
|
||||||
<ul style={{listStyle: "none", paddingLeft: "0"}}>
|
<ul style={{listStyle: "none", paddingLeft: "0"}}>
|
||||||
{list.map(item => {
|
{list.map((item, index) => {
|
||||||
const path = "/docs/"+item
|
const path = "/docs/"+item
|
||||||
const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
|
const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
|
||||||
return (
|
return (
|
||||||
<li style={{marginTop: "10px"}}>
|
<li key={index} style={{marginTop: "10px"}}>
|
||||||
<Link style={hrefStyle} to={path} onClick={() => {fetchDocs(item)}}>
|
<Link style={hrefStyle} to={path} onClick={() => {fetchDocs(item)}}>
|
||||||
<h2>{newname}</h2>
|
<h2>{newname}</h2>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const HandlePayment = () => {
|
||||||
|
return (
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HandlePayment
|
||||||
@@ -5,7 +5,8 @@ WORKDIR /app
|
|||||||
RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
|
RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
|
||||||
|
|
||||||
COPY orborus.go /app/orborus.go
|
COPY orborus.go /app/orborus.go
|
||||||
|
RUN go mod init orborus
|
||||||
|
RUN go build
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
|
||||||
|
|
||||||
FROM alpine:3.12
|
FROM alpine:3.12
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
NAME=orborus
|
NAME=shuffle-orborus
|
||||||
VERSION=0.6.2
|
VERSION=0.8.0
|
||||||
|
|
||||||
echo "Running docker build with $NAME:$VERSION"
|
echo "Running docker build with $NAME:$VERSION"
|
||||||
#docker rmi frikky/shuffle:$NAME --force
|
#docker rmi frikky/shuffle:$NAME --force
|
||||||
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|
||||||
#docker push frikky/$NAME:$VERSION
|
#docker push frikky/$NAME:$VERSION
|
||||||
docker push frikky/shuffle:$NAME
|
#docker push frikky/shuffle:$NAME
|
||||||
# docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
# docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||||
#docker push ghcr.io/frikky/$NAME:$VERSION
|
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
"github.com/satori/go.uuid"
|
||||||
//network "github.com/docker/docker/api/types/network"
|
//network "github.com/docker/docker/api/types/network"
|
||||||
//natting "github.com/docker/go-connections/nat"
|
//natting "github.com/docker/go-connections/nat"
|
||||||
)
|
)
|
||||||
@@ -134,18 +135,36 @@ func deployWorker(image string, identifier string, env []string) {
|
|||||||
Env: env,
|
Env: env,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("Identifier: %s", identifier)
|
||||||
cont, err := dockercli.ContainerCreate(
|
cont, err := dockercli.ContainerCreate(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
config,
|
config,
|
||||||
hostConfig,
|
hostConfig,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
|
||||||
identifier,
|
identifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Container create error: %s", err)
|
if strings.Contains(fmt.Sprintf("%s", err), "Conflict. The container name ") {
|
||||||
return
|
uuid := uuid.NewV4()
|
||||||
|
identifier = fmt.Sprintf("%s-%s", identifier, uuid)
|
||||||
|
log.Printf("2 - Identifier: %s", identifier)
|
||||||
|
cont, err = dockercli.ContainerCreate(
|
||||||
|
context.Background(),
|
||||||
|
config,
|
||||||
|
hostConfig,
|
||||||
|
nil,
|
||||||
|
identifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Container create error(2): %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] Container create error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dockercli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
|
err = dockercli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
|
||||||
@@ -208,27 +227,32 @@ func initializeImages() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if appSdkVersion == "" {
|
if appSdkVersion == "" {
|
||||||
appSdkVersion = "0.6.0"
|
appSdkVersion = "0.8.0"
|
||||||
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
|
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
|
||||||
}
|
}
|
||||||
if workerVersion == "" {
|
if workerVersion == "" {
|
||||||
workerVersion = "0.6.0"
|
workerVersion = "0.8.0"
|
||||||
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
|
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseimageregistry == "" {
|
if baseimageregistry == "" {
|
||||||
baseimageregistry = "docker.io"
|
baseimageregistry = "docker.io"
|
||||||
|
baseimageregistry = "ghcr.io"
|
||||||
log.Printf("Setting baseimageregistry")
|
log.Printf("Setting baseimageregistry")
|
||||||
}
|
}
|
||||||
if baseimagename == "" {
|
if baseimagename == "" {
|
||||||
baseimagename = "frikky/shuffle"
|
baseimagename = "frikky/shuffle"
|
||||||
|
baseimagename = "frikky"
|
||||||
log.Printf("Setting baseimagename")
|
log.Printf("Setting baseimagename")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether they are the same first
|
// check whether they are the same first
|
||||||
images := []string{
|
images := []string{
|
||||||
fmt.Sprintf("%s/%s:app_sdk%s", baseimageregistry, baseimagename, baseimagetagsuffix),
|
//fmt.Sprintf("%s/%s:app_sdk%s", baseimageregistry, baseimagename, baseimagetagsuffix),
|
||||||
fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix),
|
//fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix),
|
||||||
|
|
||||||
|
fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion),
|
||||||
|
fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion),
|
||||||
// fmt.Sprintf("docker.io/%s:app_sdk", baseimagename),
|
// fmt.Sprintf("docker.io/%s:app_sdk", baseimagename),
|
||||||
// fmt.Sprintf("docker.io/%s:worker", baseimagename),
|
// fmt.Sprintf("docker.io/%s:worker", baseimagename),
|
||||||
|
|
||||||
@@ -301,7 +325,8 @@ func main() {
|
|||||||
//workerImage := fmt.Sprintf("%s/worker:%s", baseimagename, workerVersion)
|
//workerImage := fmt.Sprintf("%s/worker:%s", baseimagename, workerVersion)
|
||||||
// workerImage := fmt.Sprintf("docker.io/%s:worker", baseimagename)
|
// workerImage := fmt.Sprintf("docker.io/%s:worker", baseimagename)
|
||||||
// fmt.Sprintf("%s/%s:app_sdk%s", baseimageregistry, baseimagename, baseimagetagsuffix),
|
// fmt.Sprintf("%s/%s:app_sdk%s", baseimageregistry, baseimagename, baseimagetagsuffix),
|
||||||
workerImage := fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix)
|
//workerImage := fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix)
|
||||||
|
workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion)
|
||||||
|
|
||||||
log.Printf("[INFO] Finished configuring docker environment")
|
log.Printf("[INFO] Finished configuring docker environment")
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
NAME=worker
|
NAME=shuffle-worker
|
||||||
VERSION=0.6.0
|
VERSION=0.8.0
|
||||||
|
|
||||||
echo "Running docker build with $NAME:$VERSION"
|
echo "Running docker build with $NAME:$VERSION"
|
||||||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
|
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
|
||||||
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|
||||||
# Push both for now..
|
# Push both for now..
|
||||||
#docker push frikky/$NAME:$VERSION
|
#docker push frikky/$NAME:$VERSION
|
||||||
docker push frikky/shuffle:$NAME
|
docker push frikky/shuffle:$NAME
|
||||||
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||||
#docker push ghcr.io/frikky/$NAME:$VERSION
|
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|||||||
@@ -236,10 +236,9 @@ type Action struct {
|
|||||||
AuthNotRequired bool `json:"auth_not_required" datastore:"auth_not_required" yaml:"auth_not_required"`
|
AuthNotRequired bool `json:"auth_not_required" datastore:"auth_not_required" yaml:"auth_not_required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Added environment for location to execute
|
|
||||||
type Trigger struct {
|
type Trigger struct {
|
||||||
AppName string `json:"app_name" datastore:"app_name"`
|
AppName string `json:"app_name" datastore:"app_name"`
|
||||||
Description string `json:"description" datastore:"description"`
|
Description string `json:"description" datastore:"description,noindex"`
|
||||||
LongDescription string `json:"long_description" datastore:"long_description"`
|
LongDescription string `json:"long_description" datastore:"long_description"`
|
||||||
Status string `json:"status" datastore:"status"`
|
Status string `json:"status" datastore:"status"`
|
||||||
AppVersion string `json:"app_version" datastore:"app_version"`
|
AppVersion string `json:"app_version" datastore:"app_version"`
|
||||||
@@ -253,6 +252,7 @@ type Trigger struct {
|
|||||||
Environment string `json:"environment" datastore:"environment"`
|
Environment string `json:"environment" datastore:"environment"`
|
||||||
TriggerType string `json:"trigger_type" datastore:"trigger_type"`
|
TriggerType string `json:"trigger_type" datastore:"trigger_type"`
|
||||||
Name string `json:"name" datastore:"name"`
|
Name string `json:"name" datastore:"name"`
|
||||||
|
Tags []string `json:"tags" datastore:"tags" yaml:"tags"`
|
||||||
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"`
|
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"`
|
||||||
Position struct {
|
Position struct {
|
||||||
X float64 `json:"x" datastore:"x"`
|
X float64 `json:"x" datastore:"x"`
|
||||||
@@ -549,6 +549,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
|
|
||||||
// source = parent node, dest = child node
|
// source = parent node, dest = child node
|
||||||
// parent can have more children, child can have more parents
|
// parent can have more children, child can have more parents
|
||||||
|
extra := 0
|
||||||
for _, branch := range workflowExecution.Workflow.Branches {
|
for _, branch := range workflowExecution.Workflow.Branches {
|
||||||
// Check what the parent is first. If it's trigger - skip
|
// Check what the parent is first. If it's trigger - skip
|
||||||
sourceFound := false
|
sourceFound := false
|
||||||
@@ -563,6 +564,21 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, trigger := range workflowExecution.Workflow.Triggers {
|
||||||
|
if trigger.AppName != "User Input" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if trigger.ID == branch.SourceID {
|
||||||
|
sourceFound = true
|
||||||
|
extra += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if trigger.ID == branch.DestinationID {
|
||||||
|
destinationFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if sourceFound {
|
if sourceFound {
|
||||||
parents[branch.DestinationID] = append(parents[branch.DestinationID], branch.SourceID)
|
parents[branch.DestinationID] = append(parents[branch.DestinationID], branch.SourceID)
|
||||||
} else {
|
} else {
|
||||||
@@ -576,7 +592,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Actions: %d", len(workflowExecution.Workflow.Actions))
|
log.Printf("Actions: %d + Special Triggers: %d", len(workflowExecution.Workflow.Actions), extra)
|
||||||
for _, action := range workflowExecution.Workflow.Actions {
|
for _, action := range workflowExecution.Workflow.Actions {
|
||||||
if action.Environment != environment {
|
if action.Environment != environment {
|
||||||
continue
|
continue
|
||||||
@@ -775,14 +791,51 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
// IF NOT VISITED && IN toExecuteOnPrem
|
// IF NOT VISITED && IN toExecuteOnPrem
|
||||||
// SKIP if it's not onprem
|
// SKIP if it's not onprem
|
||||||
for _, nextAction := range nextActions {
|
for _, nextAction := range nextActions {
|
||||||
action := getAction(workflowExecution, nextAction)
|
action := getAction(workflowExecution, nextAction, environment)
|
||||||
|
|
||||||
// check visited and onprem
|
// check visited and onprem
|
||||||
if arrayContains(visited, nextAction) {
|
if arrayContains(visited, nextAction) {
|
||||||
log.Printf("ALREADY VISITIED (%s): %s", action.Label, nextAction)
|
log.Printf("ALREADY VISITIED (%s): %s", action.Label, nextAction)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if action.AppName == "User Input" {
|
||||||
|
log.Printf("USER INPUT!")
|
||||||
|
|
||||||
|
if action.ID == workflowExecution.Start {
|
||||||
|
log.Printf("Skipping because it's the startnode")
|
||||||
|
visited = append(visited, action.ID)
|
||||||
|
executed = append(executed, action.ID)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
log.Printf("Should stop after this iteration because it's user-input based. %#v", action)
|
||||||
|
trigger := Trigger{}
|
||||||
|
for _, innertrigger := range workflowExecution.Workflow.Triggers {
|
||||||
|
if innertrigger.ID == action.ID {
|
||||||
|
trigger = innertrigger
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger.LargeImage = ""
|
||||||
|
triggerData, err := json.Marshal(trigger)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed unmarshalling action: %s", err)
|
||||||
|
triggerData = []byte("Failed unmarshalling. Cancel execution!")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runUserInput(client, action, workflowExecution.Workflow.ID, workflowExecution.ExecutionId, workflowExecution.Authorization, string(triggerData))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed launching backend magic: %s", err)
|
||||||
|
os.Exit(3)
|
||||||
|
} else {
|
||||||
|
log.Printf("Launched user input node succesfully!")
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Not really sure how this edgecase happens.
|
// Not really sure how this edgecase happens.
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
@@ -816,7 +869,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
}
|
}
|
||||||
|
|
||||||
if continueOuter {
|
if continueOuter {
|
||||||
//log.Printf("Parents of %s aren't finished: %s", nextAction, strings.Join(parents[nextAction], ", "))
|
log.Printf("Parents of %s aren't finished: %s", nextAction, strings.Join(parents[nextAction], ", "))
|
||||||
//for _, tmpaction := range parents[nextAction] {
|
//for _, tmpaction := range parents[nextAction] {
|
||||||
// action := getAction(workflowExecution, tmpaction)
|
// action := getAction(workflowExecution, tmpaction)
|
||||||
// _ = action
|
// _ = action
|
||||||
@@ -911,7 +964,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
// https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083
|
// https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083
|
||||||
maxSize := 32700 - len(string(actionData)) - 2000
|
maxSize := 32700 - len(string(actionData)) - 2000
|
||||||
if len(executionData) < maxSize {
|
if len(executionData) < maxSize {
|
||||||
log.Printf("ADDING FULL_EXECUTION because size is larger than %d", maxSize)
|
log.Printf("ADDING FULL_EXECUTION because size is smaller than %d", maxSize)
|
||||||
env = append(env, fmt.Sprintf("FULL_EXECUTION=%s", string(executionData)))
|
env = append(env, fmt.Sprintf("FULL_EXECUTION=%s", string(executionData)))
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Skipping FULL_EXECUTION because size is larger than %d", maxSize)
|
log.Printf("Skipping FULL_EXECUTION because size is larger than %d", maxSize)
|
||||||
@@ -973,13 +1026,13 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
|||||||
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Status: %s, Results: %d, actions: %d", workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
|
log.Printf("Status: %s, Results: %d, actions: %d", workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra)
|
||||||
if workflowExecution.Status != "EXECUTING" {
|
if workflowExecution.Status != "EXECUTING" {
|
||||||
log.Printf("Exiting as worker execution has status %s!", workflowExecution.Status)
|
log.Printf("Exiting as worker execution has status %s!", workflowExecution.Status)
|
||||||
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) {
|
if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extra {
|
||||||
shutdownCheck := true
|
shutdownCheck := true
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for _, result := range workflowExecution.Results {
|
for _, result := range workflowExecution.Results {
|
||||||
@@ -1075,16 +1128,73 @@ func getResult(workflowExecution WorkflowExecution, id string) ActionResult {
|
|||||||
return ActionResult{}
|
return ActionResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAction(workflowExecution WorkflowExecution, id string) Action {
|
func getAction(workflowExecution WorkflowExecution, id, environment string) Action {
|
||||||
for _, action := range workflowExecution.Workflow.Actions {
|
for _, action := range workflowExecution.Workflow.Actions {
|
||||||
if action.ID == id {
|
if action.ID == id {
|
||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, trigger := range workflowExecution.Workflow.Triggers {
|
||||||
|
if trigger.ID == id {
|
||||||
|
return Action{
|
||||||
|
ID: trigger.ID,
|
||||||
|
AppName: trigger.AppName,
|
||||||
|
Name: trigger.AppName,
|
||||||
|
Environment: environment,
|
||||||
|
}
|
||||||
|
log.Printf("FOUND TRIGGER: %#v!", trigger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Action{}
|
return Action{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runUserInput(client *http.Client, action Action, workflowId, workflowExecutionId, authorization string, configuration string) error {
|
||||||
|
timeNow := time.Now().Unix()
|
||||||
|
result := ActionResult{
|
||||||
|
Action: action,
|
||||||
|
ExecutionId: workflowExecutionId,
|
||||||
|
Authorization: authorization,
|
||||||
|
Result: configuration,
|
||||||
|
StartedAt: timeNow,
|
||||||
|
CompletedAt: 0,
|
||||||
|
Status: "WAITING",
|
||||||
|
}
|
||||||
|
|
||||||
|
resultData, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fullUrl := fmt.Sprintf("%s/api/v1/streams", baseUrl)
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"POST",
|
||||||
|
fullUrl,
|
||||||
|
bytes.NewBuffer([]byte(resultData)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error building test request: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newresp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error running test request: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(newresp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed reading body when waiting: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Body: %s", string(body))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func runTestExecution(client *http.Client, workflowId, apikey string) (string, string) {
|
func runTestExecution(client *http.Client, workflowId, apikey string) (string, string) {
|
||||||
fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", baseUrl, workflowId)
|
fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", baseUrl, workflowId)
|
||||||
req, err := http.NewRequest(
|
req, err := http.NewRequest(
|
||||||
@@ -1173,7 +1283,6 @@ func main() {
|
|||||||
shutdown(executionId, "")
|
shutdown(executionId, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - tmp
|
|
||||||
data := fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, executionId, authorization)
|
data := fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, executionId, authorization)
|
||||||
fullUrl := fmt.Sprintf("%s/api/v1/streams/results", baseUrl)
|
fullUrl := fmt.Sprintf("%s/api/v1/streams/results", baseUrl)
|
||||||
req, err := http.NewRequest(
|
req, err := http.NewRequest(
|
||||||
|
|||||||
Reference in New Issue
Block a user