enabled user input subflows and fixed bugs
This commit is contained in:
@@ -2,7 +2,7 @@ module main
|
||||
|
||||
go 1.19
|
||||
|
||||
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
||||
replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
||||
|
||||
require (
|
||||
cloud.google.com/go/datastore v1.10.0
|
||||
|
||||
+4
-154
@@ -1340,156 +1340,6 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, baseSSOUrl)))
|
||||
}
|
||||
|
||||
func handleLogin(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := shuffle.HandleCors(resp, request)
|
||||
if cors {
|
||||
return
|
||||
}
|
||||
|
||||
// Gets a struct of Username, password
|
||||
data, err := shuffle.ParseLoginParameters(resp, request)
|
||||
if err != nil {
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Handling login of %s", data.Username)
|
||||
|
||||
err = checkUsername(data.Username)
|
||||
if err != nil {
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
log.Printf("[INFO] Login Username: %s", data.Username)
|
||||
users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(data.Username)))
|
||||
if err != nil && len(users) == 0 {
|
||||
log.Printf("[WARNING] Failed getting user %s: %s", data.Username, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`))
|
||||
return
|
||||
}
|
||||
|
||||
if len(users) != 1 {
|
||||
log.Printf(`Found multiple or no users with the same username: %s: %d`, data.Username, len(users))
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %d users with username %s"}`, len(users), data.Username)))
|
||||
return
|
||||
}
|
||||
|
||||
Userdata := users[0]
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(Userdata.Password), []byte(data.Password))
|
||||
if err != nil {
|
||||
log.Printf("Password for %s is incorrect: %s", data.Username, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`))
|
||||
return
|
||||
}
|
||||
|
||||
if !Userdata.Active {
|
||||
log.Printf("%s is not active, but tried to login. Error: %v", data.Username, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "This user is deactivated"}`))
|
||||
return
|
||||
}
|
||||
|
||||
tutorialsFinished := []shuffle.Tutorial{}
|
||||
for _, tutorial := range Userdata.PersonalInfo.Tutorials {
|
||||
tutorialsFinished = append(tutorialsFinished, shuffle.Tutorial{
|
||||
Name: tutorial,
|
||||
})
|
||||
}
|
||||
returnValue := shuffle.HandleInfo{
|
||||
Success: true,
|
||||
Tutorials: tutorialsFinished,
|
||||
}
|
||||
|
||||
loginData := `{"success": true}`
|
||||
newData, err := json.Marshal(returnValue)
|
||||
if err == nil {
|
||||
loginData = string(newData)
|
||||
}
|
||||
|
||||
if len(Userdata.Session) != 0 {
|
||||
log.Println("[INFO] User session already exists - resetting it")
|
||||
expiration := time.Now().Add(3600 * time.Second)
|
||||
|
||||
http.SetCookie(resp, &http.Cookie{
|
||||
Name: "session_token",
|
||||
Value: Userdata.Session,
|
||||
Expires: expiration,
|
||||
})
|
||||
|
||||
returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{
|
||||
Key: "session_token",
|
||||
Value: Userdata.Session,
|
||||
Expiration: expiration.Unix(),
|
||||
})
|
||||
|
||||
loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, Userdata.Session, expiration.Unix())
|
||||
newData, err := json.Marshal(returnValue)
|
||||
if err == nil {
|
||||
loginData = string(newData)
|
||||
}
|
||||
//log.Printf("SESSION LENGTH MORE THAN 0 IN LOGIN: %s", Userdata.Session)
|
||||
|
||||
err = shuffle.SetSession(ctx, Userdata, Userdata.Session)
|
||||
if err != nil {
|
||||
log.Printf("Error adding session to database: %s", err)
|
||||
}
|
||||
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(loginData))
|
||||
return
|
||||
} else {
|
||||
log.Printf("[INFO] User session is empty - create one!")
|
||||
|
||||
sessionToken := uuid.NewV4().String()
|
||||
expiration := time.Now().Add(3600 * time.Second)
|
||||
http.SetCookie(resp, &http.Cookie{
|
||||
Name: "session_token",
|
||||
Value: sessionToken,
|
||||
Expires: expiration,
|
||||
})
|
||||
|
||||
// ADD TO DATABASE
|
||||
err = shuffle.SetSession(ctx, Userdata, sessionToken)
|
||||
if err != nil {
|
||||
log.Printf("Error adding session to database: %s", err)
|
||||
}
|
||||
|
||||
Userdata.Session = sessionToken
|
||||
err = shuffle.SetUser(ctx, &Userdata, true)
|
||||
if err != nil {
|
||||
log.Printf("Failed updating user when setting session: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{
|
||||
Key: "session_token",
|
||||
Value: sessionToken,
|
||||
Expiration: expiration.Unix(),
|
||||
})
|
||||
|
||||
loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, sessionToken, expiration.Unix())
|
||||
newData, err := json.Marshal(returnValue)
|
||||
if err == nil {
|
||||
loginData = string(newData)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[INFO] %s SUCCESSFULLY LOGGED IN with session %s", data.Username, Userdata.Session)
|
||||
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(loginData))
|
||||
}
|
||||
|
||||
func fixOrgUser(ctx context.Context, org *shuffle.Org) *shuffle.Org {
|
||||
//found := false
|
||||
//for _, id := range user.Orgs {
|
||||
@@ -4323,9 +4173,9 @@ func runInitEs(ctx context.Context) {
|
||||
|
||||
url := os.Getenv("SHUFFLE_APP_DOWNLOAD_LOCATION")
|
||||
if len(url) == 0 {
|
||||
log.Printf("Skipping download since no URL is set")
|
||||
//url = "https://github.com/frikky/shuffle-apps"
|
||||
return
|
||||
log.Printf("[INFO] Skipping download of apps since no URL is set. Default would be https://github.com/frikky/shuffle-apps")
|
||||
//url = ""
|
||||
//return
|
||||
}
|
||||
|
||||
username := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_USERNAME")
|
||||
@@ -6042,7 +5892,7 @@ func initHandlers() {
|
||||
|
||||
// General - duplicates and old.
|
||||
r.HandleFunc("/api/v1/getusers", shuffle.HandleGetUsers).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/login", handleLogin).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/login", shuffle.HandleLogin).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/register", handleRegister).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
|
||||
|
||||
@@ -2737,7 +2737,7 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
|
||||
fullPath = fmt.Sprintf("%s%s", extra, "api.yml")
|
||||
fileReader, err = fs.Open(fullPath)
|
||||
if err != nil {
|
||||
log.Printf("Failed finding api.yaml/yml: %s", err)
|
||||
log.Printf("[INFO] Failed finding api.yaml/yml for file %s: %s", filename, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#r.HandleFunc("/api/v1/files/{fileId}", handleDeleteFile).Methods("DELETE", "OPTIONS")
|
||||
|
||||
|
||||
#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -d '{"filename": "rule2.yar", "org_id": "11f67b76-6051-4425-b0d6-be23daac6d12", "workflow_id": "global", "namespace": "yara"}'
|
||||
curl http://localhost:5002/api/v1/files/file_366ee8d2-1af6-4270-8639-213af30b4a29/upload -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -F 'shuffle_file=@upload.sh'
|
||||
curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -d '{"filename": "rule2.yar", "org_id": "292c7e25-40ad-4f05-904f-77d3c7b735e6", "workflow_id": "global", "namespace": "yara"}'
|
||||
curl http://localhost:5001/api/v1/files/file_eb89e315-eb66-4d76-9df7-530fb003fc84/upload -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -F 'shuffle_file=@upload.sh'
|
||||
|
||||
#curl http://localhost:5001/api/v1/files/namespaces/yara -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" --output rules.zip
|
||||
|
||||
@@ -910,7 +910,9 @@ const AppFramework = (props) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoadNextSuggestion(frameworkData)
|
||||
if (!window.location.pathname.includes("usecases")) {
|
||||
handleLoadNextSuggestion(frameworkData)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -985,9 +987,8 @@ const AppFramework = (props) => {
|
||||
}, [newSelectedApp])
|
||||
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
|
||||
|
||||
const imgSize = 50;
|
||||
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
|
||||
@@ -1906,7 +1907,7 @@ const AppFramework = (props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{injectedApps.map((apps) => {
|
||||
{injectedApps.map((apps, appindex) => {
|
||||
var categoryTop = 100
|
||||
var categoryLeft = 100
|
||||
|
||||
@@ -1934,7 +1935,7 @@ const AppFramework = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", position: "absolute", top: categoryTop, left: categoryLeft, zIndex: 10010, }}>
|
||||
<div key={appindex} style={{display: "flex", position: "absolute", top: categoryTop, left: categoryLeft, zIndex: 10010, }}>
|
||||
{apps.map((app, appIndex) => {
|
||||
return (
|
||||
<Chip
|
||||
|
||||
@@ -170,10 +170,7 @@ const ConfigureWorkflow = (props) => {
|
||||
"required": true,
|
||||
})
|
||||
} else {
|
||||
if (
|
||||
action.authentication_id === "" &&
|
||||
app.authentication.required === true
|
||||
) {
|
||||
if (action.authentication_id === "" && app.authentication.required === true && action.parameters !== undefined && action.parameters !== null) {
|
||||
// Check if configuration is filled or not
|
||||
var filled = true;
|
||||
for (let [key,keyval] in Object.entries(action.parameters)) {
|
||||
|
||||
@@ -701,10 +701,7 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
|
||||
// bad detection mechanism probably
|
||||
if (
|
||||
event.target.value[event.target.value.length - 1] === "." &&
|
||||
actionlist.length > 0
|
||||
) {
|
||||
if (event.target.value[event.target.value.length - 1] === "." && actionlist.length > 0) {
|
||||
console.log("GET THE LAST ARGUMENT FOR NODE!");
|
||||
// THIS IS AN EXAMPLE OF SHOWING IT
|
||||
/*
|
||||
@@ -2098,6 +2095,8 @@ const ParsedAction = (props) => {
|
||||
toComplete += values[key].autocomplete;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handles the fields under OpenAPI body to be parsed.
|
||||
if (data.name.startsWith("${") && data.name.endsWith("}")) {
|
||||
console.log("INSIDE VALUE REPLACE: ", data.name, toComplete);
|
||||
@@ -2131,10 +2130,8 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
selectedActionParameters[count]["value_replace"] =
|
||||
paramcheck;
|
||||
selectedAction.parameters[count]["value_replace"] =
|
||||
paramcheck;
|
||||
selectedActionParameters[count]["value_replace"] = paramcheck;
|
||||
selectedAction.parameters[count]["value_replace"] = paramcheck;
|
||||
setSelectedAction(selectedAction);
|
||||
setUpdate(Math.random());
|
||||
|
||||
@@ -2144,13 +2141,15 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
selectedActionParameters[count].value += toComplete;
|
||||
selectedAction.parameters[count].value =
|
||||
selectedActionParameters[count].value;
|
||||
setSelectedAction(selectedAction);
|
||||
setUpdate(Math.random());
|
||||
|
||||
setShowDropdown(false);
|
||||
console.log("In nestedclick!!")
|
||||
var newValue = selectedActionParameters[count].value + toComplete
|
||||
changeActionParameter({target: {value: newValue}}, count, data)
|
||||
//selectedActionParameters[count].value += toComplete;
|
||||
//selectedAction.parameters[count].value = selectedActionParameters[count].value;
|
||||
//setSelectedAction(selectedAction);
|
||||
//setUpdate(Math.random());
|
||||
|
||||
setShowDropdown(false);
|
||||
setMenuPosition(null);
|
||||
};
|
||||
|
||||
@@ -2604,7 +2603,9 @@ const ParsedAction = (props) => {
|
||||
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
onClick={() => setShowAutocomplete(true)}
|
||||
onClick={() => {
|
||||
setShowAutocomplete(true)
|
||||
}}
|
||||
fullWidth
|
||||
open={showAutocomplete}
|
||||
style={{
|
||||
@@ -2614,22 +2615,12 @@ const ParsedAction = (props) => {
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (
|
||||
selectedActionParameters[count].value[
|
||||
selectedActionParameters[count].value.length - 1
|
||||
] === "."
|
||||
) {
|
||||
e.target.value.autocomplete =
|
||||
e.target.value.autocomplete.slice(
|
||||
1,
|
||||
e.target.value.autocomplete.length
|
||||
);
|
||||
if (selectedActionParameters[count].value[selectedActionParameters[count].value.length - 1] === ".") {
|
||||
e.target.value.autocomplete = e.target.value.autocomplete.slice(1,e.target.value.autocomplete.length);
|
||||
}
|
||||
|
||||
selectedActionParameters[count].value +=
|
||||
e.target.value.autocomplete;
|
||||
selectedAction.parameters[count].value =
|
||||
selectedActionParameters[count].value;
|
||||
selectedActionParameters[count].value += e.target.value.autocomplete;
|
||||
selectedAction.parameters[count].value = selectedActionParameters[count].value;
|
||||
setSelectedAction(selectedAction);
|
||||
setUpdate(Math.random());
|
||||
|
||||
@@ -2973,108 +2964,110 @@ const ParsedAction = (props) => {
|
||||
// Should make it a function lol
|
||||
if (workflow.branches !== undefined && workflow.branches !== null) {
|
||||
for (let [key,keyval] in Object.entries(workflow.branches)) {
|
||||
for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) {
|
||||
const condition = workflow.branches[key].conditions[subkey]
|
||||
const sourceparam = condition.source
|
||||
const destinationparam = condition.destination
|
||||
if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) {
|
||||
for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) {
|
||||
const condition = workflow.branches[key].conditions[subkey]
|
||||
const sourceparam = condition.source
|
||||
const destinationparam = condition.destination
|
||||
|
||||
// Should have a smarter way of discovering node names
|
||||
// Finding index(es) and replacing at the location
|
||||
if (sourceparam.value.includes("$")) {
|
||||
try {
|
||||
var cnt = -1
|
||||
var previous = 0
|
||||
while (true) {
|
||||
cnt += 1
|
||||
// Need to make sure e.g. changing the first here doesn't change the 2nd
|
||||
// $change_me
|
||||
// $change_me_2
|
||||
|
||||
const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
|
||||
if (foundindex === previous && foundindex !== 0) {
|
||||
break
|
||||
}
|
||||
|
||||
if (foundindex >= 0) {
|
||||
previous = foundindex+newname.length
|
||||
// Need to add diff of length to word
|
||||
|
||||
// Check location:
|
||||
// If it's a-zA-Z_ then don't replace
|
||||
if (sourceparam.value.length > foundindex+parsedBaseLabel.length) {
|
||||
const regex = /[a-zA-Z0-9_]/g;
|
||||
const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex);
|
||||
if (match !== null) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Should have a smarter way of discovering node names
|
||||
// Finding index(es) and replacing at the location
|
||||
if (sourceparam.value.includes("$")) {
|
||||
try {
|
||||
var cnt = -1
|
||||
var previous = 0
|
||||
while (true) {
|
||||
cnt += 1
|
||||
// Need to make sure e.g. changing the first here doesn't change the 2nd
|
||||
// $change_me
|
||||
// $change_me_2
|
||||
|
||||
console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value)
|
||||
const extralength = newname.length-parsedBaseLabel.length
|
||||
sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length)
|
||||
const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
|
||||
if (foundindex === previous && foundindex !== 0) {
|
||||
break
|
||||
}
|
||||
|
||||
if (foundindex >= 0) {
|
||||
previous = foundindex+newname.length
|
||||
// Need to add diff of length to word
|
||||
|
||||
// Check location:
|
||||
// If it's a-zA-Z_ then don't replace
|
||||
if (sourceparam.value.length > foundindex+parsedBaseLabel.length) {
|
||||
const regex = /[a-zA-Z0-9_]/g;
|
||||
const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex);
|
||||
if (match !== null) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value)
|
||||
const extralength = newname.length-parsedBaseLabel.length
|
||||
sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length)
|
||||
|
||||
console.log("New: ", workflow.branches[key].conditions[subkey].source.value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
console.log("New: ", workflow.branches[key].conditions[subkey].source.value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
// Break no matter what after 5 replaces. May need to increase
|
||||
if (cnt >= 5) {
|
||||
break
|
||||
}
|
||||
// Break no matter what after 5 replaces. May need to increase
|
||||
if (cnt >= 5) {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Failed value replacement based on index: ", e)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Failed value replacement based on index: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
if (destinationparam.value.includes("$")) {
|
||||
try {
|
||||
var cnt = -1
|
||||
var previous = 0
|
||||
while (true) {
|
||||
cnt += 1
|
||||
// Need to make sure e.g. changing the first here doesn't change the 2nd
|
||||
// $change_me
|
||||
// $change_me_2
|
||||
|
||||
const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
|
||||
if (foundindex === previous && foundindex !== 0) {
|
||||
break
|
||||
}
|
||||
|
||||
if (foundindex >= 0) {
|
||||
previous = foundindex+newname.length
|
||||
// Need to add diff of length to word
|
||||
|
||||
// Check location:
|
||||
// If it's a-zA-Z_ then don't replace
|
||||
if (destinationparam.value.length > foundindex+parsedBaseLabel.length) {
|
||||
const regex = /[a-zA-Z0-9_]/g;
|
||||
const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex);
|
||||
if (match !== null) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (destinationparam.value.includes("$")) {
|
||||
try {
|
||||
var cnt = -1
|
||||
var previous = 0
|
||||
while (true) {
|
||||
cnt += 1
|
||||
// Need to make sure e.g. changing the first here doesn't change the 2nd
|
||||
// $change_me
|
||||
// $change_me_2
|
||||
|
||||
console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value)
|
||||
const extralength = newname.length-parsedBaseLabel.length
|
||||
destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length)
|
||||
const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
|
||||
if (foundindex === previous && foundindex !== 0) {
|
||||
break
|
||||
}
|
||||
|
||||
if (foundindex >= 0) {
|
||||
previous = foundindex+newname.length
|
||||
// Need to add diff of length to word
|
||||
|
||||
// Check location:
|
||||
// If it's a-zA-Z_ then don't replace
|
||||
if (destinationparam.value.length > foundindex+parsedBaseLabel.length) {
|
||||
const regex = /[a-zA-Z0-9_]/g;
|
||||
const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex);
|
||||
if (match !== null) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value)
|
||||
const extralength = newname.length-parsedBaseLabel.length
|
||||
destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length)
|
||||
|
||||
console.log("New: ", workflow.branches[key].conditions[subkey].destination.value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
console.log("New: ", workflow.branches[key].conditions[subkey].destination.value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
// Break no matter what after 5 replaces. May need to increase
|
||||
if (cnt >= 5) {
|
||||
break
|
||||
}
|
||||
// Break no matter what after 5 replaces. May need to increase
|
||||
if (cnt >= 5) {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Failed value replacement based on index: ", e)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Failed value replacement based on index: ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,14 @@ import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
const liquidFilters = [
|
||||
{"name": "Size", "value": "size", "example": ""},
|
||||
{"name": "Date", "value": `date: "%Y%m%d"`, "example": `{{ "now" | date: "%s" }}`},
|
||||
{"name": "Split", "value": `split: ","`, "example": `{{ "this,can,become,a,list" | split: "," }}`},
|
||||
{"name": "Join", "value": `join: ","`, "example": `{{ ["this","can","become","a","string"] | join: "," }}`},
|
||||
{"name": "Escape String", "value": `{{ \"\"\"'string with weird'" quotes\"\"\" | escape_string }}`, "example": ``},
|
||||
{"name": "Flatten", "value": `flatten`, "example": `{{ [1, [1, 2], [2, 3, 4]] | flatten }}`},
|
||||
{"name": "URL encode", "value": `url_encode`, "example": `{{ "https://www.google.com/search?q=hello world" | url_encode }}`},
|
||||
{"name": "URL decode ", "value": `url_decode`, "example": `{{ "https://www.google.com/search?q=hello%20world" | url_decode }}`},
|
||||
{"name": "base64_encode", "value": `base64_encode`, "example": `{{ "https://www.google.com/search?q=hello%20world" | base64_encode }}`},
|
||||
{"name": "base64_decode", "value": `base64_decode`, "example": `{{ "aGVsbG8K" | base64_encode }}`},
|
||||
]
|
||||
|
||||
const mathFilters = [
|
||||
@@ -426,10 +432,11 @@ const CodeEditor = (props) => {
|
||||
if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){
|
||||
valuefound = true
|
||||
|
||||
console.log("Valuefound: ", fixedVariable, actionlist[j].example)
|
||||
|
||||
try {
|
||||
if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
|
||||
if (typeof actionlist[j].example === "object") {
|
||||
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
|
||||
|
||||
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
|
||||
input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example));
|
||||
} else {
|
||||
input = input.replace(fixedVariable, actionlist[j].example)
|
||||
@@ -438,6 +445,7 @@ const CodeEditor = (props) => {
|
||||
input = input.replace(fixedVariable, actionlist[j].example)
|
||||
}
|
||||
} else {
|
||||
// Couldn't find the correct example value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,12 +573,14 @@ const CodeEditor = (props) => {
|
||||
inputdata = JSON.stringify(inputdata)
|
||||
}
|
||||
|
||||
// Shuffle Tools 1.2.0 (in most cases?)
|
||||
const appid = "3e2bdf9d5069fe3f4746c29d68785a6a"
|
||||
const actiondata = {"description":"Repeats the call parameter","id":"","name":"repeat_back_to_me","label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id":"3e2bdf9d5069fe3f4746c29d68785a6a","tags":null,"authentication":[],"tested":false,"parameters":[{"description":"The message to repeat","id":"","name":"call","example":"REPEATING: Hello world","value":inputdata,"multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false,"autocompleted":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}}
|
||||
|
||||
setExecutionResult({
|
||||
"valid": false,
|
||||
"result": baseResult,
|
||||
"errors": [],
|
||||
})
|
||||
|
||||
setExecuting(true)
|
||||
@@ -593,21 +603,27 @@ const CodeEditor = (props) => {
|
||||
})
|
||||
.then((responseJson) => {
|
||||
//console.log("RESPONSE: ", responseJson)
|
||||
var newResult = {}
|
||||
if (responseJson.success === true && responseJson.result !== null && responseJson.result !== undefined && responseJson.result.length > 0) {
|
||||
const result = responseJson.result.slice(0, 50)+"..."
|
||||
//alert.info("SUCCESS: "+result)
|
||||
|
||||
const validate = validateJson(responseJson.result)
|
||||
setExecutionResult(validate)
|
||||
newResult = validate
|
||||
} else if (responseJson.success === false && responseJson.reason !== undefined && responseJson.reason !== null) {
|
||||
alert.error(responseJson.reason)
|
||||
setExecutionResult({"valid": false, "result": responseJson.reason})
|
||||
newResult = {"valid": false, "result": responseJson.reason}
|
||||
} else if (responseJson.success === true) {
|
||||
setExecutionResult({"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."})
|
||||
newResult = {"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."}
|
||||
} else {
|
||||
setExecutionResult({"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."})
|
||||
newResult = {"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."}
|
||||
}
|
||||
|
||||
|
||||
if (responseJson.errors !== undefined && responseJson.errors !== null && responseJson.errors.length > 0) {
|
||||
newResult.errors = responseJson.errors
|
||||
}
|
||||
|
||||
setExecutionResult(newResult)
|
||||
setExecuting(false)
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -1262,7 +1278,7 @@ const CodeEditor = (props) => {
|
||||
<IconButton disabled={executing} color="primary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 300, padding: 8}} variant="contained" onClick={() => {
|
||||
executeSingleAction(expOutput)
|
||||
}}>
|
||||
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window." placement="top">
|
||||
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
|
||||
{executing ? <CircularProgress style={{height: 18, width: 18, }} /> : <PlayArrowIcon style={{height: 18, width: 18, }} /> }
|
||||
|
||||
</Tooltip>
|
||||
@@ -1340,6 +1356,11 @@ const CodeEditor = (props) => {
|
||||
Test output: {executionResult.result}
|
||||
</Typography>
|
||||
: null}
|
||||
{executionResult.errors !== undefined && executionResult.errors !== null && executionResult.errors.length > 0 ?
|
||||
<Typography variant="body2" style={{maxHeight: 100, overflow: "auto", color: "#f85a3e",}}>
|
||||
Errors ({executionResult.errors.length}): {executionResult.errors.join("\n")}
|
||||
</Typography>
|
||||
: null}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -209,7 +209,7 @@ const Admin = (props) => {
|
||||
const [openEditor, setOpenEditor] = React.useState(false);
|
||||
const [renderTextBox, setRenderTextBox] = React.useState(false);
|
||||
const [openFileId, setOpenFileId] = React.useState(false);
|
||||
const allowedFileTypes = ["txt", "py", "yaml","yml","json"]
|
||||
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv",]
|
||||
|
||||
const runUpdateText = (text) =>{
|
||||
fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, {
|
||||
@@ -1017,6 +1017,36 @@ const Admin = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const rerunCloudWorkflows = (environment) => {
|
||||
alert.info("Starting execution reruns. This can run in the background.")
|
||||
fetch(
|
||||
`${globalUrl}/api/v1/environments/${environment.id}/rerun`,
|
||||
{
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!");
|
||||
return;
|
||||
} else {
|
||||
alert.error(response.reason);
|
||||
//alert.info("Aborted all dangling workflows");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log("Got response for execution: ", responseJson);
|
||||
//console.log("RESPONSE: ", responseJson)
|
||||
//setFiles(responseJson)
|
||||
})
|
||||
.catch((error) => {
|
||||
//alert.error(error.toString())
|
||||
});
|
||||
};
|
||||
|
||||
const abortEnvironmentWorkflows = (environment) => {
|
||||
//console.log("Aborting all workflows started >10 minutes ago, not finished");
|
||||
|
||||
@@ -3943,7 +3973,7 @@ const Admin = (props) => {
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Created"
|
||||
primary="Updated"
|
||||
style={{ maxWidth: 225, minWidth: 225 }}
|
||||
/>
|
||||
<ListItemText
|
||||
@@ -3989,7 +4019,8 @@ const Admin = (props) => {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
|
||||
const isDisabledButton = isCloud || file.filesize < 100000 && file.status === ("active") && allowedFileTypes.includes(file.filename.split(".")[1]) === true
|
||||
const filenamesplit = file.filename.split(".")
|
||||
const iseditable = file.filesize < 100000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1])
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
@@ -4006,7 +4037,7 @@ const Admin = (props) => {
|
||||
minWidth: 225,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
primary={new Date(file.created_at * 1000).toISOString()}
|
||||
primary={new Date(file.updated_at * 1000).toISOString()}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{
|
||||
@@ -4099,13 +4130,13 @@ const Admin = (props) => {
|
||||
<ListItemText
|
||||
primary=<span style={{ display:"inline"}}>
|
||||
<Tooltip
|
||||
title={"Edit File"}
|
||||
title={`Edit File (${allowedFileTypes.join(", ")})`}
|
||||
style={{}}
|
||||
aria-label={"Edit"}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
disabled={isDisabledButton ? false : true}
|
||||
disabled={!iseditable}
|
||||
style = {{padding: "6px"}}
|
||||
onClick={() => {
|
||||
setOpenEditor(true)
|
||||
@@ -4114,11 +4145,7 @@ const Admin = (props) => {
|
||||
}}
|
||||
>
|
||||
<EditIcon
|
||||
style={{
|
||||
color: isDisabledButton === true
|
||||
? "white"
|
||||
: "grey",
|
||||
}}
|
||||
style={{color: iseditable ? "white" : "grey",}}
|
||||
/>
|
||||
</IconButton>
|
||||
</span>
|
||||
@@ -4888,9 +4915,7 @@ const Admin = (props) => {
|
||||
>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
variant={
|
||||
environment.archived ? "contained" : "outlined"
|
||||
}
|
||||
variant={environment.archived ? "contained" : "outlined"}
|
||||
style={{ borderRadius: "0px" }}
|
||||
onClick={() => deleteEnvironment(environment)}
|
||||
color="primary"
|
||||
@@ -4900,16 +4925,19 @@ const Admin = (props) => {
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
style={{ borderRadius: "0px" }}
|
||||
disabled={isCloud && environment.Name.toLowerCase() !== "cloud"}
|
||||
onClick={() => {
|
||||
console.log(
|
||||
"Should clear executions for: ",
|
||||
environment
|
||||
);
|
||||
abortEnvironmentWorkflows(environment);
|
||||
console.log("Should clear executions for: ", environment);
|
||||
|
||||
if (isCloud && environment.Name.toLowerCase() === "cloud") {
|
||||
rerunCloudWorkflows(environment);
|
||||
} else {
|
||||
abortEnvironmentWorkflows(environment);
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
Clear
|
||||
{isCloud && environment.Name.toLowerCase() === "cloud" ? "Rerun" : "Clear"}
|
||||
</Button>
|
||||
</div>
|
||||
</ListItemText>
|
||||
|
||||
@@ -274,6 +274,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
|
||||
const [triggerFolders, setTriggerFolders] = React.useState([]);
|
||||
const [workflows, setWorkflows] = React.useState([]);
|
||||
const [parentWorkflows, setParentWorkflows] = React.useState([]);
|
||||
const [showEnvironment, setShowEnvironment] = React.useState(false);
|
||||
const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);
|
||||
|
||||
@@ -487,7 +488,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson !== undefined) {
|
||||
setWorkflows(responseJson);
|
||||
|
||||
// Sets up subflow trigger with the right info
|
||||
if (trigger_index > -1) {
|
||||
@@ -529,6 +529,37 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (workflows.length === 0) {
|
||||
//console.log("First request. Checking for parent trigger (if this is subflow")
|
||||
var parentworkflows = []
|
||||
for (let workflowkey in responseJson) {
|
||||
const innerworkflow = responseJson[workflowkey]
|
||||
|
||||
for (let triggerkey in innerworkflow.triggers) {
|
||||
const trigger = innerworkflow.triggers[triggerkey]
|
||||
if (trigger.trigger_type === "SUBFLOW") {
|
||||
|
||||
for (let paramkey in trigger.parameters) {
|
||||
const param = trigger.parameters[paramkey]
|
||||
if (param.name === "workflow" && param.value === props.match.params.key) {
|
||||
parentworkflows.push({
|
||||
id: innerworkflow.id,
|
||||
name: innerworkflow.name,
|
||||
image: innerworkflow.image,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parentworkflows.length > 0) {
|
||||
setParentWorkflows(parentworkflows.filter(wf => wf.id !== props.match.params.key))
|
||||
}
|
||||
}
|
||||
|
||||
setWorkflows(responseJson);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -2324,54 +2355,85 @@ const AngularWorkflow = (defaultprops) => {
|
||||
((nodedata.app_name !== "Shuffle Tools" &&
|
||||
nodedata.app_name !== "Testing" &&
|
||||
nodedata.app_name !== "Shuffle Workflow" &&
|
||||
nodedata.app_name !== "User Input" &&
|
||||
nodedata.app_name !== "Webhook" &&
|
||||
nodedata.app_name !== "Schedule" &&
|
||||
nodedata.app_name !== "Email") ||
|
||||
nodedata.app_name !== "User Input") ||
|
||||
nodedata.isStartNode)
|
||||
) {
|
||||
const allNodes = cy.nodes().jsons();
|
||||
var found = false;
|
||||
for (let nodekey in allNodes) {
|
||||
const currentNode = allNodes[nodekey];
|
||||
if (
|
||||
currentNode.data.attachedTo === nodedata.id &&
|
||||
currentNode.data.isDescriptor
|
||||
) {
|
||||
found = true;
|
||||
console.log("FOUND THE NODE!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
const allNodes = cy.nodes().jsons();
|
||||
var found = false;
|
||||
for (let nodekey in allNodes) {
|
||||
const currentNode = allNodes[nodekey];
|
||||
if (
|
||||
currentNode.data.attachedTo === nodedata.id &&
|
||||
currentNode.data.isDescriptor
|
||||
) {
|
||||
found = true;
|
||||
console.log("FOUND THE NODE!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Readding the icon after moving the node
|
||||
if (!found) {
|
||||
const iconInfo = GetIconInfo(nodedata);
|
||||
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
|
||||
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
|
||||
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
|
||||
console.log("Found triggers. Add!")
|
||||
|
||||
const offset = nodedata.isStartNode ? 36 : 44;
|
||||
const decoratorNode = {
|
||||
position: {
|
||||
x: event.target.position().x + offset,
|
||||
y: event.target.position().y + offset,
|
||||
},
|
||||
locked: true,
|
||||
data: {
|
||||
isDescriptor: true,
|
||||
isValid: true,
|
||||
is_valid: true,
|
||||
label: "",
|
||||
image: svgpin_Url,
|
||||
imageColor: iconInfo.iconBackgroundColor,
|
||||
attachedTo: nodedata.id,
|
||||
},
|
||||
};
|
||||
if (!found) {
|
||||
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
|
||||
// Find how many executions it has
|
||||
var executions = 0
|
||||
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
|
||||
console.log("Matches: ", matchingExecutions.length)
|
||||
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
|
||||
const decoratorNode = {
|
||||
position: {
|
||||
x: event.target.position().x + 44,
|
||||
y: event.target.position().y + 44,
|
||||
},
|
||||
locked: true,
|
||||
data: {
|
||||
isDescriptor: true,
|
||||
isValid: true,
|
||||
is_valid: true,
|
||||
isTrigger: true,
|
||||
label: `${matchingExecutions.length}`,
|
||||
attachedTo: nodedata.id,
|
||||
imageColor: color,
|
||||
hasExecutions: true,
|
||||
},
|
||||
};
|
||||
|
||||
cy.add(decoratorNode).unselectify();
|
||||
} else {
|
||||
console.log("Node already exists - don't add descriptor node");
|
||||
}
|
||||
cy.add(decoratorNode)
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
// Readding the icon after moving the node
|
||||
if (!found) {
|
||||
const iconInfo = GetIconInfo(nodedata);
|
||||
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
|
||||
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
|
||||
|
||||
const offset = nodedata.isStartNode ? 36 : 44;
|
||||
const decoratorNode = {
|
||||
position: {
|
||||
x: event.target.position().x + offset,
|
||||
y: event.target.position().y + offset,
|
||||
},
|
||||
locked: true,
|
||||
data: {
|
||||
isDescriptor: true,
|
||||
isValid: true,
|
||||
is_valid: true,
|
||||
label: "",
|
||||
image: svgpin_Url,
|
||||
imageColor: iconInfo.iconBackgroundColor,
|
||||
attachedTo: nodedata.id,
|
||||
},
|
||||
};
|
||||
|
||||
cy.add(decoratorNode).unselectify();
|
||||
} else {
|
||||
console.log("Node already exists - don't add descriptor node");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
originalLocation = {
|
||||
@@ -2676,6 +2738,11 @@ const AngularWorkflow = (defaultprops) => {
|
||||
return;
|
||||
} else if (data.isDescriptor) {
|
||||
console.log("Can't select descriptor");
|
||||
if (data.isTrigger) {
|
||||
console.log("But maybe we can select trigger descriptor? Maybe open execution tab?")
|
||||
setExecutionModalOpen(true)
|
||||
}
|
||||
|
||||
event.target.unselect();
|
||||
return;
|
||||
}
|
||||
@@ -4211,6 +4278,11 @@ const AngularWorkflow = (defaultprops) => {
|
||||
});
|
||||
};
|
||||
|
||||
const addRunCountButton = (event) => {
|
||||
// Count executions?
|
||||
// Maybe it shouldn't be onclick?
|
||||
}
|
||||
|
||||
const addCopyButton = (event) => {
|
||||
var parentNode = cy.$("#" + event.target.data("id"));
|
||||
if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
|
||||
@@ -4379,7 +4451,52 @@ const AngularWorkflow = (defaultprops) => {
|
||||
//if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
|
||||
|
||||
if (nodedata.app_name !== undefined && !workflow.public === true) {
|
||||
const allNodes = cy.nodes().jsons();
|
||||
const allNodes = cy.nodes().jsons();
|
||||
|
||||
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
|
||||
console.log("In this :)")
|
||||
|
||||
var found = false;
|
||||
for (let nodekey in allNodes) {
|
||||
const currentNode = allNodes[nodekey];
|
||||
if (
|
||||
currentNode.data.attachedTo === nodedata.id &&
|
||||
currentNode.data.isDescriptor
|
||||
) {
|
||||
found = true;
|
||||
console.log("FOUND THE NODE!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
|
||||
// Find how many executions it has
|
||||
var executions = 0
|
||||
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
|
||||
console.log("Matches: ", matchingExecutions.length)
|
||||
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
|
||||
const decoratorNode = {
|
||||
position: {
|
||||
x: event.target.position().x + 44,
|
||||
y: event.target.position().y + 44,
|
||||
},
|
||||
locked: true,
|
||||
data: {
|
||||
isDescriptor: true,
|
||||
isValid: true,
|
||||
is_valid: true,
|
||||
isTrigger: true,
|
||||
label: `${matchingExecutions.length}`,
|
||||
attachedTo: nodedata.id,
|
||||
imageColor: color,
|
||||
hasExecutions: true,
|
||||
},
|
||||
};
|
||||
|
||||
cy.add(decoratorNode)
|
||||
}
|
||||
}
|
||||
|
||||
var found = false;
|
||||
for (var _key in allNodes) {
|
||||
@@ -4413,7 +4530,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (nodedata.type === "TRIGGER") {
|
||||
if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") {
|
||||
addCopyButton(event);
|
||||
}
|
||||
} else {
|
||||
// Check how many executions from the source
|
||||
addRunCountButton(event);
|
||||
}
|
||||
} else {
|
||||
addCopyButton(event);
|
||||
addStartnodeButton(event);
|
||||
@@ -4467,7 +4587,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
// locked: true,
|
||||
// })
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.target !== undefined && event.target !== null) {
|
||||
event.target.animate(
|
||||
@@ -4722,6 +4842,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
return decoratorNode;
|
||||
});
|
||||
|
||||
|
||||
const triggers = workflow.triggers.map((trigger) => {
|
||||
const node = {};
|
||||
node.position = trigger.position;
|
||||
@@ -5017,14 +5138,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
// App length necessary cus of cy initialization
|
||||
if (
|
||||
// First load - gets the workflow
|
||||
elements.length === 0 &&
|
||||
workflow.actions !== undefined &&
|
||||
!graphSetup &&
|
||||
Object.getOwnPropertyNames(workflow).length > 0
|
||||
) {
|
||||
|
||||
if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
|
||||
setGraphSetup(true);
|
||||
setupGraph();
|
||||
console.log("In graph setup")
|
||||
@@ -10073,6 +10187,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
name: "custom_response_body",
|
||||
value: "",
|
||||
};
|
||||
workflow.triggers[selectedTriggerIndex].parameters[4] = {
|
||||
name: "await_response",
|
||||
value: "v1",
|
||||
};
|
||||
setWorkflow(workflow);
|
||||
} else {
|
||||
// Always update
|
||||
@@ -11065,26 +11183,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
style={{ paddingLeft: 10, backgroundColor: inputColor }}
|
||||
row
|
||||
>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
workflow.triggers[selectedTriggerIndex].parameters[2] !==
|
||||
undefined &&
|
||||
workflow.triggers[
|
||||
selectedTriggerIndex
|
||||
].parameters[2].value.includes("subflow")
|
||||
}
|
||||
onChange={() => {
|
||||
setTriggerOptionsWrapper("subflow");
|
||||
}}
|
||||
color="primary"
|
||||
value="subflow"
|
||||
disabled
|
||||
/>
|
||||
}
|
||||
label={<div style={{ color: "white" }}>Subflow</div>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
@@ -11119,12 +11217,23 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
label={<div style={{ color: "white" }}>SMS</div>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")}
|
||||
onChange={() => {
|
||||
setTriggerOptionsWrapper("subflow");
|
||||
}}
|
||||
color="primary"
|
||||
value="subflow"
|
||||
/>
|
||||
}
|
||||
label={<div style={{ color: "white" }}>Subflow</div>}
|
||||
/>
|
||||
</FormGroup>
|
||||
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
|
||||
undefined &&
|
||||
workflow.triggers[
|
||||
selectedTriggerIndex
|
||||
].parameters[2].value.includes("email") ? (
|
||||
workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? (
|
||||
<TextField
|
||||
style={{
|
||||
backgroundColor: inputColor,
|
||||
@@ -11187,11 +11296,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
|
||||
undefined &&
|
||||
workflow.triggers[
|
||||
selectedTriggerIndex
|
||||
].parameters[2].value.includes("subflow") ? (
|
||||
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow") ? (
|
||||
<TextField
|
||||
style={{
|
||||
backgroundColor: inputColor,
|
||||
@@ -11213,8 +11318,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5].value
|
||||
}
|
||||
onBlur={(event) => {
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5].value =
|
||||
event.target.value;
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5].value = event.target.value;
|
||||
setWorkflow(workflow);
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
@@ -11565,6 +11669,36 @@ const AngularWorkflow = (defaultprops) => {
|
||||
<h2 style={{ margin: 0 }}>{workflow.name}</h2>
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
<div style={{display: "flex", marginLeft: 10, }}>
|
||||
{parentWorkflows.slice(0,5).map((wf, index) => {
|
||||
return (
|
||||
<a href={`/workflows/${wf.id}`} target="_blank" rel="noopener noreferrer" key={index}>
|
||||
<Tooltip arrow placement="left" title={
|
||||
<span style={{}}>
|
||||
{wf.image !== undefined && wf.image !== null && wf.image.length > 0 ?
|
||||
<img
|
||||
src={wf.image}
|
||||
alt={wf.name}
|
||||
style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }}
|
||||
|
||||
/>
|
||||
: null}
|
||||
<Typography>
|
||||
Parent workflow: '{wf.name}'
|
||||
</Typography>
|
||||
</span>
|
||||
|
||||
} placement="bottom">
|
||||
<span onClick={() => {
|
||||
console.log("Click: ", wf)
|
||||
}}>
|
||||
<img src={theme.palette.defaultImage} style={{height: 25, width: 25, cursor: "pointer", border: 15, marginRight: 5, marginTop: 5, filter: "grayscale(90%)", }} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -14146,6 +14280,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
width: imgsize,
|
||||
height: imgsize,
|
||||
border: `2px solid ${statusColor}`,
|
||||
filter: curapp === undefined ? "grayscale(100%)" : null,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -222,22 +222,23 @@ const parseCurl = (s) => {
|
||||
return out;
|
||||
};
|
||||
|
||||
// Basically CRUD for each category + special
|
||||
export const appCategories = [
|
||||
{
|
||||
"name": "Communication",
|
||||
"color": "#FFC107",
|
||||
"icon": "communication",
|
||||
"action_labels": ["List Messages", "Send Message",],
|
||||
"action_labels": ["List Messages", "Send Message", "Get Message", "Search messages"],
|
||||
}, {
|
||||
"name": "SIEM",
|
||||
"color": "#FFC107",
|
||||
"icon": "siem",
|
||||
"action_labels": ["Get alerts", "Search", "Create detection",],
|
||||
"action_labels": ["List Alerts", "Search", "Create detection", "Add hash to lookup_list",],
|
||||
}, {
|
||||
"name": "Eradication",
|
||||
"color": "#FFC107",
|
||||
"icon": "eradication",
|
||||
"action_labels": ["List tickets", "Update ticket", "Block hash", "Isolate host"],
|
||||
"action_labels": ["List Alerts", "Update Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host"],
|
||||
}, {
|
||||
"name": "Cases",
|
||||
"color": "#FFC107",
|
||||
@@ -247,27 +248,27 @@ export const appCategories = [
|
||||
"name": "Assets",
|
||||
"color": "#FFC107",
|
||||
"icon": "assets",
|
||||
"action_labels": [],
|
||||
"action_labels": ["List Assets", "Get Asset", "Search Assets", "Search Users", "Search endpoints", "Search vulnerabilities"],
|
||||
}, {
|
||||
"name": "Intel",
|
||||
"color": "#FFC107",
|
||||
"icon": "intel",
|
||||
"action_labels": [],
|
||||
"action_labels": ["Get IOC", "Search IOC", "Create IOC", "Update IOC", "Delete IOC", "Add IOC",],
|
||||
}, {
|
||||
"name": "IAM",
|
||||
"color": "#FFC107",
|
||||
"icon": "iam",
|
||||
"action_labels": [],
|
||||
"action_labels": ["Get Identity", "Get Asset", "Search Identity", "Reset Password", "Disable user", ],
|
||||
}, {
|
||||
"name": "Network",
|
||||
"color": "#FFC107",
|
||||
"icon": "network",
|
||||
"action_labels": ["Block IP",],
|
||||
"action_labels": ["Get Rules", "Allow IP", "Block IP",],
|
||||
}, {
|
||||
"name": "Other",
|
||||
"color": "#FFC107",
|
||||
"icon": "other",
|
||||
"action_labels": [],
|
||||
"action_labels": ["Update Info", "Get Info", "Get Status", "Get Version", "Get Health", "Get Config", "Get Configs", "Get Configs by type", "Get Configs by name", "Run script"],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -3152,11 +3153,15 @@ const AppCreator = (defaultprops) => {
|
||||
<Select
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
console.log("Should change: ", e.target.value)
|
||||
console.log("Should change: ", e.target.value, " Index: ", index)
|
||||
|
||||
actions[index].action_label = e.target.value
|
||||
setActions(actions)
|
||||
setUpdate(Math.random());
|
||||
const foundIndex = actions.findIndex((action) => action.name === data.name)
|
||||
console.log("Found index: ", foundIndex)
|
||||
if (foundIndex !== undefined && foundIndex !== null && foundIndex >= 0) {
|
||||
actions[foundIndex].action_label = e.target.value
|
||||
setActions(actions)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
}}
|
||||
value={data.action_label}
|
||||
style={{
|
||||
|
||||
@@ -99,6 +99,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
const [description, setDescription] = useState("");
|
||||
const [video, setVideo] = useState("");
|
||||
const [blogpost, setBlogpost] = useState("");
|
||||
const [workflowOutline, setWorkflowOutline] = useState("");
|
||||
|
||||
const [selectedWorkflows, setSelectedWorkflows] = useState([])
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
@@ -188,6 +189,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
parsedUsecase.mitre = mitreTags
|
||||
}
|
||||
|
||||
if (workflowOutline !== inputUsecase.workflow_outline) {
|
||||
inputUsecase.workflow_outline = workflowOutline
|
||||
parsedUsecase.workflow_outline = workflowOutline
|
||||
}
|
||||
|
||||
fetch(globalUrl + "/api/v1/workflows/usecases", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -303,6 +309,10 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
if (inputUsecase.extra_buttons !== undefined && inputUsecase.extra_buttons !== null) {
|
||||
subcase.extra_buttons = inputUsecase.extra_buttons
|
||||
}
|
||||
|
||||
if (inputUsecase.workflow_outline !== undefined && inputUsecase.workflow_outline !== null) {
|
||||
subcase.workflow_outline = inputUsecase.workflow_outline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +443,12 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
if (subcase.mitre !== undefined && subcase.mitre !== null) {
|
||||
setMitreTags(subcase.mitre)
|
||||
}
|
||||
|
||||
if (subcase.workflow_outline !== undefined && subcase.workflow_outline !== null) {
|
||||
setWorkflowOutline(subcase.workflow_outline)
|
||||
} else {
|
||||
setWorkflowOutline("")
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EditIcon style={{ color: usecase.color }} />
|
||||
@@ -502,9 +518,6 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, marginRight: 15, }}>
|
||||
{editing ?
|
||||
<div style={{flex: 1, marginRight: 50, }}>
|
||||
<Typography variant="h6">
|
||||
Editing!
|
||||
</Typography>
|
||||
<TextField
|
||||
style={{
|
||||
marginTop: 10,
|
||||
@@ -553,6 +566,26 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
}}
|
||||
id="videoEditing"
|
||||
/>
|
||||
<TextField
|
||||
style={{
|
||||
marginTop: 10,
|
||||
marginRight: 10,
|
||||
}}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
color="primary"
|
||||
label="Workflow Outline"
|
||||
placeholder={"Workflow Outline"}
|
||||
value={workflowOutline}
|
||||
multiline
|
||||
minRows={3}
|
||||
onChange={(event) => {
|
||||
setWorkflowOutline(event.target.value)
|
||||
}}
|
||||
id="workflowOutline"
|
||||
tabIndex="-1"
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
@@ -573,6 +606,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
setDescription("")
|
||||
setVideo("")
|
||||
setBlogpost("")
|
||||
setWorkflowOutline("")
|
||||
setEditing(false)
|
||||
}}
|
||||
>
|
||||
@@ -590,10 +624,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
|
||||
cursor: "pointer"
|
||||
}}
|
||||
onClick={(event) => {
|
||||
setEditing(false)
|
||||
setUsecaseItem(inputUsecase)
|
||||
setEditing(false)
|
||||
setDescription("")
|
||||
setVideo("")
|
||||
setWorkflowOutline("")
|
||||
setBlogpost("")
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user