Merge branch '1.4.0' into tenzir
This commit is contained in:
@@ -60,7 +60,7 @@ This step is for setting up with Docker on windows from scratch.
|
|||||||
OUTER_HOSTNAME=YOUR.IP.HERE
|
OUTER_HOSTNAME=YOUR.IP.HERE
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Run docker compose
|
5. Run docker compose
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|||||||
+84
-178
@@ -1127,6 +1127,8 @@ class AppBase:
|
|||||||
#param_multiplier = await self.get_param_multipliers(newparams)
|
#param_multiplier = await self.get_param_multipliers(newparams)
|
||||||
param_multiplier = self.get_param_multipliers(newparams)
|
param_multiplier = self.get_param_multipliers(newparams)
|
||||||
|
|
||||||
|
#self.logger.info("PARAM MULTIPLIER: %s" % param_multiplier)
|
||||||
|
|
||||||
# FIXME: This does a deduplication of the data
|
# FIXME: This does a deduplication of the data
|
||||||
new_params = self.validate_unique_fields(param_multiplier)
|
new_params = self.validate_unique_fields(param_multiplier)
|
||||||
#self.logger.info(f"NEW PARAMS: {new_params}")
|
#self.logger.info(f"NEW PARAMS: {new_params}")
|
||||||
@@ -2524,7 +2526,7 @@ class AppBase:
|
|||||||
return template
|
return template
|
||||||
|
|
||||||
if "${" in template and "}$" in template:
|
if "${" in template and "}$" in template:
|
||||||
self.logger.info("[DEBUG] Shuffle loop shouldn't run in liquid. Data length: %d" % len(template))
|
#self.logger.info("[DEBUG] Shuffle loop shouldn't run in liquid. Data length: %d" % len(template))
|
||||||
return template
|
return template
|
||||||
|
|
||||||
|
|
||||||
@@ -2864,8 +2866,9 @@ class AppBase:
|
|||||||
# Handles for loops etc.
|
# Handles for loops etc.
|
||||||
# FIXME: Should it dump to string here? Doesn't that defeat the purpose?
|
# FIXME: Should it dump to string here? Doesn't that defeat the purpose?
|
||||||
# Trying without string dumping.
|
# Trying without string dumping.
|
||||||
|
#self.logger.info("TO BE REPLACED: %s" % to_be_replaced)
|
||||||
value, is_loop = get_json_value(fullexecution, to_be_replaced)
|
value, is_loop = get_json_value(fullexecution, to_be_replaced)
|
||||||
|
|
||||||
#self.logger.info(f"\n\nType of value: {type(value)}")
|
#self.logger.info(f"\n\nType of value: {type(value)}")
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
# Could we take it here?
|
# Could we take it here?
|
||||||
@@ -2879,26 +2882,24 @@ class AppBase:
|
|||||||
# returnvalue = fix_json_string_value(value)
|
# returnvalue = fix_json_string_value(value)
|
||||||
# value = returnvalue
|
# value = returnvalue
|
||||||
|
|
||||||
|
parameter["value"] = parameter["value"].replace(to_be_replaced, value, 1)
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
|
||||||
elif isinstance(value, dict) or isinstance(value, list):
|
elif isinstance(value, dict) or isinstance(value, list):
|
||||||
# Changed from JSON dump to str() 28.05.2021
|
# Changed from JSON dump to str() 28.05.2021
|
||||||
# This makes it so the parameters gets lists and dicts straight up
|
# This makes it so the parameters gets lists and dicts straight up
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value))
|
parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value), 1)
|
||||||
|
|
||||||
#try:
|
#try:
|
||||||
# parameter["value"] = parameter["value"].replace(to_be_replaced, str(value))
|
|
||||||
#except:
|
|
||||||
# parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value))
|
# parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value))
|
||||||
|
#except:
|
||||||
|
# parameter["value"] = parameter["value"].replace(to_be_replaced, str(value))
|
||||||
# self.logger.info("Failed parsing value as string?")
|
# self.logger.info("Failed parsing value as string?")
|
||||||
else:
|
else:
|
||||||
self.logger.error("[ERROR] Unknown type %s" % type(value))
|
self.logger.error("[ERROR] Unknown type %s" % type(value))
|
||||||
try:
|
try:
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value))
|
parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value), 1)
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
parameter["value"] = parameter["value"].replace(to_be_replaced, value, 1)
|
||||||
|
|
||||||
#self.logger.info("VALUE: %s" % parameter["value"])
|
|
||||||
else:
|
else:
|
||||||
#self.logger.info(f"[ERROR] Not running static variant regex parsing (slow) on value with length {len(parameter['value'])}. Max is 5Mb~.")
|
#self.logger.info(f"[ERROR] Not running static variant regex parsing (slow) on value with length {len(parameter['value'])}. Max is 5Mb~.")
|
||||||
pass
|
pass
|
||||||
@@ -3435,196 +3436,101 @@ class AppBase:
|
|||||||
handled = False
|
handled = False
|
||||||
|
|
||||||
# Has a loop without a variable used inside
|
# Has a loop without a variable used inside
|
||||||
if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER":
|
|
||||||
|
# This is here to handle for loops within variables.. kindof
|
||||||
tmpitem = value
|
# 1. Find the length of the longest array
|
||||||
|
# 2. Build an array with the base values based on parameter["value"]
|
||||||
index = 0
|
# 3. Get the n'th value of the generated list from values
|
||||||
replacement = actualitem[index][2]
|
# 4. Execute all n answers
|
||||||
if replacement.endswith("}$"):
|
replacements = {}
|
||||||
replacement = replacement[:-2]
|
curminlength = 0
|
||||||
|
for replace in actualitem:
|
||||||
if replacement.startswith("\"") and replacement.endswith("\""):
|
|
||||||
replacement = replacement[1:len(replacement)-1]
|
|
||||||
|
|
||||||
#json_replacement = tmpitem.replace(actualitem[index][0], replacement, 1)
|
|
||||||
json_replacement = replacement
|
|
||||||
try:
|
try:
|
||||||
json_replacement = json.loads(replacement)
|
to_be_replaced = replace[0]
|
||||||
|
actualitem = replace[2]
|
||||||
|
if actualitem.endswith("}$"):
|
||||||
|
actualitem = actualitem[:-2]
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
self.logger.info("[WARNING] Indexerror")
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
itemlist = json.loads(actualitem)
|
||||||
|
if len(itemlist) > minlength:
|
||||||
|
minlength = len(itemlist)
|
||||||
|
|
||||||
|
if len(itemlist) > curminlength:
|
||||||
|
curminlength = len(itemlist)
|
||||||
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
self.logger.info("JSON Error (replace): %s in %s" % (e, actualitem))
|
||||||
|
|
||||||
|
replacements[to_be_replaced] = actualitem
|
||||||
|
|
||||||
|
|
||||||
|
# Parses the data as string with length, split etc. before moving on.
|
||||||
|
#self.logger.info("In second part of else: %s" % (len(itemlist)))
|
||||||
|
# This is a result array for JUST this value..
|
||||||
|
# What if there are more?
|
||||||
|
resultarray = []
|
||||||
|
for i in range(0, curminlength):
|
||||||
|
tmpitem = json.loads(json.dumps(parameter["value"]))
|
||||||
|
for key, value in replacements.items():
|
||||||
|
replacement = value
|
||||||
try:
|
try:
|
||||||
replacement = replacement.replace("\'", "\"", -1)
|
replacement = json.dumps(json.loads(value)[i])
|
||||||
json_replacement = json.loads(replacement)
|
except IndexError as e:
|
||||||
except:
|
self.logger.info(f"[ERROR] Failed handling value parsing with index: {e}")
|
||||||
self.logger.info("JSON error singular: %s" % e)
|
pass
|
||||||
|
|
||||||
if len(json_replacement) > minlength:
|
if replacement.startswith("\"") and replacement.endswith("\""):
|
||||||
minlength = len(json_replacement)
|
replacement = replacement[1:len(replacement)-1]
|
||||||
|
#except json.decoder.JSONDecodeError as e:
|
||||||
self.logger.info("PRE new_replacement")
|
|
||||||
|
|
||||||
new_replacement = []
|
|
||||||
for i in range(len(json_replacement)):
|
|
||||||
if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list):
|
|
||||||
tmp_replacer = json.dumps(json_replacement[i])
|
|
||||||
newvalue = tmpitem.replace(str(actualitem[index][0]), str(tmp_replacer), 1)
|
|
||||||
else:
|
|
||||||
newvalue = tmpitem.replace(str(actualitem[index][0]), str(json_replacement[i]), 1)
|
|
||||||
|
|
||||||
|
#self.logger.info("REPLACING %s with %s" % (key, replacement))
|
||||||
|
#replacement = parse_wrapper_start(replacement)
|
||||||
|
tmpitem = tmpitem.replace(key, replacement, -1)
|
||||||
try:
|
try:
|
||||||
newvalue = parse_liquid(newvalue, self)
|
tmpitem = parse_liquid(tmpitem, self)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}")
|
self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}")
|
||||||
|
|
||||||
try:
|
|
||||||
newvalue = json.loads(newvalue)
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
new_replacement.append(newvalue)
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Should this use new_replacement?
|
|
||||||
tmpitem = tmpitem.replace(actualitem[index][0], replacement, 1)
|
|
||||||
|
|
||||||
# This code handles files.
|
# This code handles files.
|
||||||
resultarray = []
|
|
||||||
isfile = False
|
isfile = False
|
||||||
try:
|
try:
|
||||||
if parameter["schema"]["type"] == "file" and len(value) > 0:
|
if parameter["schema"]["type"] == "file" and len(value) > 0:
|
||||||
self.logger.info("(1) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % tmpitem)
|
self.logger.info("(2) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % parameter["value"])
|
||||||
# This is silly :)
|
|
||||||
# Q: Is there something wrong with the download system?
|
for tmp_file_split in json.loads(parameter["value"]):
|
||||||
# It seems to return "FILE CONTENT: %s" with the ID as %s
|
|
||||||
for tmp_file_split in json.loads(tmpitem):
|
|
||||||
file_value = self.get_file(tmp_file_split)
|
file_value = self.get_file(tmp_file_split)
|
||||||
resultarray.append(file_value)
|
resultarray.append(file_value)
|
||||||
|
|
||||||
|
|
||||||
isfile = True
|
isfile = True
|
||||||
except NameError as e:
|
|
||||||
self.logger.info("(1) SCHEMA NAMEERROR IN FILE HANDLING: %s" % e)
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
self.logger.info("(1) SCHEMA KEYERROR IN FILE HANDLING: %s" % e)
|
self.logger.info("(2) SCHEMA ERROR IN FILE HANDLING: %s" % e)
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
self.logger.info("(1) JSON ERROR IN FILE HANDLING: %s" % e)
|
self.logger.info("(2) JSON ERROR IN FILE HANDLING: %s" % e)
|
||||||
|
|
||||||
if not isfile:
|
if not isfile:
|
||||||
params[parameter["name"]] = tmpitem
|
tmpitem = tmpitem.replace("\\\\", "\\", -1)
|
||||||
multi_parameters[parameter["name"]] = new_replacement
|
resultarray.append(tmpitem)
|
||||||
else:
|
|
||||||
params[parameter["name"]] = resultarray
|
|
||||||
multi_parameters[parameter["name"]] = resultarray
|
|
||||||
|
|
||||||
#if len(resultarray) == 0:
|
# With this parameter ready, add it to... a greater list of parameters. Rofl
|
||||||
# self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (1)")
|
if len(resultarray) == 0:
|
||||||
# action_result["status"] = "SUCCESS"
|
self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (0)")
|
||||||
# action_result["result"] = "[]"
|
self.action_result["status"] = "SUCCESS"
|
||||||
# self.send_result(action_result, headers, stream_path)
|
self.action_result["result"] = "[]"
|
||||||
# return
|
self.send_result(self.action_result, headers, stream_path)
|
||||||
|
return
|
||||||
|
|
||||||
multi_execution_lists.append(new_replacement)
|
#self.logger.info("RESULTARRAY: %s" % resultarray)
|
||||||
#self.logger.info("MULTI finished: %s" % json_replacement)
|
if resultarray not in multi_execution_lists:
|
||||||
else:
|
multi_execution_lists.append(resultarray)
|
||||||
# This is here to handle for loops within variables.. kindof
|
|
||||||
# 1. Find the length of the longest array
|
|
||||||
# 2. Build an array with the base values based on parameter["value"]
|
|
||||||
# 3. Get the n'th value of the generated list from values
|
|
||||||
# 4. Execute all n answers
|
|
||||||
replacements = {}
|
|
||||||
curminlength = 0
|
|
||||||
for replace in actualitem:
|
|
||||||
try:
|
|
||||||
to_be_replaced = replace[0]
|
|
||||||
actualitem = replace[2]
|
|
||||||
if actualitem.endswith("}$"):
|
|
||||||
actualitem = actualitem[:-2]
|
|
||||||
|
|
||||||
except IndexError:
|
multi_parameters[parameter["name"]] = resultarray
|
||||||
self.logger.info("[WARNING] Indexerror")
|
|
||||||
continue
|
|
||||||
|
|
||||||
#self.logger.info(f"\n\nTMPITEM: {actualitem}\n\n")
|
|
||||||
#actualitem = parse_wrapper_start(actualitem)
|
|
||||||
#self.logger.info(f"\n\nTMPITEM2: {actualitem}\n\n")
|
|
||||||
|
|
||||||
try:
|
|
||||||
itemlist = json.loads(actualitem)
|
|
||||||
if len(itemlist) > minlength:
|
|
||||||
minlength = len(itemlist)
|
|
||||||
|
|
||||||
if len(itemlist) > curminlength:
|
|
||||||
curminlength = len(itemlist)
|
|
||||||
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
self.logger.info("JSON Error (replace): %s in %s" % (e, actualitem))
|
|
||||||
|
|
||||||
replacements[to_be_replaced] = actualitem
|
|
||||||
|
|
||||||
|
|
||||||
# Parses the data as string with length, split etc. before moving on.
|
|
||||||
|
|
||||||
|
|
||||||
#self.logger.info("In second part of else: %s" % (len(itemlist)))
|
|
||||||
# This is a result array for JUST this value..
|
|
||||||
# What if there are more?
|
|
||||||
resultarray = []
|
|
||||||
for i in range(0, curminlength):
|
|
||||||
tmpitem = json.loads(json.dumps(parameter["value"]))
|
|
||||||
for key, value in replacements.items():
|
|
||||||
replacement = value
|
|
||||||
try:
|
|
||||||
replacement = json.dumps(json.loads(value)[i])
|
|
||||||
except IndexError as e:
|
|
||||||
self.logger.info(f"[ERROR] Failed handling value parsing with index: {e}")
|
|
||||||
pass
|
|
||||||
|
|
||||||
if replacement.startswith("\"") and replacement.endswith("\""):
|
|
||||||
replacement = replacement[1:len(replacement)-1]
|
|
||||||
#except json.decoder.JSONDecodeError as e:
|
|
||||||
|
|
||||||
#self.logger.info("REPLACING %s with %s" % (key, replacement))
|
|
||||||
#replacement = parse_wrapper_start(replacement)
|
|
||||||
tmpitem = tmpitem.replace(key, replacement, -1)
|
|
||||||
try:
|
|
||||||
tmpitem = parse_liquid(tmpitem, self)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}")
|
|
||||||
|
|
||||||
|
|
||||||
# This code handles files.
|
|
||||||
isfile = False
|
|
||||||
try:
|
|
||||||
if parameter["schema"]["type"] == "file" and len(value) > 0:
|
|
||||||
self.logger.info("(2) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % parameter["value"])
|
|
||||||
|
|
||||||
for tmp_file_split in json.loads(parameter["value"]):
|
|
||||||
file_value = self.get_file(tmp_file_split)
|
|
||||||
resultarray.append(file_value)
|
|
||||||
|
|
||||||
|
|
||||||
isfile = True
|
|
||||||
except KeyError as e:
|
|
||||||
self.logger.info("(2) SCHEMA ERROR IN FILE HANDLING: %s" % e)
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
|
||||||
self.logger.info("(2) JSON ERROR IN FILE HANDLING: %s" % e)
|
|
||||||
|
|
||||||
if not isfile:
|
|
||||||
tmpitem = tmpitem.replace("\\\\", "\\", -1)
|
|
||||||
resultarray.append(tmpitem)
|
|
||||||
|
|
||||||
# With this parameter ready, add it to... a greater list of parameters. Rofl
|
|
||||||
if len(resultarray) == 0:
|
|
||||||
self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (0)")
|
|
||||||
self.action_result["status"] = "SUCCESS"
|
|
||||||
self.action_result["result"] = "[]"
|
|
||||||
self.send_result(self.action_result, headers, stream_path)
|
|
||||||
return
|
|
||||||
|
|
||||||
#self.logger.info("RESULTARRAY: %s" % resultarray)
|
|
||||||
if resultarray not in multi_execution_lists:
|
|
||||||
multi_execution_lists.append(resultarray)
|
|
||||||
|
|
||||||
multi_parameters[parameter["name"]] = resultarray
|
|
||||||
else:
|
else:
|
||||||
# Parses things like int(value)
|
# Parses things like int(value)
|
||||||
#self.logger.info("[DEBUG] Normal parsing (not looping)")#with data %s" % value)
|
#self.logger.info("[DEBUG] Normal parsing (not looping)")#with data %s" % value)
|
||||||
@@ -3653,8 +3559,7 @@ class AppBase:
|
|||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
self.logger.info("SCHEMA ERROR IN FILE HANDLING: %s" % e)
|
self.logger.info("SCHEMA ERROR IN FILE HANDLING: %s" % e)
|
||||||
|
|
||||||
|
|
||||||
#remove_params.append(parameter["name"])
|
|
||||||
# Fix lists here
|
# Fix lists here
|
||||||
# FIXME: This doesn't really do anything anymore
|
# FIXME: This doesn't really do anything anymore
|
||||||
#self.logger.info("[DEBUG] CHECKING multi execution list: %d!" % len(multi_execution_lists))
|
#self.logger.info("[DEBUG] CHECKING multi execution list: %d!" % len(multi_execution_lists))
|
||||||
@@ -3932,7 +3837,8 @@ class AppBase:
|
|||||||
# 1. Use number of executions based on the arrays being similar
|
# 1. Use number of executions based on the arrays being similar
|
||||||
# 2. Find the right value from the parsed multi_params
|
# 2. Find the right value from the parsed multi_params
|
||||||
|
|
||||||
self.logger.info("[INFO] Running WITHOUT outer loop (looping)")
|
#self.logger.info("[INFO] Running WITH loop. MULTI: %s", multi_parameters)
|
||||||
|
self.logger.info("[INFO] Running WITH loop")
|
||||||
json_object = False
|
json_object = False
|
||||||
#results = await self.run_recursed_items(func, multi_parameters, {})
|
#results = await self.run_recursed_items(func, multi_parameters, {})
|
||||||
results = self.run_recursed_items(func, multi_parameters, {})
|
results = self.run_recursed_items(func, multi_parameters, {})
|
||||||
|
|||||||
@@ -3583,8 +3583,8 @@ func runInitEs(ctx context.Context) {
|
|||||||
setUsers := false
|
setUsers := false
|
||||||
_ = setUsers
|
_ = setUsers
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if fmt.Sprintf("%s", err) == "EOF" {
|
if fmt.Sprintf("%s", err) == "EOF" || strings.Contains(fmt.Sprintf("%s", err), "bad status") {
|
||||||
time.Sleep(7 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
runInitEs(ctx)
|
runInitEs(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3668,7 +3668,7 @@ func runInitEs(ctx context.Context) {
|
|||||||
|
|
||||||
if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") {
|
if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") {
|
||||||
log.Printf("[INFO] Waiting during init to make sure the opensearch instance is up and running with security features properly")
|
log.Printf("[INFO] Waiting during init to make sure the opensearch instance is up and running with security features properly")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
schedules, err := shuffle.GetAllSchedules(ctx, "ALL")
|
schedules, err := shuffle.GetAllSchedules(ctx, "ALL")
|
||||||
@@ -4780,6 +4780,7 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
|
r.HandleFunc("/api/v1/users/apps", shuffle.HandleGetUserApps).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/getsettings", shuffle.HandleSettings).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users/getsettings", shuffle.HandleSettings).Methods("GET", "OPTIONS")
|
||||||
@@ -4908,8 +4909,8 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS")
|
||||||
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
|
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
||||||
|
|||||||
+6
-6
@@ -1,18 +1,17 @@
|
|||||||
# Build environment
|
# Build environment
|
||||||
FROM node:18 as builder
|
FROM node:21 as builder
|
||||||
|
|
||||||
RUN mkdir /usr/src/app
|
RUN mkdir /usr/src/app
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||||
|
|
||||||
COPY package.json /usr/src/app/package.json
|
COPY package.json /usr/src/app/package.json
|
||||||
|
|
||||||
# Nocache yarn install
|
# Nocache yarn install
|
||||||
RUN yarn config set "strict-ssl" false -g
|
#RUN yarn config set "strict-ssl" false -g
|
||||||
RUN yarn install --network-timeout 1000000
|
#RUN yarn install --network-timeout 1000000
|
||||||
|
|
||||||
#RUN npm install
|
RUN npm install --legacy-peer-deps
|
||||||
|
|
||||||
# copy only required files to not trigger rebuilding every time
|
# copy only required files to not trigger rebuilding every time
|
||||||
COPY ./certs /usr/src/app/certs/
|
COPY ./certs /usr/src/app/certs/
|
||||||
@@ -22,7 +21,8 @@ COPY ./*.sh /usr/src/app/
|
|||||||
COPY ./*.json /usr/src/app/
|
COPY ./*.json /usr/src/app/
|
||||||
|
|
||||||
#RUN rm -rf /usr/src/app/node_modules/webpack
|
#RUN rm -rf /usr/src/app/node_modules/webpack
|
||||||
RUN yarn build
|
#RUN yarn build --verbose
|
||||||
|
RUN npm run build --loglevel verbose 2>&1
|
||||||
|
|
||||||
# Production environment
|
# Production environment
|
||||||
FROM nginx:1.21.5
|
FROM nginx:1.21.5
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "shuffler",
|
"name": "shuffler",
|
||||||
"homepage": "https://shuffler.io",
|
"homepage": "https://shuffler.io",
|
||||||
"version": "1.3.3",
|
"version": "1.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
"create-react-app": "^5.0.1",
|
"create-react-app": "^5.0.1",
|
||||||
"cytoscape": "^3.23.0",
|
"cytoscape": "^3.29.2",
|
||||||
"cytoscape-clipboard": "^2.2.1",
|
"cytoscape-clipboard": "^2.2.1",
|
||||||
"cytoscape-cxtmenu": "^3.4.0",
|
"cytoscape-cxtmenu": "^3.4.0",
|
||||||
"cytoscape-edgehandles": "^3.5.1",
|
"cytoscape-edgehandles": "^3.5.1",
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import {
|
|||||||
Delete as DeleteIcon,
|
Delete as DeleteIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
import * as edgehandles from "cytoscape-edgehandles";
|
import edgehandles from "cytoscape-edgehandles";
|
||||||
import * as cytoscape from "cytoscape";
|
import cytoscape from "cytoscape";
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
cytoscape.use(edgehandles);
|
cytoscape.use(edgehandles);
|
||||||
|
|||||||
+1970
-331
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ import BillingStats from "../components/BillingStats.jsx";
|
|||||||
import { handlePayasyougo } from "../views/HandlePaymentNew.jsx"
|
import { handlePayasyougo } from "../views/HandlePaymentNew.jsx"
|
||||||
|
|
||||||
const Billing = (props) => {
|
const Billing = (props) => {
|
||||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props;
|
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props;
|
||||||
//const alert = useAlert();
|
//const alert = useAlert();
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
@@ -970,21 +970,29 @@ const Billing = (props) => {
|
|||||||
|
|
||||||
const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null
|
const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null
|
||||||
return (
|
return (
|
||||||
<div style={{}}>
|
<div style={{ width: clickedFromOrgTab? 1030 : "auto", padding: 27, backgroundColor: '#212121', borderRadius: '16px', }}>
|
||||||
{addDealModal}
|
{addDealModal}
|
||||||
|
{clickedFromOrgTab?
|
||||||
|
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Billing & Licensing</h2>:
|
||||||
<Typography variant="h4" style={{ marginTop: 20, marginBottom: 10 }}>
|
<Typography variant="h4" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||||
Billing & Licensing
|
Billing & Licensing
|
||||||
</Typography>
|
</Typography>}
|
||||||
|
{clickedFromOrgTab?
|
||||||
|
<span style={{ color: "#9E9E9E" }}>{isCloud ?
|
||||||
|
"Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below."
|
||||||
|
:
|
||||||
|
"Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update."
|
||||||
|
}</span>:
|
||||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 0, marginBottom: 10}}>
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 0, marginBottom: 10}}>
|
||||||
{isCloud ?
|
{isCloud ?
|
||||||
"Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below."
|
"Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below."
|
||||||
:
|
:
|
||||||
"Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update."
|
"Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update."
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>}
|
||||||
|
|
||||||
{userdata.support === true ?
|
{userdata.support === true ?
|
||||||
<div style={{marginBottom: 10, }}>
|
<div style={{marginBottom: 10, marginTop:clickedFromOrgTab?16:null, color:clickedFromOrgTab?"#F1F1F1":null }}>
|
||||||
For sales: Create
|
For sales: Create
|
||||||
<a href={"https://docs.google.com/document/d/1OeJSi42812EMg7fUAw1HAj1ymOG8rfp8Ma_DGJKvwgI/copy?usp=sharing&organization=" + selectedOrganization.id} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
<a href={"https://docs.google.com/document/d/1OeJSi42812EMg7fUAw1HAj1ymOG8rfp8Ma_DGJKvwgI/copy?usp=sharing&organization=" + selectedOrganization.id} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||||
New Cloud Contract
|
New Cloud Contract
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
//import { useAlert
|
//import { useAlert
|
||||||
|
|
||||||
const Branding = (props) => {
|
const Branding = (props) => {
|
||||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props;
|
const { globalUrl, userdata, serverside, billingInfo,clickedFromOrgTab, stripeKey, selectedOrganization, handleGetOrg, } = props;
|
||||||
//const alert = useAlert();
|
//const alert = useAlert();
|
||||||
const [publishingInfo, setPublishingInfo] = useState("");
|
const [publishingInfo, setPublishingInfo] = useState("");
|
||||||
const [publishRequirements, setPublishRequirements] = useState([])
|
const [publishRequirements, setPublishRequirements] = useState([])
|
||||||
@@ -103,8 +103,8 @@ const Branding = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ width: clickedFromOrgTab? 1030: "auto", padding: 27, height: "auto", backgroundColor: '#212121', borderRadius: '16px', }}>
|
||||||
<h2>
|
<h2 style={{marginTop: clickedFromOrgTab ?0:null,}}>
|
||||||
Branding
|
Branding
|
||||||
</h2>
|
</h2>
|
||||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import ReactJson from "react-json-view";
|
import ReactJson from "react-json-view";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Divider,
|
Divider,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -22,8 +22,8 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Link as LinkIcon,
|
Link as LinkIcon,
|
||||||
AutoFixHigh as AutoFixHighIcon,
|
AutoFixHigh as AutoFixHighIcon,
|
||||||
Edit as EditIcon,
|
Edit as EditIcon,
|
||||||
FileCopy as FileCopyIcon,
|
FileCopy as FileCopyIcon,
|
||||||
SelectAll as SelectAllIcon,
|
SelectAll as SelectAllIcon,
|
||||||
@@ -46,7 +46,7 @@ import {
|
|||||||
Visibility as VisibilityIcon,
|
Visibility as VisibilityIcon,
|
||||||
VisibilityOff as VisibilityOffIcon,
|
VisibilityOff as VisibilityOffIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { validateJson, } from "../views/Workflows.jsx";
|
import { validateJson, } from "../views/Workflows.jsx";
|
||||||
|
|
||||||
const scrollStyle1 = {
|
const scrollStyle1 = {
|
||||||
height: 100,
|
height: 100,
|
||||||
@@ -64,9 +64,9 @@ const scrollStyle2 = {
|
|||||||
overflow: "scroll",
|
overflow: "scroll",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const CacheView = (props) => {
|
const CacheView = (props) => {
|
||||||
const { globalUrl, userdata, serverside, orgId } = props;
|
const { globalUrl, userdata, serverside, orgId, isSelectedDataStore } = props;
|
||||||
const [orgCache, setOrgCache] = React.useState("");
|
const [orgCache, setOrgCache] = React.useState("");
|
||||||
const [listCache, setListCache] = React.useState([]);
|
const [listCache, setListCache] = React.useState([]);
|
||||||
const [addCache, setAddCache] = React.useState("");
|
const [addCache, setAddCache] = React.useState("");
|
||||||
@@ -79,7 +79,7 @@ const CacheView = (props) => {
|
|||||||
const [dataValue, setDataValue] = React.useState({});
|
const [dataValue, setDataValue] = React.useState({});
|
||||||
const [editCache, setEditCache] = React.useState(false);
|
const [editCache, setEditCache] = React.useState(false);
|
||||||
const [show, setShow] = useState({});
|
const [show, setShow] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listOrgCache(orgId);
|
listOrgCache(orgId);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -115,62 +115,25 @@ const CacheView = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// const getCacheList = (orgId) => {
|
|
||||||
// fetch(`${globalUrl}/api/v1/orgs/${orgId}/get_cache`, {
|
|
||||||
// method: "GET",
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// Accept: "application/json",
|
|
||||||
// },
|
|
||||||
// credentials: "include",
|
|
||||||
// })
|
|
||||||
// .then((response) => {
|
|
||||||
// if (response.status !== 200) {
|
|
||||||
// console.log("Status not 200 for WORKFLOW EXECUTION :O!");
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// return response.json();
|
|
||||||
// })
|
|
||||||
// .then((responseJson) => {
|
|
||||||
// if (responseJson.success !== false) {
|
|
||||||
// console.log("Found cache: ", responseJson)
|
|
||||||
// setListCache(responseJson)
|
|
||||||
// } else {
|
|
||||||
// console.log("Couldn't find the creator profile (rerun?): ", responseJson)
|
|
||||||
// // If the current user is any of the Shuffle Creators
|
|
||||||
// // AND the workflow doesn't have an owner: allow editing.
|
|
||||||
// // else: Allow suggestions?
|
|
||||||
// //console.log("User: ", userdata)
|
|
||||||
// //if (rerun !== true) {
|
|
||||||
// // getUserProfile(userdata.id, true)
|
|
||||||
// //}
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// console.log("Get userprofile error: ", error);
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
const deleteCache = (orgId, key) => {
|
const deleteCache = (orgId, key) => {
|
||||||
toast("Attempting to delete Cache");
|
toast("Attempting to delete Cache");
|
||||||
|
|
||||||
// method: "DELETE",
|
// method: "DELETE",
|
||||||
const method = "POST"
|
const method = "POST"
|
||||||
//const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/${key}`
|
//const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/${key}`
|
||||||
const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache`
|
const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache`
|
||||||
const parsed = {
|
const parsed = {
|
||||||
"org_id": orgId,
|
"org_id": orgId,
|
||||||
"key": key,
|
"key": key,
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(parsed),
|
body: JSON.stringify(parsed),
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -255,20 +218,20 @@ const CacheView = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidJson = validateJson(value)
|
const isValidJson = validateJson(value)
|
||||||
const autoFixJson = (inputvalue) => {
|
const autoFixJson = (inputvalue) => {
|
||||||
console.log("inputvalue: ", inputvalue)
|
console.log("inputvalue: ", inputvalue)
|
||||||
try {
|
try {
|
||||||
var parsedjson = JSON.parse(inputvalue)
|
var parsedjson = JSON.parse(inputvalue)
|
||||||
|
|
||||||
// setValue() with the parsed json as string
|
// setValue() with the parsed json as string
|
||||||
setValue(JSON.stringify(parsedjson, null, 2))
|
setValue(JSON.stringify(parsedjson, null, 2))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error parsing JSON: ", e)
|
console.log("Error parsing JSON: ", e)
|
||||||
//return JSON.stringify(inputvalue);
|
//return JSON.stringify(inputvalue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const modalView = (
|
const modalView = (
|
||||||
// console.log("key:", dataValue.key),
|
// console.log("key:", dataValue.key),
|
||||||
//console.log("value:",dataValue.value),
|
//console.log("value:",dataValue.value),
|
||||||
@@ -316,21 +279,21 @@ const CacheView = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ paddingLeft: 30, paddingRight: 30 }}>
|
<div style={{ paddingLeft: 30, paddingRight: 30 }}>
|
||||||
<div style={{display: "flex", }}>
|
<div style={{display: "flex", }}>
|
||||||
<Typography style={{marginTop: 25, marginBottom: 0, flex: 20, }}>
|
<Typography style={{marginTop: 25, marginBottom: 0, flex: 20, }}>
|
||||||
Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON)
|
Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON)
|
||||||
</Typography>
|
</Typography>
|
||||||
<Tooltip title="Auto Fix JSON" placement="right">
|
<Tooltip title="Auto Fix JSON" placement="right">
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{flex: 1, }}
|
style={{flex: 1, }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
autoFixJson(value)
|
autoFixJson(value)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AutoFixHighIcon />
|
<AutoFixHighIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<TextField
|
<TextField
|
||||||
color="primary"
|
color="primary"
|
||||||
style={{ backgroundColor: theme.palette.inputColor, marginTop: 0, }}
|
style={{ backgroundColor: theme.palette.inputColor, marginTop: 0, }}
|
||||||
@@ -347,22 +310,22 @@ const CacheView = (props) => {
|
|||||||
id="Valuefield"
|
id="Valuefield"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
multiline
|
multiline
|
||||||
minRows={4}
|
minRows={4}
|
||||||
maxRows={12}
|
maxRows={12}
|
||||||
//defaultValue={editCache ? dataValue.value : ""}
|
//defaultValue={editCache ? dataValue.value : ""}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DialogActions style={{ paddingLeft: "30px", paddingRight: '30px' }}>
|
<DialogActions style={{ paddingLeft: "30px", paddingRight: '30px' }}>
|
||||||
<Button
|
<Button
|
||||||
style={{ borderRadius: "0px" }}
|
style={{ borderRadius: "0px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false)
|
setModalOpen(false)
|
||||||
setValue("")
|
setValue("")
|
||||||
setDataValue({})
|
setDataValue({})
|
||||||
}}
|
}}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
@@ -372,9 +335,9 @@ const CacheView = (props) => {
|
|||||||
style={{ borderRadius: "0px" }}
|
style={{ borderRadius: "0px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
{editCache ? editOrgCache(orgId) : addOrgCache(orgId)}
|
{editCache ? editOrgCache(orgId) : addOrgCache(orgId)}
|
||||||
|
|
||||||
setValue("")
|
setValue("")
|
||||||
setDataValue({})
|
setDataValue({})
|
||||||
}}
|
}}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
@@ -386,116 +349,117 @@ const CacheView = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div style={{paddingBottom: 250, }}>
|
<div style={{paddingBottom: isSelectedDataStore?null:250, width: isSelectedDataStore?1030:null, padding:isSelectedDataStore?27:null, height: isSelectedDataStore?1200:null, color: isSelectedDataStore?'#ffffff':null, backgroundColor: isSelectedDataStore?'#212121':null, borderRadius: isSelectedDataStore?'16px':null, }}>
|
||||||
{modalView}
|
{modalView}
|
||||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
<div style={{ marginTop: isSelectedDataStore?null:20, marginBottom: 20 }}>
|
||||||
<h2 style={{ display: "inline" }}>Shuffle Datastore</h2>
|
<h2 style={{ display: isSelectedDataStore?null: "inline" }}>Shuffle Datastore</h2>
|
||||||
<span style={{ marginLeft: 25 }}>
|
<span style={{ marginLeft: isSelectedDataStore?null:25, color:isSelectedDataStore?"#9E9E9E":null}}>
|
||||||
Datastore is a permanent key-value database for storing data that can be used cross-workflow. You can store anything from lists of IPs to complex configurations.
|
Datastore is a permanent key-value database for storing data that can be used cross-workflow. <br/>You can store anything from lists of IPs to complex configurations.
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href="/docs/organizations#datastore"
|
href="/docs/organizations#datastore"
|
||||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
style={{ textDecoration: isSelectedDataStore?null:"none", color: isSelectedDataStore?"#FF8444":"#f85a3e" }}
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
style={{}}
|
style={{backgroundColor: isSelectedDataStore?'rgba(255, 132, 68, 0.2)':null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null, color:isSelectedDataStore?"#FF8444":null, borderRadius:isSelectedDataStore?200:null, width:isSelectedDataStore?162:null, height:isSelectedDataStore?40:null}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() =>{
|
onClick={() =>{
|
||||||
setEditCache(false)
|
setEditCache(false)
|
||||||
setModalOpen(true)
|
setModalOpen(true)
|
||||||
|
|
||||||
setValue("")
|
setValue("")
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Add Cache
|
Add Cache
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
style={{ marginLeft: 5, marginRight: 15 }}
|
style={{ marginLeft: 5, marginRight: 15, backgroundColor: isSelectedDataStore?"#2F2F2F":null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null,borderRadius:isSelectedDataStore?200:null, width:isSelectedDataStore?81:null, height:isSelectedDataStore?40:null, }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => listOrgCache(orgId)}
|
onClick={() => listOrgCache(orgId)}
|
||||||
>
|
>
|
||||||
<CachedIcon />
|
<CachedIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Divider
|
{isSelectedDataStore? null :<Divider
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
}}
|
}}
|
||||||
/>
|
/>}
|
||||||
<List>
|
<List style={{borderRadius: isSelectedDataStore?8:null, border:isSelectedDataStore?"1px solid #494949":null, marginTop:isSelectedDataStore?24:null}}>
|
||||||
<ListItem>
|
<ListItem style={{width: isSelectedDataStore?"100%":null, borderBottom:isSelectedDataStore?"1px solid #494949":null}}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Key"
|
primary="Key"
|
||||||
style={{ minWidth: 250, maxWidth: 250, }}
|
style={{ minWidth: isSelectedDataStore?200:250, maxWidth: isSelectedDataStore?200:250, }}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Value"
|
primary="Value"
|
||||||
style={{ minWidth: 400, maxWidth: 400, overflowX: "auto", overflowY: "hidden", }}
|
style={{ minWidth: isSelectedDataStore?300:400, maxWidth: isSelectedDataStore?300:400, overflowX: "auto", overflowY: "hidden", }}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Actions"
|
primary="Actions"
|
||||||
style={{ minWidth: 150, maxWidth: 150, marginLeft: 50, }}
|
style={{ minWidth: 150, maxWidth: 150, marginLeft: isSelectedDataStore?80:null,}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
|
style={{textAlign:isSelectedDataStore?"center":null}}
|
||||||
primary="Updated"
|
primary="Updated"
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{listCache === undefined || listCache === null
|
{listCache === undefined || listCache === null
|
||||||
? null
|
? null
|
||||||
: listCache.map((data, index) => {
|
: listCache.map((data, index) => {
|
||||||
var bgColor = "#27292d";
|
var bgColor = isSelectedDataStore? "#212121":"#27292d";
|
||||||
if (index % 2 === 0) {
|
if (index % 2 === 0) {
|
||||||
bgColor = "#1f2023";
|
bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023";
|
||||||
}
|
}
|
||||||
|
|
||||||
const validate = validateJson(data.value);
|
const validate = validateJson(data.value);
|
||||||
return (
|
return (
|
||||||
<ListItem key={index} style={{ backgroundColor: bgColor, maxHeight: 300, overflow: "auto", }}>
|
<ListItem key={index} style={{ backgroundColor: bgColor, maxHeight: 300, overflow: "auto", }}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 250,
|
maxWidth: 200,
|
||||||
minWidth: 250,
|
minWidth: 200,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
primary={data.key}
|
primary={data.key}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
minWidth: 400,
|
minWidth: 300,
|
||||||
maxWidth: 400,
|
maxWidth: 300,
|
||||||
}}
|
}}
|
||||||
primary={validate.valid ?
|
primary={validate.valid ?
|
||||||
<ReactJson
|
<ReactJson
|
||||||
src={validate.result}
|
src={validate.result}
|
||||||
theme={theme.palette.jsonTheme}
|
theme={theme.palette.jsonTheme}
|
||||||
style={theme.palette.reactJsonStyle}
|
style={theme.palette.reactJsonStyle}
|
||||||
collapsed={true}
|
collapsed={true}
|
||||||
enableClipboard={(copy) => {
|
enableClipboard={(copy) => {
|
||||||
//handleReactJsonClipboard(copy);
|
//handleReactJsonClipboard(copy);
|
||||||
}}
|
}}
|
||||||
displayDataTypes={false}
|
displayDataTypes={false}
|
||||||
onSelect={(select) => {
|
onSelect={(select) => {
|
||||||
//HandleJsonCopy(showResult, select, data.action.label);
|
//HandleJsonCopy(showResult, select, data.action.label);
|
||||||
//console.log("SELECTED!: ", select);
|
//console.log("SELECTED!: ", select);
|
||||||
}}
|
}}
|
||||||
name={"value"}
|
name={"value"}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
data.value
|
data.value
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
marginLeft: 50,
|
marginLeft: 50,
|
||||||
}}
|
}}
|
||||||
primary=<span style={{ display: "inline" }}>
|
primary=<span style={{ display: "inline" }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -508,11 +472,11 @@ const CacheView = (props) => {
|
|||||||
style={{ padding: "6px" }}
|
style={{ padding: "6px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditCache(true)
|
setEditCache(true)
|
||||||
setDataValue({
|
setDataValue({
|
||||||
"key": data.key,
|
"key": data.key,
|
||||||
"value":data.value
|
"value":data.value
|
||||||
})
|
})
|
||||||
setValue(data.value)
|
setValue(data.value)
|
||||||
setModalOpen(true)
|
setModalOpen(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -525,18 +489,18 @@ const CacheView = (props) => {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={"Public URL (types: text, raw, json)"}
|
title={"Public URL (types: text, raw, json)"}
|
||||||
style={{ marginLeft: 0, }}
|
style={{ marginLeft: 0, }}
|
||||||
aria-label={"Public URL"}
|
aria-label={"Public URL"}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{ padding: "6px" }}
|
style={{ padding: "6px" }}
|
||||||
disabled={data.public_authorization === undefined || data.public_authorization === null || data.public_authorization === "" ? true : false}
|
disabled={data.public_authorization === undefined || data.public_authorization === null || data.public_authorization === "" ? true : false}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(`${globalUrl}/api/v1/orgs/${orgId}/cache/${data.key}?type=text&authorization=${data.public_authorization}`, "_blank");
|
window.open(`${globalUrl}/api/v1/orgs/${orgId}/cache/${data.key}?type=text&authorization=${data.public_authorization}`, "_blank");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LinkIcon
|
<LinkIcon
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
|||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
|
|
||||||
const Files = (props) => {
|
const Files = (props) => {
|
||||||
const { globalUrl, userdata, serverside, selectedOrganization, isCloud, } = props;
|
const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props;
|
||||||
|
|
||||||
const [files, setFiles] = React.useState([]);
|
const [files, setFiles] = React.useState([]);
|
||||||
const [selectedNamespace, setSelectedNamespace] = React.useState("default");
|
const [selectedNamespace, setSelectedNamespace] = React.useState("default");
|
||||||
@@ -617,16 +617,16 @@ const Files = (props) => {
|
|||||||
style={{
|
style={{
|
||||||
maxWidth: window.innerWidth > 1366 ? 1366 : 1200,
|
maxWidth: window.innerWidth > 1366 ? 1366 : 1200,
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
padding: 20,
|
padding: isSelectedFiles ? null : 20,
|
||||||
}}
|
}}
|
||||||
onDrop={uploadFile}
|
onDrop={uploadFile}
|
||||||
>
|
>
|
||||||
<div style={{position: "relative"}}>
|
<div style={{position: "relative", width: isSelectedFiles? 1030: null, padding:isSelectedFiles?27:null, height: isSelectedFiles?1200:null, color: isSelectedFiles?'#ffffff':null, backgroundColor: isSelectedFiles?'#212121':null, borderRadius: isSelectedFiles?'16px':null,}}>
|
||||||
|
|
||||||
<Tooltip color="primary" title={"Import files to Shuffle from Git"} placement="top">
|
<Tooltip color="primary" title={"Import files to Shuffle from Git"} placement="top">
|
||||||
<IconButton
|
<IconButton
|
||||||
color="secondary"
|
color="secondary"
|
||||||
style={{position: "absolute", right: 0, top: 0, }}
|
style={{position: "absolute", right: 0, top: isSelectedFiles?null:0, left: isSelectedFiles? 990:null }}
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={() => setLoadFileModalOpen(true)}
|
onClick={() => setLoadFileModalOpen(true)}
|
||||||
>
|
>
|
||||||
@@ -636,15 +636,15 @@ const Files = (props) => {
|
|||||||
|
|
||||||
{fileDownloadModal}
|
{fileDownloadModal}
|
||||||
|
|
||||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
<div style={{ marginTop: isSelectedFiles ? 2: 20, marginBottom:20 }}>
|
||||||
<h2 style={{ display: "inline" }}>Files</h2>
|
<h2 style={{ display: isSelectedFiles ? null : "inline", marginTop: isSelectedFiles?0:null, marginBottom: isSelectedFiles?8:null }}>Files</h2>
|
||||||
<span style={{ marginLeft: 25 }}>
|
<span style={{ marginLeft: isSelectedFiles ? null : 25, color:isSelectedFiles?"#9E9E9E":null}}>
|
||||||
Files from Workflows are a way to store as well as edit files.{" "}
|
Files from Workflows are a way to store as well as edit files.{" "}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href="https://shuffler.io/docs/organizations#files"
|
href="https://shuffler.io/docs/organizations#files"
|
||||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
style={{ textDecoration: isSelectedFiles ? null:"none", color: isSelectedFiles? "#FF8444": "#f85a3e" }}
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
@@ -659,6 +659,7 @@ const Files = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
upload.click();
|
upload.click();
|
||||||
}}
|
}}
|
||||||
|
style={{backgroundColor: isSelectedFiles?'rgba(255, 132, 68, 0.2)':null, color:isSelectedFiles?"#FF8444":null, borderRadius:isSelectedFiles?200:null, width:isSelectedFiles?162:null, height:isSelectedFiles?40:null, boxShadow: isSelectedFiles?'none':null,}}
|
||||||
>
|
>
|
||||||
<PublishIcon /> Upload files
|
<PublishIcon /> Upload files
|
||||||
</Button>
|
</Button>
|
||||||
@@ -678,7 +679,7 @@ const Files = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
style={{ marginLeft: 5, marginRight: 15 }}
|
style={{ marginLeft: 5, marginRight: 15, backgroundColor:isSelectedFiles?"#2F2F2F":null,borderRadius:isSelectedFiles?200:null, width:isSelectedFiles?81:null, height:isSelectedFiles?40:null, boxShadow: isSelectedFiles?'none':null, }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => getFiles()}
|
onClick={() => getFiles()}
|
||||||
@@ -783,20 +784,20 @@ const Files = (props) => {
|
|||||||
key = {fileContent} //https://reactjs.org/docs/reconciliation.html#recursing-on-children
|
key = {fileContent} //https://reactjs.org/docs/reconciliation.html#recursing-on-children
|
||||||
runUpdateText = {runUpdateText}
|
runUpdateText = {runUpdateText}
|
||||||
/>
|
/>
|
||||||
|
{isSelectedFiles?null:
|
||||||
<Divider
|
<Divider
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>}
|
||||||
|
|
||||||
<List>
|
<List style={{borderRadius: isSelectedFiles?8:null, border:isSelectedFiles?"1px solid #494949":null, marginTop:isSelectedFiles?24:null}}>
|
||||||
<ListItem>
|
<ListItem style={{width:isSelectedFiles?"100%":null, borderBottom:isSelectedFiles?"1px solid #494949":null}}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Updated"
|
primary="Updated"
|
||||||
style={{ maxWidth: 225, minWidth: 225 }}
|
style={{ maxWidth: 185, minWidth: 185 }}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Name"
|
primary="Name"
|
||||||
@@ -813,7 +814,7 @@ const Files = (props) => {
|
|||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Md5"
|
primary="Md5"
|
||||||
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
|
style={{ minWidth: isSelectedFiles?210:250, maxWidth: isSelectedFiles?210:250, overflow: "hidden" }}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Status"
|
primary="Status"
|
||||||
@@ -835,9 +836,9 @@ const Files = (props) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bgColor = "#27292d";
|
var bgColor = isSelectedFiles ? "#212121":"#27292d";
|
||||||
if (index % 2 === 0) {
|
if (index % 2 === 0) {
|
||||||
bgColor = "#1f2023";
|
bgColor = isSelectedFiles ? "#1A1A1A":"#1f2023";
|
||||||
}
|
}
|
||||||
|
|
||||||
const filenamesplit = file.filename.split(".")
|
const filenamesplit = file.filename.split(".")
|
||||||
@@ -854,8 +855,8 @@ const Files = (props) => {
|
|||||||
>
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 225,
|
maxWidth: isSelectedFiles ? 170:225,
|
||||||
minWidth: 225,
|
minWidth: isSelectedFiles ? 170:225,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
primary={new Date(file.updated_at * 1000).toISOString()}
|
primary={new Date(file.updated_at * 1000).toISOString()}
|
||||||
@@ -921,14 +922,16 @@ const Files = (props) => {
|
|||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
maxWidth: 100,
|
maxWidth: 100,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
textAlign: isSelectedFiles?"center":null
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={file.md5_sum}
|
primary={file.md5_sum}
|
||||||
style={{
|
style={{
|
||||||
minWidth: 300,
|
minWidth: isSelectedFiles?200:300,
|
||||||
maxWidth: 300,
|
maxWidth: isSelectedFiles?200:300,
|
||||||
overflow: "hidden",
|
marginLeft:isSelectedFiles? 15:null,
|
||||||
|
overflow: isSelectedFiles?"auto":"hidden",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
@@ -937,14 +940,16 @@ const Files = (props) => {
|
|||||||
minWidth: 75,
|
minWidth: 75,
|
||||||
maxWidth: 75,
|
maxWidth: 75,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
textAlign:isSelectedFiles?"center":null,
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={file.filesize}
|
primary={file.filesize}
|
||||||
style={{
|
style={{
|
||||||
minWidth: 125,
|
minWidth: isSelectedFiles?80:125,
|
||||||
maxWidth: 125,
|
maxWidth: isSelectedFiles?80:125,
|
||||||
|
marginLeft: isSelectedFiles?15:null,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -1063,7 +1068,7 @@ const Files = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={"Delete file"}
|
title={"Delete file"}
|
||||||
style={{marginLeft: 15, }}
|
style={{marginLeft: isSelectedFiles?5:15, }}
|
||||||
aria-label={"Delete"}
|
aria-label={"Delete"}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/* Style for the outer container */
|
||||||
|
.ais-RefinementList {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for the list */
|
||||||
|
.ais-RefinementList-list {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for each list item */
|
||||||
|
.ais-RefinementList-item {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for label */
|
||||||
|
.ais-RefinementList-label {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for checkbox */
|
||||||
|
.ais-RefinementList-checkbox {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid #595b5e;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #27292d;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: "10px";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ais-RefinementList-checkbox:checked {
|
||||||
|
background-color: rgb(248, 103, 67);
|
||||||
|
border-color: rgb(248, 103, 67);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for checked state - inner dot */
|
||||||
|
.ais-RefinementList-checkbox:checked::before {
|
||||||
|
content: "\2714";
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 18px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for hover state */
|
||||||
|
/* .ais-RefinementList-checkbox:hover {
|
||||||
|
border-color: rgb(248, 103, 67);
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* Style for label text */
|
||||||
|
.ais-RefinementList-labelText {
|
||||||
|
/* margin-left: 5px; */
|
||||||
|
font-size: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for count */
|
||||||
|
.ais-RefinementList-count {
|
||||||
|
/* margin-left: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: gray; */
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ais-ClearRefinements-button {
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgb(248, 103, 67);
|
||||||
|
border: none;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
@@ -103,7 +103,8 @@ const Header = (props) => {
|
|||||||
serverside === true || typeof window === "undefined"
|
serverside === true || typeof window === "undefined"
|
||||||
? true
|
? true
|
||||||
: window.location.host === "localhost:3002" ||
|
: window.location.host === "localhost:3002" ||
|
||||||
window.location.host === "shuffler.io";
|
window.location.host === "shuffler.io" ||
|
||||||
|
window.location.host === "localhost:5002";
|
||||||
|
|
||||||
const clearNotifications = () => {
|
const clearNotifications = () => {
|
||||||
// Don't really care about the logout
|
// Don't really care about the logout
|
||||||
@@ -616,7 +617,7 @@ const Header = (props) => {
|
|||||||
<Divider style={{marginBottom: 10, }}/>
|
<Divider style={{marginBottom: 10, }}/>
|
||||||
|
|
||||||
<Typography variant="body2" color="textSecondary" align="center" style={{marginTop: 5, marginBottom: 5,}}>
|
<Typography variant="body2" color="textSecondary" align="center" style={{marginTop: 5, marginBottom: 5,}}>
|
||||||
Version: 1.3.4
|
Version: 1.4.0
|
||||||
</Typography>
|
</Typography>
|
||||||
</Menu>
|
</Menu>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import Priority from "../components/Priority.jsx";
|
|||||||
//import { useAlert
|
//import { useAlert
|
||||||
|
|
||||||
const Priorities = (props) => {
|
const Priorities = (props) => {
|
||||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props;
|
const { globalUrl, userdata,clickedFromOrgTab, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props;
|
||||||
const [showDismissed, setShowDismissed] = React.useState(false);
|
const [showDismissed, setShowDismissed] = React.useState(false);
|
||||||
const [showRead, setShowRead] = React.useState(false);
|
const [showRead, setShowRead] = React.useState(false);
|
||||||
const [appFramework, setAppFramework] = React.useState({});
|
const [appFramework, setAppFramework] = React.useState({});
|
||||||
@@ -203,7 +203,7 @@ const Priorities = (props) => {
|
|||||||
<Paper
|
<Paper
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.platformColor,
|
backgroundColor: theme.palette.platformColor,
|
||||||
width: notificationWidth,
|
width: clickedFromOrgTab ? null :notificationWidth,
|
||||||
padding: 30,
|
padding: 30,
|
||||||
borderBottom: "1px solid rgba(255,255,255,0.4)",
|
borderBottom: "1px solid rgba(255,255,255,0.4)",
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
@@ -312,15 +312,15 @@ const Priorities = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{maxWidth: 1000, }}>
|
<div style={{width: clickedFromOrgTab ? 1030:1000, padding: clickedFromOrgTab ? 27:null, height: clickedFromOrgTab ? "auto":null, backgroundColor: clickedFromOrgTab ? '#212121':null, borderRadius: clickedFromOrgTab ? '16px':null, }}>
|
||||||
<h2 style={{ display: "inline" }}>Suggestions</h2>
|
<h2 style={{ display: clickedFromOrgTab ?null:"inline", marginBottom: clickedFromOrgTab ? 8:null, marginTop: clickedFromOrgTab ?0:null, color: clickedFromOrgTab ?"#ffffff":null }}>Suggestions</h2>
|
||||||
<span style={{ marginLeft: 25 }}>
|
<span style={{ color: clickedFromOrgTab ?"#9E9E9E":null,marginLeft: clickedFromOrgTab ?null:25 }}>
|
||||||
Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company. These range from simple configurations in Shuffle to Usecases you may have missed.
|
Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company. <br/>These range from simple configurations in Shuffle to Usecases you may have missed.
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href="/docs/organizations#priorities"
|
href="/docs/organizations#priorities"
|
||||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
style={{ textDecoration: clickedFromOrgTab ?null:"none", color: clickedFromOrgTab ?"#FF8444":"#f85a3e" }}
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
@@ -348,6 +348,7 @@ const Priorities = (props) => {
|
|||||||
globalUrl={globalUrl}
|
globalUrl={globalUrl}
|
||||||
priority={priority}
|
priority={priority}
|
||||||
checkLogin={checkLogin}
|
checkLogin={checkLogin}
|
||||||
|
clickedFromOrgTab={true}
|
||||||
setAdminTab={setAdminTab}
|
setAdminTab={setAdminTab}
|
||||||
setCurTab={setCurTab}
|
setCurTab={setCurTab}
|
||||||
appFramework={appFramework}
|
appFramework={appFramework}
|
||||||
@@ -355,15 +356,15 @@ const Priorities = (props) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
<Divider style={{marginTop: 50, marginBottom: 50, }} />
|
{clickedFromOrgTab?null:<Divider style={{marginTop: 50, marginBottom: 50, }} />}
|
||||||
<h2 style={{ display: "inline" }}>Notifications</h2>
|
<h2 style={{ display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, marginTop: clickedFromOrgTab?40:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications</h2>
|
||||||
<span style={{ marginLeft: 25 }}>
|
<span style={{ marginLeft: clickedFromOrgTab?null:25, color: clickedFromOrgTab?"#9E9E9E":null, }}>
|
||||||
Notifications help you find potential problems with your workflows and apps.
|
Notifications help you find potential problems with your workflows and apps.
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href="/docs/organizations#notifications"
|
href="/docs/organizations#notifications"
|
||||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
style={{ textDecoration: clickedFromOrgTab?null:"none", color: clickedFromOrgTab?"#FF8444":"#f85a3e" }}
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
//import { useAlert
|
//import { useAlert
|
||||||
|
|
||||||
const Priority = (props) => {
|
const Priority = (props) => {
|
||||||
const { globalUrl, userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
|
const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
@@ -114,7 +114,7 @@ const Priority = (props) => {
|
|||||||
const srcSize = realignedSrc ? 35 : 30
|
const srcSize = realignedSrc ? 35 : 30
|
||||||
const dstSize = realignedDst ? 35 : 30
|
const dstSize = realignedDst ? 35 : 30
|
||||||
return (
|
return (
|
||||||
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
|
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : clickedFromOrgTab ?null:"1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: clickedFromOrgTab ? 24:15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: clickedFromOrgTab ? "#1A1A1A": theme.palette.surfaceColor, display: "flex", }}>
|
||||||
<div style={{flex: 2, overflow: "hidden",}}>
|
<div style={{flex: 2, overflow: "hidden",}}>
|
||||||
<span style={{display: "flex", }}>
|
<span style={{display: "flex", }}>
|
||||||
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
|
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
|
||||||
@@ -147,7 +147,7 @@ const Priority = (props) => {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div style={{flex: 1, display: "flex", marginLeft: 30, }}>
|
<div style={{flex: 1, display: "flex", marginLeft: 30, }}>
|
||||||
<Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "rgba(255,255,255,0.8)", }} variant="contained" color="secondary" onClick={() => {
|
<Button style={{height: 50, borderRadius: 25, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" :clickedFromOrgTab ?"#FF8444": "black", backgroundColor: priority.active === false ? theme.palette.inputColor :clickedFromOrgTab?"rgba(255, 132, 68, 0.2)":"rgba(255,255,255,0.8)", }} variant="contained" color="secondary" onClick={() => {
|
||||||
|
|
||||||
if (isCloud) {
|
if (isCloud) {
|
||||||
ReactGA.event({
|
ReactGA.event({
|
||||||
@@ -173,7 +173,7 @@ const Priority = (props) => {
|
|||||||
Explore
|
Explore
|
||||||
</Button>
|
</Button>
|
||||||
{priority.active === true ?
|
{priority.active === true ?
|
||||||
<Button style={{borderRadius: 25, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => {
|
<Button style={{borderRadius: 25, fontSize:16, boxShadow: clickedFromOrgTab ? "none":null,textTransform: clickedFromOrgTab ? 'capitalize':null, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => {
|
||||||
// dismiss -> get envs
|
// dismiss -> get envs
|
||||||
changeRecommendation(priority, "dismiss")
|
changeRecommendation(priority, "dismiss")
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
+595
-101
@@ -201,7 +201,9 @@ const Admin = (props) => {
|
|||||||
const [showApiKey, setShowApiKey] = useState(false);
|
const [showApiKey, setShowApiKey] = useState(false);
|
||||||
const [billingInfo, setBillingInfo] = React.useState({});
|
const [billingInfo, setBillingInfo] = React.useState({});
|
||||||
const [selectedStatus, setSelectedStatus] = React.useState([]);
|
const [selectedStatus, setSelectedStatus] = React.useState([]);
|
||||||
|
const [webHooks, setWebHooks] = React.useState([]);
|
||||||
|
const [allSchedules, setAllSchedules] = React.useState([]);
|
||||||
|
const [pipelines, setPipelines] = React.useState([]);
|
||||||
const [, forceUpdate] = React.useState();
|
const [, forceUpdate] = React.useState();
|
||||||
|
|
||||||
const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] =
|
const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] =
|
||||||
@@ -214,22 +216,32 @@ const Admin = (props) => {
|
|||||||
getUsers();
|
getUsers();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (adminTab === 3) {
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
window.scroll({
|
const params = Object.fromEntries(urlSearchParams.entries());
|
||||||
top: 450,
|
const foundTab = params["admin_tab"]
|
||||||
left: 0,
|
if (foundTab !== null && foundTab !== undefined) {
|
||||||
behavior: "smooth",
|
if (adminTab === 3) {
|
||||||
});
|
window.scroll({
|
||||||
}
|
top: 450,
|
||||||
|
left: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scroll({
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
top: 450,
|
const params = Object.fromEntries(urlSearchParams.entries());
|
||||||
left: 0,
|
const foundTab = params["admin_tab"]
|
||||||
behavior: "smooth",
|
if (foundTab !== null && foundTab !== undefined) {
|
||||||
});
|
window.scroll({
|
||||||
|
top: 450,
|
||||||
|
left: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [adminTab]);
|
}, [adminTab]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -247,7 +259,11 @@ const Admin = (props) => {
|
|||||||
) {
|
) {
|
||||||
handleGetSubOrgs(userdata.active_org.id);
|
handleGetSubOrgs(userdata.active_org.id);
|
||||||
} else console.log("error in user data");
|
} else console.log("error in user data");
|
||||||
}, [userdata]);
|
}, [userdata]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleGetAllTriggers()
|
||||||
|
}, []);
|
||||||
|
|
||||||
const isCloud =
|
const isCloud =
|
||||||
window.location.host === "localhost:3002" ||
|
window.location.host === "localhost:3002" ||
|
||||||
@@ -731,6 +747,32 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGetAllTriggers = () => {
|
||||||
|
fetch(globalUrl + "/api/v1/triggers", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for getting all triggers");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined
|
||||||
|
setAllSchedules(responseJson.schedules || []);
|
||||||
|
// setPipelines(responseJson.pipelines || []);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// toast(error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const deleteSchedule = (data) => {
|
const deleteSchedule = (data) => {
|
||||||
// FIXME - add some check here ROFL
|
// FIXME - add some check here ROFL
|
||||||
console.log("INPUT: ", data);
|
console.log("INPUT: ", data);
|
||||||
@@ -756,11 +798,11 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
toast("Failed stopping schedule");
|
toast("Failed stopping schedule");
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
toast("Successfully stopped schedule!");
|
||||||
getSchedules();
|
|
||||||
}, 1500);
|
|
||||||
//toast("Successfully stopped schedule!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(handleGetAllTriggers, 1000);
|
||||||
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -768,6 +810,189 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startSchedule = (trigger) => {
|
||||||
|
if (trigger.name.length <= 0) {
|
||||||
|
toast("Error: name can't be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("Creating schedule");
|
||||||
|
const data = {
|
||||||
|
name: trigger.name,
|
||||||
|
frequency: trigger.frequency,
|
||||||
|
execution_argument: trigger.argument,
|
||||||
|
environment: trigger.environment,
|
||||||
|
id: trigger.id,
|
||||||
|
start: trigger.start_node,
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/workflows/${trigger.workflow_id}/schedule`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for stream results :O!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (!responseJson.success) {
|
||||||
|
toast("Failed to set schedule: " + responseJson.reason);
|
||||||
|
} else {
|
||||||
|
toast("Successfully created schedule");
|
||||||
|
}
|
||||||
|
setTimeout(handleGetAllTriggers, 1000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
//toast(error.toString());
|
||||||
|
console.log("Get schedule error: ", error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteWebhook = (trigger) => {
|
||||||
|
if (trigger === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for stream results :O!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success) {
|
||||||
|
toast("Successfully stopped webhook");
|
||||||
|
} else {
|
||||||
|
if (responseJson.reason !== undefined) {
|
||||||
|
toast("Failed stopping webhook: " + responseJson.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(handleGetAllTriggers, 1000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast(
|
||||||
|
"Delete webhook error. Contact support or check logs if this persists.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const startWebHook = (trigger) => {
|
||||||
|
const hookname = trigger.info.name;
|
||||||
|
if (hookname.length === 0) {
|
||||||
|
toast("Missing name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trigger.id.length !== 36) {
|
||||||
|
toast("Missing id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("Starting webhook");
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: hookname,
|
||||||
|
type: "webhook",
|
||||||
|
id: trigger.id,
|
||||||
|
workflow: trigger.workflows[0],
|
||||||
|
start: trigger.start,
|
||||||
|
environment: trigger.environment,
|
||||||
|
auth: trigger.auth,
|
||||||
|
custom_response: trigger.custom_response,
|
||||||
|
version: trigger.version,
|
||||||
|
version_timeout: 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Trigger data: ", data);
|
||||||
|
|
||||||
|
fetch(globalUrl + "/api/v1/hooks/new", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success) {
|
||||||
|
// Set the status
|
||||||
|
toast("Successfully started webhook");
|
||||||
|
} else {
|
||||||
|
toast("Failed starting webhook: " + responseJson.reason);
|
||||||
|
}
|
||||||
|
setTimeout(handleGetAllTriggers, 1000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
//console.log(error.toString());
|
||||||
|
console.log("New webhook error: ", error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePipelineState = (pipeline, state) => {
|
||||||
|
if (state.trim() === "") {
|
||||||
|
toast("state is not defined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: pipeline.name,
|
||||||
|
type: state,
|
||||||
|
environment: pipeline.environment,
|
||||||
|
workflow_id: pipeline.workflow_id,
|
||||||
|
trigger_id: pipeline.trigger_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state === "start") toast("starting the pipeline");
|
||||||
|
else toast("stopping the pipeline");
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/triggers/pipeline`;
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for stream results :O!");
|
||||||
|
toast("Failed to update the pipeline state");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (!responseJson.success) {
|
||||||
|
toast("Failed to update the pipeline: " + responseJson.reason);
|
||||||
|
} else {
|
||||||
|
if (state === "start") toast("Successfully created pipeline");
|
||||||
|
else toast("Sucessfully stopped the pipeline");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
//toast(error.toString());
|
||||||
|
console.log("Get schedule error: ", error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
userdata.support === true &&
|
userdata.support === true &&
|
||||||
selectedOrganization.id !== "" &&
|
selectedOrganization.id !== "" &&
|
||||||
@@ -3127,7 +3352,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
color="textSecondary"
|
color="textSecondary"
|
||||||
style={{ marginLeft: 0 }}
|
style={{ marginLeft: 0 }}
|
||||||
>
|
>
|
||||||
On this page organization admins can configure organisations, and
|
On this page organization admins can configure organizations, and
|
||||||
sub-orgs (MSSP).{" "}
|
sub-orgs (MSSP).{" "}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -4384,7 +4609,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const schedulesView =
|
const schedulesView =
|
||||||
curTab === 5 ? (
|
curTab === 5 ? (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||||
@@ -4408,88 +4633,357 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<List>
|
{allSchedules === undefined ||
|
||||||
<ListItem>
|
allSchedules === null ||
|
||||||
<ListItemText
|
allSchedules.length === 0 ? (
|
||||||
primary="Interval"
|
<div
|
||||||
style={{ maxWidth: 200, minWidth: 200 }}
|
style={{
|
||||||
/>
|
textAlign: "center",
|
||||||
<ListItemText
|
padding: "20px",
|
||||||
primary="Environment"
|
color: "#666",
|
||||||
style={{ maxWidth: 150, minWidth: 150 }}
|
borderRadius: "5px",
|
||||||
/>
|
}}
|
||||||
<ListItemText
|
>
|
||||||
primary="Workflow"
|
No schedules found.
|
||||||
style={{ maxWidth: 315, minWidth: 315 }}
|
</div>
|
||||||
/>
|
) : (
|
||||||
<ListItemText
|
<List>
|
||||||
primary="Argument"
|
<ListItem>
|
||||||
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
|
<ListItemText
|
||||||
/>
|
primary="Interval"
|
||||||
<ListItemText primary="Actions" />
|
style={{ maxWidth: 200, minWidth: 200 }}
|
||||||
<ListItemText primary="Delegation" />
|
/>
|
||||||
</ListItem>
|
<ListItemText
|
||||||
{schedules === undefined || schedules === null
|
primary="Environment"
|
||||||
? null
|
style={{ maxWidth: 150, minWidth: 150 }}
|
||||||
: schedules.map((schedule, index) => {
|
/>
|
||||||
var bgColor = "#27292d";
|
<ListItemText
|
||||||
if (index % 2 === 0) {
|
primary="Workflow"
|
||||||
bgColor = "#1f2023";
|
style={{ maxWidth: 315, minWidth: 315 }}
|
||||||
}
|
/>
|
||||||
|
<ListItemText
|
||||||
return (
|
primary="Argument"
|
||||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
|
||||||
<ListItemText
|
/>
|
||||||
style={{ maxWidth: 200, minWidth: 200 }}
|
<ListItemText primary="Actions" />
|
||||||
primary={
|
<ListItemText primary="Delegation" />
|
||||||
schedule.environment === "cloud" ||
|
</ListItem>
|
||||||
schedule.environment === "" ||
|
{allSchedules.map((schedule, index) => {
|
||||||
schedule.frequency.length > 0 ? (
|
var bgColor = "#27292d";
|
||||||
schedule.frequency
|
if (index % 2 === 0) {
|
||||||
) : (
|
bgColor = "#1f2023";
|
||||||
<span>{schedule.seconds} seconds</span>
|
}
|
||||||
)
|
|
||||||
}
|
return (
|
||||||
/>
|
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{ maxWidth: 150, minWidth: 150 }}
|
style={{ maxWidth: 200, minWidth: 200 }}
|
||||||
primary={schedule.environment}
|
primary={
|
||||||
/>
|
schedule.environment === "cloud" ||
|
||||||
<ListItemText
|
schedule.environment === "" ||
|
||||||
style={{ maxWidth: 315, minWidth: 315 }}
|
schedule.frequency.length > 0 ? (
|
||||||
primary={
|
schedule.frequency
|
||||||
<a
|
) : (
|
||||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
<span>{schedule.seconds} seconds</span>
|
||||||
href={`/workflows/${schedule.workflow_id}`}
|
)
|
||||||
target="_blank"
|
}
|
||||||
rel="noopener noreferrer"
|
/>
|
||||||
>
|
<ListItemText
|
||||||
{schedule.workflow_id}
|
style={{ maxWidth: 150, minWidth: 150 }}
|
||||||
</a>
|
primary={schedule.environment}
|
||||||
}
|
/>
|
||||||
/>
|
<ListItemText
|
||||||
<ListItemText
|
style={{ maxWidth: 315, minWidth: 315 }}
|
||||||
primary={schedule.argument.replaceAll('\\"', '"')}
|
primary={
|
||||||
style={{
|
<a
|
||||||
minWidth: 300,
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||||
maxWidth: 300,
|
href={`/workflows/${schedule.workflow_id}`}
|
||||||
overflow: "hidden",
|
target="_blank"
|
||||||
}}
|
rel="noopener noreferrer"
|
||||||
/>
|
|
||||||
<ListItemText>
|
|
||||||
<Button
|
|
||||||
style={{}}
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => deleteSchedule(schedule)}
|
|
||||||
>
|
>
|
||||||
Stop schedule
|
{schedule.workflow_id}
|
||||||
</Button>
|
</a>
|
||||||
</ListItemText>
|
}
|
||||||
</ListItem>
|
/>
|
||||||
);
|
<ListItemText
|
||||||
})}
|
primary={schedule.wrapped_argument.replaceAll('\\"', '"')}
|
||||||
</List>
|
style={{
|
||||||
|
minWidth: 300,
|
||||||
|
maxWidth: 300,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText>
|
||||||
|
<Button
|
||||||
|
style={{}}
|
||||||
|
variant={
|
||||||
|
schedule.status === "running" ? "contained" : "outlined"
|
||||||
|
}
|
||||||
|
disabled={schedule.status === "uninitialized"}
|
||||||
|
onClick={() => {
|
||||||
|
if (schedule.status === "running") {
|
||||||
|
deleteSchedule(schedule);
|
||||||
|
} else startSchedule(schedule);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{schedule.status === "running"
|
||||||
|
? "Stop Schedule"
|
||||||
|
: "Start Schedule"}
|
||||||
|
</Button>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||||
|
<h2 style={{ display: "inline" }}>WebHooks</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider
|
||||||
|
style={{
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{webHooks === undefined || webHooks === null || webHooks.length === 0 ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
padding: "20px",
|
||||||
|
color: "#666",
|
||||||
|
borderRadius: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No webhooks found.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary="Name"
|
||||||
|
style={{ maxWidth: 200, minWidth: 200 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Environment"
|
||||||
|
style={{ maxWidth: 150, minWidth: 150 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Workflow"
|
||||||
|
style={{ maxWidth: 315, minWidth: 315 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Url"
|
||||||
|
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
|
||||||
|
/>
|
||||||
|
<ListItemText primary="Actions" />
|
||||||
|
</ListItem>
|
||||||
|
{webHooks.map((webhook, index) => {
|
||||||
|
var bgColor = "#27292d";
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
bgColor = "#1f2023";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||||
|
<ListItemText
|
||||||
|
style={{ maxWidth: 200, minWidth: 200 }}
|
||||||
|
primary={webhook.info.name}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
style={{ maxWidth: 150, minWidth: 150 }}
|
||||||
|
primary={webhook.environment}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
style={{ maxWidth: 315, minWidth: 315 }}
|
||||||
|
primary={
|
||||||
|
<a
|
||||||
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||||
|
href={`/workflows/${webhook.workflows[0]}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{webhook.workflows[0]}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
style={{ marginLeft: 10, maxWidth: 100, minWidth: 100 }}
|
||||||
|
primary={
|
||||||
|
webhook.info.url === undefined || webhook.info.url === 0 ? (
|
||||||
|
""
|
||||||
|
) : (
|
||||||
|
<Tooltip
|
||||||
|
title={"Copy URL"}
|
||||||
|
style={{}}
|
||||||
|
aria-label={"Copy URL"}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{}}
|
||||||
|
onClick={() => {
|
||||||
|
const elementName = "copy_element_shuffle";
|
||||||
|
var copyText = document.getElementById(elementName);
|
||||||
|
if (copyText !== null && copyText !== undefined) {
|
||||||
|
const clipboard = navigator.clipboard;
|
||||||
|
if (clipboard === undefined) {
|
||||||
|
toast("Can only copy over HTTPS (port 3443)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(webhook.info.url);
|
||||||
|
copyText.select();
|
||||||
|
copyText.setSelectionRange(
|
||||||
|
0,
|
||||||
|
99999,
|
||||||
|
); /* For mobile devices */
|
||||||
|
|
||||||
|
/* Copy the text inside the text field */
|
||||||
|
document.execCommand("copy");
|
||||||
|
|
||||||
|
toast("URL copied to clipboard");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileCopyIcon
|
||||||
|
style={{ color: "rgba(255,255,255,0.8)" }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListItemText>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: "18%" }}
|
||||||
|
variant={
|
||||||
|
webhook.status === "running" ? "contained" : "outlined"
|
||||||
|
}
|
||||||
|
disabled={webhook.status === "uninitialized"}
|
||||||
|
onClick={() => {
|
||||||
|
if (webhook.status === "running") {
|
||||||
|
deleteWebhook(webhook);
|
||||||
|
} else startWebHook(webhook);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{webhook.status === "running"
|
||||||
|
? "Stop webhook"
|
||||||
|
: "Start Webhook"}
|
||||||
|
</Button>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||||
|
<h2 style={{ display: "inline" }}>Tenzir Pipelines</h2>
|
||||||
|
<span style={{ marginLeft: 25 }}>
|
||||||
|
Controls a pipeline to run things.{" "}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="/docs/triggers#pipelines"
|
||||||
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider
|
||||||
|
style={{
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{pipelines === undefined ||
|
||||||
|
pipelines === null ||
|
||||||
|
pipelines.length === 0 ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
padding: "20px",
|
||||||
|
color: "#666",
|
||||||
|
borderRadius: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No pipelines found.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary="Name"
|
||||||
|
style={{ maxWidth: 200, minWidth: 200 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Environment"
|
||||||
|
style={{ maxWidth: 150, minWidth: 150 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Workflow"
|
||||||
|
style={{ maxWidth: 315, minWidth: 315 }}
|
||||||
|
/>
|
||||||
|
<ListItemText primary="Actions" />
|
||||||
|
</ListItem>
|
||||||
|
{pipelines.map((pipeline, index) => {
|
||||||
|
var bgColor = "#27292d";
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
bgColor = "#1f2023";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||||
|
<ListItemText
|
||||||
|
style={{ maxWidth: 200, minWidth: 200 }}
|
||||||
|
primary={pipeline.name}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
style={{ maxWidth: 150, minWidth: 150 }}
|
||||||
|
primary={pipeline.environment}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
style={{ maxWidth: 315, minWidth: 315 }}
|
||||||
|
primary={
|
||||||
|
<a
|
||||||
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||||
|
href={`/workflows/${pipeline.workflow_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{pipeline.workflow_id}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ListItemText>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: "18%" }}
|
||||||
|
variant={
|
||||||
|
pipeline.status === "running" ? "contained" : "outlined"
|
||||||
|
}
|
||||||
|
disabled={pipeline.status === "uninitialized"}
|
||||||
|
onClick={() => {
|
||||||
|
if (pipeline.status === "running") {
|
||||||
|
changePipelineState(pipeline, "stop");
|
||||||
|
} else changePipelineState(pipeline, "start");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pipeline.status === "running"
|
||||||
|
? "Stop pipeline"
|
||||||
|
: "Start pipeline"}
|
||||||
|
</Button>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}*/}
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
|||||||
@@ -120,9 +120,12 @@ import {
|
|||||||
Add as AddIcon,
|
Add as AddIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
import * as cytoscape from "cytoscape";
|
//import * as cytoscape from "cytoscape";
|
||||||
import * as edgehandles from "cytoscape-edgehandles";
|
import cytoscape from "cytoscape";
|
||||||
|
|
||||||
|
import edgehandles from "cytoscape-edgehandles";
|
||||||
import CytoscapeComponent from "react-cytoscapejs";
|
import CytoscapeComponent from "react-cytoscapejs";
|
||||||
|
|
||||||
import Draggable from "react-draggable";
|
import Draggable from "react-draggable";
|
||||||
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
|
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
|
||||||
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
||||||
@@ -137,6 +140,8 @@ import ExtraApps from "../components/ExtraApps.jsx"
|
|||||||
import EditWorkflow from "../components/EditWorkflow.jsx"
|
import EditWorkflow from "../components/EditWorkflow.jsx"
|
||||||
// import AppStats from "../components/AppStats.jsx";
|
// import AppStats from "../components/AppStats.jsx";
|
||||||
|
|
||||||
|
cytoscape.use(edgehandles);
|
||||||
|
|
||||||
export const triggers = [
|
export const triggers = [
|
||||||
{
|
{
|
||||||
name: "Webhook",
|
name: "Webhook",
|
||||||
@@ -236,15 +241,7 @@ export const triggers = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// http://apps.cytoscape.org/apps/yfileslayoutalgorithms
|
|
||||||
cytoscape.use(edgehandles);
|
|
||||||
//cytoscape.use(clipboard);
|
|
||||||
//cytoscape.use(undoRedo);
|
|
||||||
//cytoscape.use(cxtmenu);
|
|
||||||
|
|
||||||
// Adds specific text to items
|
// Adds specific text to items
|
||||||
//import popper from 'cytoscape-popper';
|
|
||||||
//cytoscape.use(popper);
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react
|
// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react
|
||||||
function useWindowSize() {
|
function useWindowSize() {
|
||||||
@@ -592,7 +589,27 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
]
|
]
|
||||||
}*/
|
}*/
|
||||||
]
|
]
|
||||||
}]
|
},{
|
||||||
|
"name": "Communication",
|
||||||
|
"description": "Available actions for communication",
|
||||||
|
"label": "Communication",
|
||||||
|
"parameters": [{
|
||||||
|
"name": "action",
|
||||||
|
"value": "list_messages",
|
||||||
|
"options": [
|
||||||
|
"list_messages",
|
||||||
|
"send_message",
|
||||||
|
],
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"value": "",
|
||||||
|
"required": false,
|
||||||
|
"multiline": true,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
]
|
||||||
}]
|
}]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -685,7 +702,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
props.userdata.active_org !== undefined
|
props.userdata.active_org !== undefined
|
||||||
? props.userdata.active_org.cloud_sync === true
|
? props.userdata.active_org.cloud_sync === true
|
||||||
: false;
|
: false;
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io";
|
||||||
|
|
||||||
const appBarSize = isCloud ? 75 : 72;
|
const appBarSize = isCloud ? 75 : 72;
|
||||||
const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"];
|
const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"];
|
||||||
@@ -3996,42 +4013,50 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
|
|
||||||
workflow.actions.push(newNodeData);
|
workflow.actions.push(newNodeData);
|
||||||
|
|
||||||
const sourcebranches = workflow.branches.filter(
|
const sourcebranches = workflow.branches.filter((foundbranch) => foundbranch.source_id === parentNode.data("id"))
|
||||||
(foundbranch) => foundbranch.source_id === parentNode.data("id")
|
|
||||||
);
|
|
||||||
const destinationbranches = workflow.branches.filter(
|
const destinationbranches = workflow.branches.filter((foundbranch) => foundbranch.destination_id === parentNode.data("id"))
|
||||||
(foundbranch) =>
|
|
||||||
foundbranch.destination_id === parentNode.data("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var sourceBranchesKey in sourcebranches) {
|
for (var sourceBranchesKey in sourcebranches) {
|
||||||
var newbranch = JSON.parse(JSON.stringify(sourcebranches[sourceBranchesKey]));
|
var newbranch = JSON.parse(JSON.stringify(sourcebranches[sourceBranchesKey]));
|
||||||
newbranch.id = uuidv4();
|
|
||||||
newbranch.source_id = newNodeData.id;
|
|
||||||
|
|
||||||
newbranch._id = newbranch.id;
|
newbranch.id = uuidv4()
|
||||||
newbranch.source = newbranch.source_id;
|
newbranch.source_id = newNodeData.id
|
||||||
newbranch.target = newbranch.destination_id;
|
|
||||||
cy.add({
|
newbranch._id = newbranch.id
|
||||||
group: "edges",
|
newbranch.source = newbranch.source_id
|
||||||
data: newbranch,
|
newbranch.target = newbranch.destination_id
|
||||||
});
|
cy.add({
|
||||||
|
group: "edges",
|
||||||
|
data: newbranch,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var destinationBranchesKey in destinationbranches) {
|
for (var destinationBranchesKey in destinationbranches) {
|
||||||
var newbranch = JSON.parse(
|
var newbranch = JSON.parse(JSON.stringify(destinationbranches[destinationBranchesKey]))
|
||||||
JSON.stringify(destinationbranches[destinationBranchesKey])
|
|
||||||
);
|
|
||||||
newbranch.id = uuidv4();
|
|
||||||
newbranch.destination_id = newNodeData.id;
|
|
||||||
|
|
||||||
newbranch._id = newbranch.id;
|
const sourcenode = cy.getElementById(newbranch.source_id)
|
||||||
newbranch.source = newbranch.source_id;
|
if (sourcenode !== null && sourcenode !== undefined) {
|
||||||
newbranch.target = newbranch.destination_id;
|
const sourcedata = sourcenode.data()
|
||||||
cy.add({
|
|
||||||
group: "edges",
|
if (sourcedata.trigger_type !== "SUBFLOW" && sourcedata.trigger_type !== "USERINPUT") {
|
||||||
data: newbranch,
|
continue
|
||||||
});
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
newbranch.id = uuidv4()
|
||||||
|
newbranch.destination_id = newNodeData.id
|
||||||
|
|
||||||
|
newbranch._id = newbranch.id
|
||||||
|
newbranch.source = newbranch.source_id
|
||||||
|
newbranch.target = newbranch.destination_id
|
||||||
|
cy.add({
|
||||||
|
group: "edges",
|
||||||
|
data: newbranch,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//event.target.unselect();
|
//event.target.unselect();
|
||||||
@@ -4783,7 +4808,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
event.target.remove()
|
event.target.remove()
|
||||||
|
|
||||||
//console.log("Found branch already!")
|
//console.log("Found branch already!")
|
||||||
toast("Triggers can have exactly one target node")
|
toast.error("Triggers can have exactly one target node")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -5194,7 +5219,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
data: newdata,
|
data: newdata,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast("You must STOP the trigger before deleting its branches")
|
toast.error("You must STOP the trigger before deleting its branches")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed re-adding edge: ", e)
|
console.log("Failed re-adding edge: ", e)
|
||||||
}
|
}
|
||||||
@@ -13130,7 +13155,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
if (trigger.id === undefined) {
|
if (trigger.id === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
|
fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -13143,32 +13168,34 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
console.log("Status not 200 for stream results :O!");
|
console.log("Status not 200 for stream results :O!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
|
if (!responseJson.success) {
|
||||||
|
if (responseJson.reason !== undefined) {
|
||||||
|
toast("Failed to stop webhook: " + responseJson.reason);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast("Successfully stopped webhook");
|
||||||
|
}
|
||||||
if (workflow.triggers[triggerindex] !== undefined) {
|
if (workflow.triggers[triggerindex] !== undefined) {
|
||||||
workflow.triggers[triggerindex].status = "stopped";
|
workflow.triggers[triggerindex].status = "stopped";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseJson.success) {
|
|
||||||
// Set the status
|
|
||||||
saveWorkflow(workflow);
|
|
||||||
} else {
|
|
||||||
if (responseJson.reason !== undefined) {
|
|
||||||
toast("Failed stopping webhook: " + responseJson.reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trigger.status = "stopped";
|
trigger.status = "stopped";
|
||||||
setWorkflow(workflow);
|
|
||||||
setSelectedTrigger(trigger);
|
setSelectedTrigger(trigger);
|
||||||
|
setWorkflow(workflow);
|
||||||
|
saveWorkflow(workflow);
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
//toast(error.toString());
|
//toast(error.toString());
|
||||||
toast("Delete webhook error. Contact support or check logs if this persists.")
|
toast(
|
||||||
|
"Delete webhook error. Contact support or check logs if this persists.",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// POST to /api/v1/workflows
|
// POST to /api/v1/workflows
|
||||||
const createWorkflow = (workflow, trigger_index) => {
|
const createWorkflow = (workflow, trigger_index) => {
|
||||||
@@ -17221,6 +17248,10 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=environments to create an environment to connect to"
|
return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=environments to create an environment to connect to"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stringjson.toLowerCase().includes("invalid header")) {
|
||||||
|
return "A header or authentication token in the app is invalid. Check the app's configuration"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1037,55 +1037,41 @@ const AppCreator = (defaultprops) => {
|
|||||||
"schema"
|
"schema"
|
||||||
] !== null
|
] !== null
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
methodvalue["requestBody"]["content"]["application/xml"][
|
methodvalue["requestBody"]["content"]["application/xml"][
|
||||||
"schema"
|
"schema"
|
||||||
]["properties"] !== undefined
|
]["properties"] !== undefined
|
||||||
) {
|
) {
|
||||||
var tmpobject = {};
|
var tmpobject = {};
|
||||||
for (let [prop, propvalue] of Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["properties"])) {
|
for (let [prop, propvalue] of Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["properties"])) {
|
||||||
|
|
||||||
tmpobject[prop] = `\$\{${prop}\}`;
|
tmpobject[prop] = `\$\{${prop}\}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let [subkey,subkeyval] in Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["required"])) {
|
for (let [subkey,subkeyval] in Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["required"])) {
|
||||||
const tmpitem =
|
const tmpitem =
|
||||||
methodvalue["requestBody"]["content"][
|
methodvalue["requestBody"]["content"][
|
||||||
"application/xml"
|
"application/xml"
|
||||||
]["schema"]["required"][subkey];
|
]["schema"]["required"][subkey];
|
||||||
tmpobject[tmpitem] = `\$\{${tmpitem}\}`;
|
tmpobject[tmpitem] = `\$\{${tmpitem}\}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log("OBJ XML: ", tmpobject)
|
//console.log("OBJ XML: ", tmpobject)
|
||||||
//newaction["body"] = XML.stringify(tmpobject, null, 2)
|
//newaction["body"] = XML.stringify(tmpobject, null, 2)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("RequestBody xml error: ", e, path)
|
console.log("RequestBody xml error: ", e, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (methodvalue["requestBody"]["content"]["example"] !== undefined) {
|
||||||
methodvalue["requestBody"]["content"]["example"] !== undefined
|
if (methodvalue["requestBody"]["content"]["example"]["example"] !== undefined) {
|
||||||
) {
|
newaction["body"] = methodvalue["requestBody"]["content"]["example"]["example"]
|
||||||
if (
|
|
||||||
methodvalue["requestBody"]["content"]["example"][
|
|
||||||
"example"
|
|
||||||
] !== undefined
|
|
||||||
) {
|
|
||||||
newaction["body"] =
|
|
||||||
methodvalue["requestBody"]["content"]["example"][
|
|
||||||
"example"
|
|
||||||
];
|
|
||||||
//JSON.stringify(tmpobject, null, 2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) {
|
||||||
methodvalue["requestBody"]["content"][
|
|
||||||
"multipart/form-data"
|
|
||||||
] !== undefined
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
methodvalue["requestBody"]["content"][
|
methodvalue["requestBody"]["content"][
|
||||||
"multipart/form-data"
|
"multipart/form-data"
|
||||||
@@ -1553,8 +1539,10 @@ const AppCreator = (defaultprops) => {
|
|||||||
} else if (parameter.in === "body") {
|
} else if (parameter.in === "body") {
|
||||||
// FIXME: Add tracking for components
|
// FIXME: Add tracking for components
|
||||||
// E.G: https://raw.githubusercontent.com/owentl/Shuffle/master/gosecure.yaml
|
// E.G: https://raw.githubusercontent.com/owentl/Shuffle/master/gosecure.yaml
|
||||||
if (parameter.example !== undefined) {
|
if (parameter.example !== undefined && parameter.example !== null) {
|
||||||
newaction.body = parameter.example;
|
if (newaction.body === undefined || newaction.body === null || newaction.body.length < 5) {
|
||||||
|
newaction.body = parameter.example
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (parameter.in === "header") {
|
} else if (parameter.in === "header") {
|
||||||
newaction.headers += `${parameter.name}=${parameter.example}\n`;
|
newaction.headers += `${parameter.name}=${parameter.example}\n`;
|
||||||
@@ -1566,6 +1554,13 @@ const AppCreator = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if body is valid JSON.
|
||||||
|
if (newaction.body !== undefined && newaction.body !== null && newaction.body.length > 0) {
|
||||||
|
// Trim starting / ending newlines, spaces and tabs
|
||||||
|
newaction.body = newaction.body.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (newaction.name === "" || newaction.name === undefined) {
|
if (newaction.name === "" || newaction.name === undefined) {
|
||||||
// Find a unique part of the string
|
// Find a unique part of the string
|
||||||
// FIXME: Looks for length between /, find the one where they differ
|
// FIXME: Looks for length between /, find the one where they differ
|
||||||
@@ -1920,11 +1915,11 @@ const AppCreator = (defaultprops) => {
|
|||||||
|
|
||||||
setProjectCategories(all_categories);
|
setProjectCategories(all_categories);
|
||||||
|
|
||||||
// Rearrange them by which has action_label
|
// Rearrange them by which has action_label
|
||||||
const firstActions = newActions.filter(data => data.action_label !== undefined && data.action_label !== null && data.action_label !== "No Label")
|
const firstActions = newActions.filter(data => data.action_label !== undefined && data.action_label !== null && data.action_label !== "No Label")
|
||||||
console.log("First actions: ", firstActions)
|
console.log("First actions: ", firstActions)
|
||||||
const secondActions = newActions.filter(data => data.action_label === undefined || data.action_label === null || data.action_label === "No Label")
|
const secondActions = newActions.filter(data => data.action_label === undefined || data.action_label === null || data.action_label === "No Label")
|
||||||
newActions = firstActions.concat(secondActions)
|
newActions = firstActions.concat(secondActions)
|
||||||
setActions(newActions);
|
setActions(newActions);
|
||||||
//data.paths[item.url][item.method.toLowerCase()]["x-label"] = item.action_label
|
//data.paths[item.url][item.method.toLowerCase()]["x-label"] = item.action_label
|
||||||
|
|
||||||
@@ -3078,7 +3073,10 @@ const AppCreator = (defaultprops) => {
|
|||||||
Refresh-token URL for Oauth2 (Optional)
|
Refresh-token URL for Oauth2 (Optional)
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
|
style={{
|
||||||
|
margin: 0, flex: "1", backgroundColor: inputColor,
|
||||||
|
border: !refreshUrl.startsWith("http") || refreshUrl.includes("//shuffler.") ? "2px solid red" : "inherit",
|
||||||
|
}}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
placeholder="The URL to retrieve refresh-tokens at"
|
placeholder="The URL to retrieve refresh-tokens at"
|
||||||
type="name"
|
type="name"
|
||||||
@@ -3086,8 +3084,9 @@ const AppCreator = (defaultprops) => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={refreshUrl}
|
value={refreshUrl}
|
||||||
|
helperText={!refreshUrl.startsWith("http") || refreshUrl.includes("//shuffler.")? "Must start with http(s):// and can not contain shuffler.io" : ""}
|
||||||
onChange={(e) => setRefreshUrl(e.target.value)}
|
onChange={(e) => setRefreshUrl(e.target.value)}
|
||||||
onBlur={(event) => {
|
onBlur={(event) => {
|
||||||
var tmpstring = event.target.value.trim();
|
var tmpstring = event.target.value.trim();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -3153,7 +3152,7 @@ const AppCreator = (defaultprops) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<div style={{display: "flex", marginTop: 10, }}>
|
<div style={{display: "flex", marginTop: 10, }}>
|
||||||
<div style={{flex: 4,}}>
|
<div style={{flex: 4,}}>
|
||||||
Key
|
Key
|
||||||
<TextField
|
<TextField
|
||||||
required
|
required
|
||||||
style={{ marginTop: 0, backgroundColor: inputColor }}
|
style={{ marginTop: 0, backgroundColor: inputColor }}
|
||||||
@@ -3166,12 +3165,17 @@ const AppCreator = (defaultprops) => {
|
|||||||
value={parameterName}
|
value={parameterName}
|
||||||
helperText={
|
helperText={
|
||||||
<span style={{ color: "white", marginBottom: "2px" }}>
|
<span style={{ color: "white", marginBottom: "2px" }}>
|
||||||
Can't be empty or contain any of the following: !#$%&'^"+-._~|]+$
|
Can't be empty or contain any of the following: !#$%&'^"+-._~|]+$:=
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setParameterName(e.target.value);
|
setParameterName(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
onBlur={(event) => {
|
||||||
|
var tmpstring = event.target.value.trim()
|
||||||
|
|
||||||
|
// Check if tmpstring has any of the illegal characters in it
|
||||||
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
classes: {
|
classes: {
|
||||||
notchedOutline: classes.notchedOutline,
|
notchedOutline: classes.notchedOutline,
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ const EditWorkflow = (props) => {
|
|||||||
|
|
||||||
const cyDummy = cytoscape();
|
const cyDummy = cytoscape();
|
||||||
if (!cyDummy.edgehandles) {
|
if (!cyDummy.edgehandles) {
|
||||||
cytoscape.use(edgehandles);
|
//cytoscape.use(edgehandles);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+285
-175
@@ -1,206 +1,316 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
|
|
||||||
import theme from '../theme.jsx';
|
import theme from "../theme.jsx";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import AppGrid from "../components/AppGrid.jsx"
|
import AppGrid from "../components/AppGrid.jsx";
|
||||||
import WorkflowGrid from "../components/WorkflowGrid.jsx"
|
import WorkflowGrid from "../components/WorkflowGrid.jsx";
|
||||||
import CreatorGrid from "../components/CreatorGrid.jsx"
|
import CreatorGrid from "../components/CreatorGrid.jsx";
|
||||||
import DocsGrid from "../components/DocsGrid.jsx"
|
import DocsGrid from "../components/DocsGrid.jsx";
|
||||||
import DiscordChat from "../components/DiscordChat.jsx";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { Tabs, Tab, setRef } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { makeStyles } from '@mui/styles';
|
||||||
|
import DiscordChat from "../components/DiscordChat.jsx";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Tabs,
|
Apps as AppsIcon,
|
||||||
Tab,
|
Code as CodeIcon,
|
||||||
} from "@mui/material";
|
EmojiObjects as EmojiObjectsIcon,
|
||||||
|
Chat as ChatIcon,
|
||||||
import {
|
BorderBottom,
|
||||||
Apps as AppsIcon,
|
|
||||||
Code as CodeIcon,
|
|
||||||
Chat as ChatIcon,
|
|
||||||
EmojiObjects as EmojiObjectsIcon,
|
|
||||||
Description as DescriptionIcon,
|
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
|
import PeopleAltOutlinedIcon from '@mui/icons-material/PeopleAltOutlined';
|
||||||
|
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||||
|
|
||||||
// Should be different if logged in :|
|
// Should be different if logged in :|
|
||||||
const Search = (props) => {
|
const Search = (props) => {
|
||||||
const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader } = props;
|
const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader } =
|
||||||
let navigate = useNavigate();
|
props;
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
const [curTab, setCurTab] = useState(0);
|
const [curTab, setCurTab] = useState(0);
|
||||||
const iconStyle = { marginRight: isHeader ? null : 10 };
|
const iconStyle = { marginRight: isHeader ? null : 10 };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
|
if (
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
serverside !== true &&
|
||||||
const params = Object.fromEntries(urlSearchParams.entries())
|
window.location.search !== undefined &&
|
||||||
const foundTab = params["tab"]
|
window.location.search !== null
|
||||||
if (foundTab !== null && foundTab !== undefined) {
|
) {
|
||||||
for (var key in Object.keys(views)) {
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
const value = views[key]
|
const params = Object.fromEntries(urlSearchParams.entries());
|
||||||
console.log(key, value)
|
const foundTab = params["tab"];
|
||||||
if (value === foundTab) {
|
if (foundTab !== null && foundTab !== undefined) {
|
||||||
setConfig("", key)
|
for (var key in Object.keys(views)) {
|
||||||
break
|
const value = views[key];
|
||||||
}
|
console.log(key, value);
|
||||||
}
|
if (value === foundTab) {
|
||||||
}
|
setConfig("", key);
|
||||||
}
|
break;
|
||||||
}, [])
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (serverside === true) {
|
//Stop unnecessariry re-rendering of the component to improve performace
|
||||||
return null
|
const MemoizedAppGrid = useMemo(() => <AppGrid
|
||||||
}
|
maxRows={4}
|
||||||
|
isHeader={true}
|
||||||
|
showSuggestion={true}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
isMobile={isMobile}
|
||||||
|
userdata={userdata}
|
||||||
|
/>, [curTab]);
|
||||||
|
|
||||||
const bodyDivStyle = {
|
const MemoizedWorkflowGrid = useMemo(() => <WorkflowGrid
|
||||||
margin: "auto",
|
maxRows={3}
|
||||||
maxWidth: 1024,
|
showSuggestion={true}
|
||||||
scrollX: "hidden",
|
globalUrl={globalUrl}
|
||||||
overflowX: "hidden",
|
isMobile={isMobile}
|
||||||
justifyContent: isHeader ? "center" : null,
|
userdata={userdata}
|
||||||
}
|
/>, [curTab]);
|
||||||
|
|
||||||
const boxStyle = {
|
const MemoizedDocsGrid = useMemo(() => <DocsGrid
|
||||||
color: "white",
|
maxRows={6}
|
||||||
flex: "1",
|
parsedXs={12}
|
||||||
marginLeft: isHeader ? null : 10,
|
showSuggestion={true}
|
||||||
marginRight: isHeader ? null : 10,
|
globalUrl={globalUrl}
|
||||||
paddingLeft: isHeader ? null : 30,
|
isMobile={isMobile}
|
||||||
paddingRight: isHeader ? null : 30,
|
userdata={userdata}
|
||||||
paddingBottom: isHeader ? null : 30,
|
/>, [curTab]);
|
||||||
paddingTop: hidemargins === true ? 0 : isHeader ? null : 30,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
overflowX: "hidden",
|
|
||||||
minHeight: 400,
|
|
||||||
}
|
|
||||||
|
|
||||||
const views = {
|
const MemoizedCreatorGrid = useMemo(() => <CreatorGrid
|
||||||
0: "apps",
|
parsedXs={4}
|
||||||
1: "workflows",
|
isHeader={true}
|
||||||
2: "docs",
|
showSuggestion={true}
|
||||||
3: "creators",
|
globalUrl={globalUrl}
|
||||||
4: "discord",
|
isMobile={isMobile}
|
||||||
}
|
userdata={userdata}
|
||||||
|
/>, [curTab]);
|
||||||
|
|
||||||
const setConfig = (event, inputValue) => {
|
const MemoizedDiscordChat = useMemo(() => <DiscordChat isMobile={isMobile} />)
|
||||||
const newValue = parseInt(inputValue)
|
|
||||||
|
|
||||||
setCurTab(newValue)
|
const useStyles = makeStyles({
|
||||||
if (newValue === 0) {
|
hideIndicator: {
|
||||||
document.title = "Shuffle - search - apps";
|
display: 'none',
|
||||||
} else if (newValue === 1) {
|
},
|
||||||
document.title = "Shuffle - search - workflows";
|
customTab: {
|
||||||
} else if (newValue === 2) {
|
justifyContent: 'center',
|
||||||
document.title = "Shuffle - search - documentation";
|
gap: '46px',
|
||||||
} else if (newValue === 3) {
|
}
|
||||||
document.title = "Shuffle - search - creators";
|
});
|
||||||
} else if (newValue === 4) {
|
const classes = useStyles();
|
||||||
document.title = "Shuffle - search - Discord Chat";
|
|
||||||
}else {
|
|
||||||
document.title = "Shuffle - search";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (serverside === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
const bodyDivStyle = {
|
||||||
const params = Object.fromEntries(urlSearchParams.entries())
|
margin: "auto",
|
||||||
const foundQuery = params["q"]
|
maxWidth: "100%",
|
||||||
var extraQ = ""
|
scrollX: "hidden",
|
||||||
if (foundQuery !== null && foundQuery !== undefined) {
|
overflowX: "hidden",
|
||||||
extraQ = "&q=" + foundQuery
|
justifyContent: isHeader ? "center" : null,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const boxStyle = {
|
||||||
|
color: "white",
|
||||||
|
flex: "1",
|
||||||
|
marginLeft: isHeader ? null : 10,
|
||||||
|
marginRight: isHeader ? null : 10,
|
||||||
|
paddingLeft: isHeader ? null : 30,
|
||||||
|
paddingRight: isHeader ? null : 30,
|
||||||
|
paddingBottom: isHeader ? null : 30,
|
||||||
|
paddingTop: hidemargins === true ? 0 : isHeader ? null : 30,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
overflowX: "hidden",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: 400,
|
||||||
|
};
|
||||||
|
|
||||||
if ((serverside === false || serverside === undefined) && window.location.pathname.includes("/search")) {
|
const views = {
|
||||||
navigate(`/search?tab=${views[newValue]}` + extraQ)
|
0: "apps",
|
||||||
}
|
1: "workflows",
|
||||||
}
|
2: "docs",
|
||||||
|
3: "creators",
|
||||||
|
};
|
||||||
|
const setConfig = (event, inputValue) => {
|
||||||
|
const newValue = parseInt(inputValue);
|
||||||
|
|
||||||
if (isLoaded === false) {
|
setCurTab(newValue);
|
||||||
return null
|
if (newValue === 0) {
|
||||||
}
|
document.title = "Shuffle - search - apps";
|
||||||
|
} else if (newValue === 1) {
|
||||||
|
document.title = "Shuffle - search - workflows";
|
||||||
|
} else if (newValue === 2) {
|
||||||
|
document.title = "Shuffle - search - documentation";
|
||||||
|
} else if (newValue === 3) {
|
||||||
|
document.title = "Shuffle - search - creators";
|
||||||
|
} else {
|
||||||
|
document.title = "Shuffle - search";
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
|
const params = Object.fromEntries(urlSearchParams.entries());
|
||||||
|
const foundQuery = params["q"];
|
||||||
|
var extraQ = "";
|
||||||
|
if (foundQuery !== null && foundQuery !== undefined) {
|
||||||
|
extraQ = "&q=" + foundQuery;
|
||||||
|
}
|
||||||
|
|
||||||
// Random names for type & autoComplete. Didn't research :^)
|
if (
|
||||||
const landingpageDataBrowser =
|
(serverside === false || serverside === undefined) &&
|
||||||
<div style={{ paddingBottom: hidemargins === true ? 0 : 100, color: "white", }}>
|
window.location.pathname.includes("/search")
|
||||||
<div style={boxStyle}>
|
) {
|
||||||
<Tabs
|
navigate(`/search?tab=${views[newValue]}` + extraQ);
|
||||||
style={{ width: isHeader ? 765 : 610, margin: isHeader ? null : "auto", marginTop: hidemargins === true ? 0 : isHeader ? null : 25, }}
|
}
|
||||||
value={curTab}
|
};
|
||||||
indicatorColor="primary"
|
|
||||||
textColor="secondary"
|
|
||||||
onChange={setConfig}
|
|
||||||
aria-label="disabled tabs example"
|
|
||||||
variant="scrollable"
|
|
||||||
scrollButtons="auto"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
label=<span>
|
|
||||||
<AppsIcon style={iconStyle} /> Apps
|
|
||||||
</span>
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
label=<span>
|
|
||||||
<CodeIcon style={iconStyle} /> Workflows
|
|
||||||
</span>
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
label=<span>
|
|
||||||
<DescriptionIcon style={iconStyle} /> Docs
|
|
||||||
</span>
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
label=<span>
|
|
||||||
<EmojiObjectsIcon style={iconStyle} /> Creators
|
|
||||||
</span>
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
label=<span>
|
|
||||||
<ChatIcon style={iconStyle} /> Discord Chat
|
|
||||||
</span>
|
|
||||||
/>
|
|
||||||
|
|
||||||
</Tabs>
|
if (isLoaded === false) {
|
||||||
{curTab === 0 ?
|
return null;
|
||||||
<AppGrid maxRows={3} isHeader={true} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
}
|
||||||
:
|
|
||||||
curTab === 1 ?
|
|
||||||
window.location.pathname === "/search" ?
|
|
||||||
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
|
||||||
:
|
|
||||||
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
|
||||||
:
|
|
||||||
curTab === 2 ?
|
|
||||||
<DocsGrid maxRows={6} parsedXs={12} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
|
||||||
:
|
|
||||||
curTab === 3 ?
|
|
||||||
<CreatorGrid parsedXs={4} isHeader={true} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
|
||||||
:
|
|
||||||
curTab === 4 ?
|
|
||||||
<DiscordChat isMobile={isMobile} />
|
|
||||||
:
|
|
||||||
|
|
||||||
null}
|
const StyledTab = styled(Tab)(({ theme }) => ({
|
||||||
</div>
|
width: 151,
|
||||||
</div>
|
height: 51,
|
||||||
//{/*alternativeView={true} />*/}
|
padding: "10px 20px",
|
||||||
|
borderRadius: 8,
|
||||||
|
fontWeight: 600,
|
||||||
|
textTransform: "none",
|
||||||
|
border: 'none',
|
||||||
|
"&.Mui-selected": {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: theme.palette.common.white,
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
color: theme.palette.common.white,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const loadedCheck = isLoaded ?
|
const tabSpanStyling = {
|
||||||
<div>
|
display: 'flex',
|
||||||
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
|
flexDirection: 'row',
|
||||||
</div>
|
alignItems: 'center'
|
||||||
:
|
}
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// #1f2023?
|
const tabTextStyling = {
|
||||||
return (
|
marginLeft: '5px',
|
||||||
<div style={{}}>
|
color: 'white'
|
||||||
{loadedCheck}
|
}
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
// Random names for type & autoComplete. Didn't research :^)
|
||||||
|
const landingpageDataBrowser = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingBottom: hidemargins === true ? 0 : 100,
|
||||||
|
color: "white",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={boxStyle}>
|
||||||
|
<Tabs
|
||||||
|
style={{
|
||||||
|
width: 741,
|
||||||
|
margin: isHeader ? null : "auto",
|
||||||
|
marginTop: hidemargins === true ? 0 : isHeader ? null : 25,
|
||||||
|
backgroundColor: "rgba(33, 33, 33, 1)",
|
||||||
|
borderRadius:8
|
||||||
|
}}
|
||||||
|
value={curTab}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="secondary"
|
||||||
|
onChange={setConfig}
|
||||||
|
aria-label="disabled tabs example"
|
||||||
|
variant="scrollable"
|
||||||
|
scrollButtons="auto"
|
||||||
|
classes={{indicator: classes.hideIndicator, root: classes.customTab}}
|
||||||
|
>
|
||||||
|
<StyledTab
|
||||||
|
style={{
|
||||||
|
backgroundColor: curTab === 0 ? theme.palette.primary.main : 'inherit',
|
||||||
|
color: curTab === 0? theme.palette.common.white : 'inherit',
|
||||||
|
}}
|
||||||
|
label={
|
||||||
|
<span style={tabSpanStyling}>
|
||||||
|
<AppsIcon style={iconStyle} />
|
||||||
|
<Typography variant="body1" style={tabTextStyling}>App</Typography>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledTab
|
||||||
|
style={{
|
||||||
|
backgroundColor: curTab ===1 ? theme.palette.primary.main : 'inherit',
|
||||||
|
color: curTab === 1? theme.palette.common.white : 'inherit',
|
||||||
|
}}
|
||||||
|
label={
|
||||||
|
<span style={tabSpanStyling}>
|
||||||
|
<CodeIcon style={iconStyle} />
|
||||||
|
<Typography variant="body1" style={tabTextStyling}>Workflow</Typography>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledTab
|
||||||
|
style={{
|
||||||
|
backgroundColor: curTab === 2 ? theme.palette.primary.main : 'inherit',
|
||||||
|
color: curTab === 2? theme.palette.common.white : 'inherit',
|
||||||
|
}}
|
||||||
|
label={
|
||||||
|
<span style={tabSpanStyling}>
|
||||||
|
<DescriptionOutlinedIcon style={iconStyle} />
|
||||||
|
<Typography variant="body1" style={tabTextStyling}>Docs</Typography>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledTab
|
||||||
|
style={{
|
||||||
|
backgroundColor: curTab === 3 ? theme.palette.primary.main : 'inherit',
|
||||||
|
color: curTab === 3 ? theme.palette.common.white : 'inherit'
|
||||||
|
}}
|
||||||
|
label={
|
||||||
|
<span style={tabSpanStyling}>
|
||||||
|
<PeopleAltOutlinedIcon style={iconStyle} />
|
||||||
|
<Typography variant="body1" style={tabTextStyling}>Creators</Typography>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledTab
|
||||||
|
style={{
|
||||||
|
backgroundColor: curTab === 4 ? theme.palette.primary.main : 'inherit',
|
||||||
|
color: curTab === 4 ? theme.palette.common.white : 'inherit'
|
||||||
|
}}
|
||||||
|
label={
|
||||||
|
<span style={tabSpanStyling}>
|
||||||
|
<ChatIcon style={iconStyle} />
|
||||||
|
<Typography variant="body1" style={{tabTextStyling, whiteSpace: "nowrap", color: "white"}}>Discord Chat</Typography>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
{curTab === 0 && MemoizedAppGrid}
|
||||||
|
{curTab === 1 && MemoizedWorkflowGrid}
|
||||||
|
{curTab === 2 && MemoizedDocsGrid}
|
||||||
|
{curTab === 3 && MemoizedCreatorGrid}
|
||||||
|
{curTab === 4 && MemoizedDiscordChat}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
//{/*alternativeView={true} />*/}
|
||||||
|
|
||||||
|
const loadedCheck = isLoaded ? (
|
||||||
|
<div>
|
||||||
|
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div></div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// #1f2023?
|
||||||
|
return <div style={{}}>{loadedCheck}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
export default Search;
|
export default Search;
|
||||||
|
|||||||
Reference in New Issue
Block a user