Merge branch 'frikky:launch' into launch
This commit is contained in:
@@ -50,8 +50,8 @@ SHUFFLE_PASS_APP_PROXY=FALSE
|
||||
TZ=Europe/Amsterdam # Timezone-handler in Orborus, Worker and Apps
|
||||
ORBORUS_CONTAINER_NAME= # Used to FIND the containername. cgroup v2: issue 501
|
||||
|
||||
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
|
||||
SHUFFLE_BASE_IMAGE_NAME=frikky
|
||||
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
|
||||
SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-0.8.80"
|
||||
|
||||
# Used for auto-cleanup of containers. REALLY important at scale.
|
||||
|
||||
@@ -17,7 +17,7 @@ cd Shuffle
|
||||
|
||||
3. Fix prerequisites for the Opensearch database (Elasticsearch):
|
||||
```
|
||||
sudo chown 1000:1000 -R shuffle-database # Required for Opensearch
|
||||
sudo chown -R 1000:1000 shuffle-database # Required for Opensearch
|
||||
```
|
||||
|
||||
4. Run docker-compose.
|
||||
|
||||
@@ -27,3 +27,5 @@ shuffle-database/logging_enabled.conf
|
||||
shuffle-database/nodes
|
||||
shuffle-database/performance_analyzer_enabled.conf
|
||||
shuffle-database/rca_enabled.conf
|
||||
|
||||
*/package-lock.json
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
# Shuffle
|
||||
[Shuffle](https://shuffler.io) is an automation platform focused on accessibility. We believe everyone should have access to efficient processes, and are striving to make that a possibility by making integrations for YOUR tools. Security Operations is complex, but it doesn't have to be.
|
||||
<h1 align="center">
|
||||
|
||||
[](https://discord.gg/B2CBzUm)
|
||||
[](https://shuffler.io)
|
||||
|
||||
Shuffle Automation
|
||||
|
||||
</h1><h4 align="center">
|
||||
|
||||
 is an automation platform for and by the community, focusing on accessibility for anyone to automate. Security operations is complex, but it doesn't have to be.
|
||||
|
||||
[_Key Features_](https://shuffler.io/docs/features) —
|
||||
[_Community & Support_](https://discord.gg/B2CBzUm) —
|
||||
[_Documentation_](https://shuffler.io/docs) —
|
||||
[_Getting Started_](https://shuffler.io/docs/getting_started) —
|
||||
[_Development_](https://github.com/frikky/Shuffle/blob/master/.github/CONTRIBUTING.md)
|
||||
|
||||
Follow us on Twitter at [@shuffleio](https://twitter.com/shuffleio).
|
||||
|
||||
</h4>
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -8,5 +8,15 @@ This is the SDK used for apps to behave like they should.
|
||||
4. Delete the specific app's Docker image (docker rmi frikky/shuffle:...)
|
||||
5. Rebuild the Docker image (click load in GUI?)
|
||||
|
||||
## Cloud updates
|
||||
1. Go to shuffle cloud on GCP
|
||||
2. Go to Cloud Storage
|
||||
3. Find shuffler.appspot.com
|
||||
4. Navigate to generated_apps/baseline
|
||||
5. Update SDK there. This will make all new apps run with the new SDK
|
||||
|
||||
## Cloud app force-updates
|
||||
1. Run the "stitcher.go" program in the public shuffle-shared repository.
|
||||
|
||||
# LICENSE
|
||||
Everything in here is MIT, not AGPLv3 as indicated by the license.
|
||||
|
||||
+113
-4
@@ -62,12 +62,113 @@ class AppBase:
|
||||
if len(self.base_url) == 0:
|
||||
self.base_url = self.url
|
||||
|
||||
# Checks output for whether it should be automatically parsed or not
|
||||
def run_magic_parser(self, input_data):
|
||||
if not isinstance(input_data, str):
|
||||
self.logger.info("[DEBUG] Not string. Returning from magic")
|
||||
return input_data
|
||||
|
||||
# Don't touch existing JSON/lists
|
||||
if (input_data.startswith("[") and input_data.endswith("]")) or (input_data.startswith("{") and input_data.endswith("}")):
|
||||
self.logger.info("[DEBUG] Already JSON-like. Returning from magic")
|
||||
return input_data
|
||||
|
||||
if len(input_data) < 3:
|
||||
self.logger.info("[DEBUG] Too short input data")
|
||||
return input_data
|
||||
|
||||
# Don't touch large data.
|
||||
if len(input_data) > 100000:
|
||||
self.logger.info("[DEBUG] Value too large. Returning from magic")
|
||||
return input_data
|
||||
|
||||
if not "\n" in input_data and not "," in input_data:
|
||||
self.logger.info("[DEBUG] No data to autoparse - requires newline or comma")
|
||||
return input_data
|
||||
|
||||
new_input = input_data
|
||||
try:
|
||||
#new_input.strip()
|
||||
new_input = input_data.split()
|
||||
new_return = []
|
||||
|
||||
index = 0
|
||||
for item in new_input:
|
||||
splititem = ","
|
||||
if ", " in item:
|
||||
splititem = ", "
|
||||
elif "," in item:
|
||||
splititem = ","
|
||||
else:
|
||||
new_return.append(item)
|
||||
|
||||
index += 1
|
||||
continue
|
||||
|
||||
#print("FIX ITEM %s" % item)
|
||||
for subitem in item.split(splititem):
|
||||
new_return.insert(index, subitem)
|
||||
|
||||
index += 1
|
||||
|
||||
# Prevent large data or infinite loops
|
||||
if index > 10000:
|
||||
self.logger.info(f"[DEBUG] Infinite loop. Returning default data.")
|
||||
return input_data
|
||||
|
||||
fixed_return = []
|
||||
for item in new_return:
|
||||
if not item:
|
||||
continue
|
||||
|
||||
if not isinstance(item, str):
|
||||
fixed_return.append(item)
|
||||
continue
|
||||
|
||||
if item.endswith(","):
|
||||
item = item[0:-1]
|
||||
|
||||
fixed_return.append(item)
|
||||
|
||||
new_input = fixed_return
|
||||
except Exception as e:
|
||||
self.logger.info(f"[ERROR] Failed to run magic parser (2): {e}")
|
||||
return input_data
|
||||
|
||||
try:
|
||||
new_input = input_data.split()
|
||||
except Exception as e:
|
||||
self.logger.info(f"[ERROR] Failed to run magic parser (1): {e}")
|
||||
return input_data
|
||||
|
||||
# Won't ever touch this one?
|
||||
if isinstance(input_data, list) or isinstance(input_data, object):
|
||||
try:
|
||||
return json.dumps(new_input)
|
||||
except Exception as e:
|
||||
self.logger.info(f"[ERROR] Failed to run magic parser: {e}")
|
||||
|
||||
return new_input
|
||||
|
||||
# FIXME: Add more info like logs in here.
|
||||
# Docker logs: https://forums.docker.com/t/docker-logs-inside-the-docker-container/68190/2
|
||||
def send_result(self, action_result, headers, stream_path):
|
||||
if action_result["status"] == "EXECUTING":
|
||||
action_result["status"] = "FAILURE"
|
||||
|
||||
try:
|
||||
#self.logger.info(f"[DEBUG] ACTION: {self.action}")
|
||||
if self.action["run_magic_output"] == True:
|
||||
self.logger.warning(f"[INFO] Action result ran with Magic parser output.")
|
||||
action_result["result"] = self.run_magic_parser(action_result["result"])
|
||||
else:
|
||||
self.logger.warning(f"[ERROR] Magic output not defined.")
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[ERROR] Failed to run magic autoparser: {e}")
|
||||
pass
|
||||
|
||||
# Try it with some magic
|
||||
|
||||
self.logger.info(f"""[DEBUG] Inside Send result with status {action_result["status"]}""")
|
||||
|
||||
# FIXME: Add cleanup of parameters to not send to frontend here
|
||||
@@ -981,12 +1082,20 @@ class AppBase:
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
self.action_result["result"] = "Bad result from backend: %d" % ret.status_code
|
||||
self.action_result["result"] = json.dumps({
|
||||
"success": False,
|
||||
"reason": f"Bad result from backend during startup of app: {ret.status_code}",
|
||||
"extended_reason": f"{ret.text}"
|
||||
})
|
||||
self.send_result(self.action_result, headers, stream_path)
|
||||
return
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
self.logger.info("[DEBUG] FullExec Connectionerror: %s" % e)
|
||||
self.action_result["result"] = "Connection error during startup: %s" % e
|
||||
self.action_result["result"] = json.dumps({
|
||||
"success": False,
|
||||
"reason": f"Connection error during startup: {e}"
|
||||
})
|
||||
|
||||
self.send_result(self.action_result, headers, stream_path)
|
||||
return
|
||||
else:
|
||||
@@ -1501,7 +1610,7 @@ class AppBase:
|
||||
appendresult += char
|
||||
|
||||
actionname_lower = "exec"
|
||||
elif actionname_lower.startswith("shuffle_cache "):
|
||||
elif actionname_lower.startswith("shuffle_cache ") or actionname_lower.startswith("shuffle_db "):
|
||||
actionname_lower = "shuffle_cache"
|
||||
|
||||
actionname_lower = actionname_lower.replace(" ", "_", -1)
|
||||
@@ -2805,7 +2914,7 @@ class AppBase:
|
||||
# Dump the result as a string of a list
|
||||
#self.logger.info("RESULTS: %s" % results)
|
||||
if isinstance(results, list) or isinstance(results, dict):
|
||||
self.logger.info("JSON OBJECT? ", json_object)
|
||||
self.logger.info(f"JSON OBJECT? {json_object}")
|
||||
|
||||
# This part is weird lol
|
||||
if json_object:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
### DEFAULT
|
||||
NAME=shuffle-app_sdk
|
||||
VERSION=0.9.35
|
||||
VERSION=0.9.44
|
||||
|
||||
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
||||
docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
urllib3==1.26.5
|
||||
requests==2.25.1
|
||||
MarkupSafe==2.0.1
|
||||
liquidpy==0.7.2
|
||||
liquidpy==0.7.3
|
||||
flask[async]==2.0.2
|
||||
#waitress==2.0.0
|
||||
#flask==1.1.2
|
||||
|
||||
@@ -299,6 +299,7 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin
|
||||
}
|
||||
|
||||
if !downloaded {
|
||||
|
||||
return errors.New(fmt.Sprintf("Failed to build / download images %s", strings.Join(tags, ",")))
|
||||
}
|
||||
//baseDockerName
|
||||
|
||||
@@ -22,7 +22,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/h2non/filetype v1.1.1
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shuffle/shuffle-shared v0.1.44
|
||||
github.com/shuffle/shuffle-shared v0.1.62
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||
|
||||
+31
-80
@@ -2846,82 +2846,6 @@ func getOpenapi(resp http.ResponseWriter, request *http.Request) {
|
||||
resp.Write(data)
|
||||
}
|
||||
|
||||
func echoOpenapiData(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := handleCors(resp, request)
|
||||
if cors {
|
||||
return
|
||||
}
|
||||
|
||||
// Just here to verify that the user is logged in
|
||||
user, err := shuffle.HandleApiAuthentication(resp, request)
|
||||
if err != nil {
|
||||
log.Printf("Api authentication failed in validate swagger: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed authentication"}`))
|
||||
return
|
||||
}
|
||||
|
||||
if user.Role == "org-reader" {
|
||||
log.Printf("[WARNING] Org-reader doesn't have access to echo OpenAPI data: %s (%s)", user.Username, user.Id)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Read only user"}`))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
log.Printf("Bodyreader err: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`))
|
||||
return
|
||||
}
|
||||
|
||||
newbody := string(body)
|
||||
newbody = strings.TrimSpace(newbody)
|
||||
if strings.HasPrefix(newbody, "\"") {
|
||||
newbody = newbody[1:len(newbody)]
|
||||
}
|
||||
|
||||
if strings.HasSuffix(newbody, "\"") {
|
||||
newbody = newbody[0 : len(newbody)-1]
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", newbody, nil)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Requestbuilder err: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed building request"}`))
|
||||
return
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
newresp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Grabbing error: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed making remote request to get the data"}`)))
|
||||
return
|
||||
}
|
||||
defer newresp.Body.Close()
|
||||
|
||||
urlbody, err := ioutil.ReadAll(newresp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] URLbody error: %s", err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't get data from selected uri"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
if newresp.StatusCode >= 400 {
|
||||
resp.WriteHeader(201)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, urlbody)))
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteHeader(200)
|
||||
resp.Write(urlbody)
|
||||
}
|
||||
|
||||
func handleSwaggerValidation(body []byte) (shuffle.ParsedOpenApi, error) {
|
||||
type versionCheck struct {
|
||||
Swagger string `datastore:"swagger" json:"swagger" yaml:"swagger"`
|
||||
@@ -4071,7 +3995,7 @@ func runInitEs(ctx context.Context) {
|
||||
|
||||
// FIXME: Have this for all envs in all orgs (loop and find).
|
||||
if len(parsedApikey) > 0 {
|
||||
cleanupSchedule := 3600
|
||||
cleanupSchedule := 600
|
||||
environments := []string{"Shuffle"}
|
||||
log.Printf("[DEBUG] Starting schedule setup for execution cleanup every %d seconds. Running first immediately.", cleanupSchedule)
|
||||
cleanupJob := func() func() {
|
||||
@@ -4079,8 +4003,8 @@ func runInitEs(ctx context.Context) {
|
||||
log.Printf("[INFO] Running schedule for cleaning up or re-running unfinished workflows in %d environments.", len(environments))
|
||||
|
||||
for _, environment := range environments {
|
||||
url := fmt.Sprintf("http://localhost:5001/api/v1/environments/%s/stop", environment)
|
||||
httpClient := &http.Client{}
|
||||
url := fmt.Sprintf("http://localhost:5001/api/v1/environments/%s/stop", environment)
|
||||
req, err := http.NewRequest(
|
||||
"GET",
|
||||
url,
|
||||
@@ -4106,6 +4030,33 @@ func runInitEs(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
log.Printf("[DEBUG] Successfully ran workflow cleanup request for %s. Body: %s", environment, string(respBody))
|
||||
|
||||
url = fmt.Sprintf("http://localhost:5001/api/v1/environments/%s/rerun", environment)
|
||||
req, err = http.NewRequest(
|
||||
"GET",
|
||||
url,
|
||||
nil,
|
||||
)
|
||||
|
||||
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, parsedApikey))
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed CREATING environment request to rerun for %s: %s", environment, err)
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
newresp, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed running environment request to rerun for %s: %s", environment, err)
|
||||
continue
|
||||
}
|
||||
|
||||
respBody, err = ioutil.ReadAll(newresp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed setting respbody %s", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[DEBUG] Successfully ran workflow RERUN request for %s. Body: %s", environment, string(respBody))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5861,7 +5812,7 @@ func initHandlers() {
|
||||
// OpenAPI configuration
|
||||
r.HandleFunc("/api/v1/verify_swagger", verifySwagger).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/verify_openapi", verifySwagger).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/get_openapi_uri", echoOpenapiData).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/get_openapi_uri", shuffle.EchoOpenapiData).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/validate_openapi", shuffle.ValidateSwagger).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS")
|
||||
|
||||
@@ -5899,7 +5850,7 @@ func initHandlers() {
|
||||
// This is a new API that validates if a key has been seen before.
|
||||
// Not sure what the best course of action is for it.
|
||||
r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS")
|
||||
//r.HandleFunc("/api/v1/environments/{key}/rerun", shuffle.HandleRerunExecutions).Methods("GET", "POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/environments/{key}/rerun", shuffle.HandleRerunExecutions).Methods("GET", "POST", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS")
|
||||
|
||||
+69
-22
@@ -208,7 +208,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
|
||||
|
||||
err = shuffle.DeleteKeys(ctx, parsedId, ids)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed deleting %d execution keys for org %s", len(ids), id)
|
||||
log.Printf("[ERROR] Failed deleting %d execution keys for org %s: %s", len(ids), id, err)
|
||||
} else {
|
||||
//log.Printf("[INFO] Deleted %d keys from org %s", len(ids), parsedId)
|
||||
}
|
||||
@@ -366,7 +366,7 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
|
||||
ctx := context.Background()
|
||||
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
|
||||
if err != nil {
|
||||
log.Printf("Failed getting execution (streamresult) %s: %s", actionResult.ExecutionId, err)
|
||||
log.Printf("[WARNING] Failed getting execution (streamresult) %s: %s", actionResult.ExecutionId, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
|
||||
return
|
||||
@@ -412,7 +412,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
//log.Printf("Actionresult unmarshal: %s", string(body))
|
||||
log.Printf("[DEBUG] Got workflow result from %s of length %d", request.RemoteAddr, len(body))
|
||||
log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
|
||||
err = shuffle.ValidateNewWorkerExecution(body)
|
||||
if err == nil {
|
||||
resp.WriteHeader(200)
|
||||
@@ -494,7 +494,9 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
||||
err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed userinput handler: %s", err)
|
||||
actionResult.Result = fmt.Sprintf("Cloud error: %s", err)
|
||||
|
||||
actionResult.Result = fmt.Sprintf(`{"success": False, "reason": "%s"}`, err)
|
||||
|
||||
workflowExecution.Results = append(workflowExecution.Results, actionResult)
|
||||
workflowExecution.Status = "ABORTED"
|
||||
err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
|
||||
@@ -506,12 +508,13 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err)))
|
||||
return
|
||||
} else {
|
||||
log.Printf("[INFO] Successful userinput handler")
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`)))
|
||||
|
||||
actionResult.Result = "Waiting for user feedback based on configuration"
|
||||
actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}`
|
||||
|
||||
workflowExecution.Results = append(workflowExecution.Results, actionResult)
|
||||
workflowExecution.Status = actionResult.Status
|
||||
@@ -519,7 +522,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed setting userinput: %s", err)
|
||||
} else {
|
||||
log.Printf("Successfully set the execution to waiting.")
|
||||
log.Printf("[DEBUG] Successfully set the execution to waiting.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,7 +878,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf(`workflow %s is invalid`, workflow.ID), errors.New("Failed getting workflow")
|
||||
}
|
||||
|
||||
workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request)
|
||||
workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request, 10)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed in prepareExecution: %s", err)
|
||||
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed preparration: %s", err), err
|
||||
@@ -919,6 +922,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
|
||||
//}
|
||||
|
||||
//log.Printf("Execution request: %#v", executionRequest)
|
||||
executionRequest.Priority = workflowExecution.Priority
|
||||
err = shuffle.SetWorkflowQueue(ctx, executionRequest, environment)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed adding execution to db: %s", err)
|
||||
@@ -1035,13 +1039,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := shuffle.HandleApiAuthentication(resp, request)
|
||||
if err != nil {
|
||||
log.Printf("[INFO] Api authentication failed in execute workflow: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
user, userErr := shuffle.HandleApiAuthentication(resp, request)
|
||||
|
||||
if user.Role == "org-reader" {
|
||||
log.Printf("[WARNING] Org-reader doesn't have access to run workflow: %s (%s)", user.Username, user.Id)
|
||||
@@ -1079,14 +1077,38 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if user.Id != workflow.Owner && user.Role != "scheduler" && user.Role != fmt.Sprintf("workflow_%s", fileId) {
|
||||
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
log.Printf("[AUDIT] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID)
|
||||
} else {
|
||||
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID)
|
||||
executionAuthValid := false
|
||||
newOrgId := ""
|
||||
if userErr != nil {
|
||||
// Check if the execution data has correct info in it! Happens based on subflows.
|
||||
// 1. Parent workflow contains this workflow ID in the source trigger?
|
||||
// 2. Parent workflow's owner is same org?
|
||||
// 3. Parent execution auth is correct
|
||||
|
||||
executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow)
|
||||
if !executionAuthValid {
|
||||
log.Printf("[INFO] Api authentication failed in execute workflow: %s", userErr)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
} else {
|
||||
log.Printf("[DEBUG] Execution of %s successfully validated and started based on subflow or user input execution", workflow.ID)
|
||||
user.ActiveOrg = shuffle.OrgMini{
|
||||
Id: newOrgId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !executionAuthValid {
|
||||
if user.Id != workflow.Owner && user.Role != "scheduler" && user.Role != fmt.Sprintf("workflow_%s", fileId) {
|
||||
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
|
||||
log.Printf("[AUDIT] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID)
|
||||
} else {
|
||||
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2419,6 +2441,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId
|
||||
// E.g. check email
|
||||
sms := ""
|
||||
email := ""
|
||||
subflow := ""
|
||||
triggerType := ""
|
||||
triggerInformation := ""
|
||||
for _, item := range trigger.Parameters {
|
||||
@@ -2430,11 +2453,14 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId
|
||||
email = item.Value
|
||||
} else if item.Name == "sms" {
|
||||
sms = item.Value
|
||||
} else if item.Name == "subflow" {
|
||||
subflow = item.Value
|
||||
}
|
||||
}
|
||||
_ = subflow
|
||||
|
||||
if len(triggerType) == 0 {
|
||||
log.Printf("No type specified for user input node")
|
||||
log.Printf("[WARNING] No type specified for user input node")
|
||||
return errors.New("No type specified for user input node")
|
||||
}
|
||||
|
||||
@@ -2467,6 +2493,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId
|
||||
|
||||
log.Printf("[INFO] Should send email to %s during execution.", email)
|
||||
}
|
||||
|
||||
if strings.Contains(triggerType, "sms") {
|
||||
action := shuffle.CloudSyncJob{
|
||||
Type: "user_input",
|
||||
@@ -2491,7 +2518,11 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Should send SMS to %s during execution.", sms)
|
||||
log.Printf("[DEBUG] Should send SMS to %s during execution.", sms)
|
||||
}
|
||||
|
||||
if strings.Contains(triggerType, "subflow") {
|
||||
log.Printf("[DEBUG] Should run a subflow with the result for user input.")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -2547,6 +2578,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
workflowExecution.Priority = 10
|
||||
environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id)
|
||||
environment := "Shuffle"
|
||||
if len(environments) >= 1 {
|
||||
@@ -2567,6 +2599,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
|
||||
Environments: []string{environment},
|
||||
}
|
||||
|
||||
executionRequest.Priority = workflowExecution.Priority
|
||||
err = shuffle.SetWorkflowQueue(ctx, executionRequest, environment)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed adding execution to db: %s", err)
|
||||
@@ -2927,7 +2960,21 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
|
||||
for _, item := range buildLaterFirst {
|
||||
err = buildImageMemory(fs, item.Tags, item.Extra, true)
|
||||
if err != nil {
|
||||
log.Printf("Failed image build memory: %s", err)
|
||||
orgId := ""
|
||||
|
||||
log.Printf("[DEBUG] Failed image build memory. Creating notification with org %#v: %s", orgId, err)
|
||||
|
||||
if len(item.Tags) > 0 {
|
||||
err = shuffle.CreateOrgNotification(
|
||||
ctx,
|
||||
fmt.Sprintf("App failed to build"),
|
||||
fmt.Sprintf("The app %s with image %s failed to build. Check backend logs with docker! docker logs shuffle-backend", item.Tags[0], item.Extra),
|
||||
fmt.Sprintf("/apps"),
|
||||
orgId,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(item.Tags) > 0 {
|
||||
log.Printf("[INFO] Successfully built image %s", item.Tags[0])
|
||||
|
||||
+8
-6
@@ -16,7 +16,7 @@ services:
|
||||
depends_on:
|
||||
- backend
|
||||
backend:
|
||||
build: ./backend
|
||||
#build: ./backend
|
||||
image: ghcr.io/frikky/shuffle-backend:nightly
|
||||
container_name: shuffle-backend
|
||||
hostname: ${BACKEND_HOSTNAME}
|
||||
@@ -59,12 +59,12 @@ services:
|
||||
- HTTPS_PROXY=${HTTPS_PROXY}
|
||||
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
||||
- SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY}
|
||||
- SHUFFLE_SCALE_REPLICAS=5
|
||||
- SHUFFLE_SWARM_CONFIG=runn
|
||||
- SHUFFLE_SWARM_NETWORK_NAME=shuffle-executions
|
||||
- SHUFFLE_SWARM_NETWORK_NAME=shuffle_swarm_executions
|
||||
- SHUFFLE_SCALE_REPLICAS=1
|
||||
- SHUFFLE_SWARM_CONFIG=run
|
||||
restart: unless-stopped
|
||||
opensearch:
|
||||
image: opensearchproject/opensearch:1.1.0
|
||||
image: opensearchproject/opensearch:1.2.3
|
||||
hostname: shuffle-opensearch
|
||||
container_name: shuffle-opensearch
|
||||
environment:
|
||||
@@ -93,4 +93,6 @@ services:
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
shuffle:
|
||||
driver: bridge
|
||||
driver: overlay
|
||||
|
||||
#driver: bridge
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ RUN rm -rf /usr/src/app/node_modules/webpack
|
||||
RUN yarn build
|
||||
|
||||
# Production environment
|
||||
FROM nginx:1.21.3
|
||||
FROM nginx:1.21.5
|
||||
|
||||
RUN mkdir -p /usr/share/nginx/html/build
|
||||
RUN mkdir -p /usr/share/nginx/html/css
|
||||
|
||||
Generated
-20014
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.15.8",
|
||||
"@emotion/react": "^11.7.0",
|
||||
"@emotion/styled": "^11.6.0",
|
||||
"@material-ui/core": "^4.5.2",
|
||||
"@material-ui/data-grid": "^4.0.0-alpha.22",
|
||||
"@material-ui/icons": "^4.5.1",
|
||||
@@ -12,6 +14,8 @@
|
||||
"@material-ui/styles": "^4.5.2",
|
||||
"@material-ui/utils": "^4.11.2",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@mui/icons-material": "^5.2.1",
|
||||
"@mui/material": "^5.2.3",
|
||||
"@uiw/react-codemirror": "^3.2.1",
|
||||
"@use-it/interval": "^1.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
@@ -60,6 +64,7 @@
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-scripts": "^4.0.1",
|
||||
"react-shepherd": "^3.3.6",
|
||||
"reactstrap": "^7.1.0",
|
||||
"shellwords": "^0.1.1",
|
||||
"simplebar": "^4.2.3",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -33,6 +33,8 @@ import MyView from "./views/MyView";
|
||||
|
||||
import { createMuiTheme, MuiThemeProvider } from "@material-ui/core/styles";
|
||||
|
||||
import DetectionFramework from "./components/DetectionFramework.jsx";
|
||||
import FrameworkData from "./components/FrameworkData";
|
||||
import ScrollToTop from "./components/ScrollToTop";
|
||||
import AlertTemplate from "./components/AlertTemplate";
|
||||
import { useAlert, positions, Provider } from "react-alert";
|
||||
@@ -76,6 +78,7 @@ const App = (message, props) => {
|
||||
!isLoggedIn &&
|
||||
!window.location.pathname.startsWith("/login") &&
|
||||
!window.location.pathname.startsWith("/docs") &&
|
||||
!window.location.pathname.startsWith("/detectionframework") &&
|
||||
!window.location.pathname.startsWith("/adminsetup")
|
||||
) {
|
||||
window.location = "/login";
|
||||
@@ -133,6 +136,7 @@ const App = (message, props) => {
|
||||
}
|
||||
|
||||
// Handling Ethereum update
|
||||
{/*
|
||||
detectEthereumProvider().then((provider) => {
|
||||
if (
|
||||
provider &&
|
||||
@@ -245,6 +249,7 @@ const App = (message, props) => {
|
||||
userInfo.eth_info.parsed_balance =
|
||||
userInfo.eth_info.balance / 1000000000000000000;
|
||||
}
|
||||
*/}
|
||||
|
||||
//console.log("USER: ", userInfo)
|
||||
setUserData(userInfo);
|
||||
@@ -394,6 +399,22 @@ const App = (message, props) => {
|
||||
path="/schedules"
|
||||
render={(props) => <Schedules globalUrl={globalUrl} {...props} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/detectionframework"
|
||||
render={(props) => (
|
||||
<DetectionFramework
|
||||
frameworkData={FrameworkData}
|
||||
selectedOption={"Draw"}
|
||||
showOptions={false}
|
||||
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/dashboard"
|
||||
|
||||
@@ -164,7 +164,10 @@ const ConfigureWorkflow = (props) => {
|
||||
newaction.must_authenticate = true;
|
||||
newaction.action_ids.push(action.id);
|
||||
}
|
||||
}
|
||||
} else if (action.authentication_id !== "" && app.authentication.required === true) {
|
||||
console.log("Should verify authentication ID ", action.authentication_id)
|
||||
|
||||
}
|
||||
|
||||
newaction.app = app;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,942 @@
|
||||
import React, {useState } from 'react';
|
||||
|
||||
import { securityFramework } from "./LandingpageUsecases.jsx";
|
||||
import CytoscapeComponent from 'react-cytoscapejs';
|
||||
import frameworkStyle from '../frameworkStyle.jsx';
|
||||
import uuid from "uuid";
|
||||
|
||||
import {
|
||||
Button
|
||||
} from '@material-ui/core';
|
||||
|
||||
import * as edgehandles from "cytoscape-edgehandles";
|
||||
import * as cytoscape from "cytoscape";
|
||||
|
||||
cytoscape.use(edgehandles);
|
||||
|
||||
export const usecases = {
|
||||
"None": {
|
||||
"manual": [],
|
||||
"automated": [],
|
||||
},
|
||||
"Phishing": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "BOTTOM_LEFT",
|
||||
"target": "COMMS",
|
||||
"description": "Email received",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "COMMS",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "INTEL",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "ASSETS",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "INTEL",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "COMMS",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "CASES",
|
||||
"target": "EDR & AV",
|
||||
"human": true,
|
||||
},
|
||||
]},
|
||||
"Ransomware": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "BOTTOM_LEFT",
|
||||
"target": "EDR & AV",
|
||||
"description": "EDR & AV alert",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "EDR & AV",
|
||||
"target": "SHUFFLE",
|
||||
"description": "",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "EDR & AV",
|
||||
"human": false,
|
||||
"description": "isolate",
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "IAM",
|
||||
"human": false,
|
||||
"description": "Block access",
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "COMMS",
|
||||
"description": "Notify oncall and affected user",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"description": "Create enriched alert",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "CASES",
|
||||
"target": "EDR & AV",
|
||||
"description": "Validate alert",
|
||||
"human": true,
|
||||
},
|
||||
]
|
||||
},
|
||||
"Exploits": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "TOP_LEFT",
|
||||
"target": "NETWORK",
|
||||
"description": "Exploit",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "NETWORK",
|
||||
"target": "SIEM",
|
||||
"description": "WAF alert",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "COMMS",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "INTEL",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "ASSETS",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "IAM",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "CASES",
|
||||
"target": "EDR & AV",
|
||||
"human": true,
|
||||
},
|
||||
]
|
||||
},
|
||||
"AWS S3 honeypots": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "TOP_LEFT",
|
||||
"target": "SIEM",
|
||||
"description": "S3 logs",
|
||||
"human": false,
|
||||
},
|
||||
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "INTEL",
|
||||
"description": "Add sighting",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "INTEL",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"description": "Create case",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "NETWORK",
|
||||
"description": "Block IP",
|
||||
"human": false,
|
||||
},
|
||||
]
|
||||
},
|
||||
"SIEM alerts": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "TOP_LEFT",
|
||||
"target": "SIEM",
|
||||
"description": "Syslog",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Alerts",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "INTEL",
|
||||
"description": "Enrich",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "INTEL",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "IAM",
|
||||
"target": "SHUFFLE",
|
||||
"human": false,
|
||||
"description": "enrich",
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "IAM",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "COMMS",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "EDR & AV",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "EDR & AV",
|
||||
"human": true,
|
||||
},
|
||||
]
|
||||
},
|
||||
"New Detections": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "TOP_LEFT",
|
||||
"target": "SIEM",
|
||||
"description": "Hypothesis",
|
||||
"human": true,
|
||||
},
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "SIEM",
|
||||
"description": "Create rule",
|
||||
"human": true,
|
||||
},
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "NETWORK",
|
||||
"description": "Create rule",
|
||||
"human": true,
|
||||
},
|
||||
{
|
||||
"source": "NETWORK",
|
||||
"target": "EDR & AV",
|
||||
"description": "Create rule",
|
||||
"human": true,
|
||||
},
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Send alert",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "NETWORK",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Send alert",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "EDR & AV",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Send alert",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "INTEL",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Enrich IOCs",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "ASSETS",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Enrich hostnames etc.",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"description": "Create enriched alert",
|
||||
"human": false,
|
||||
},
|
||||
]
|
||||
},
|
||||
"Vulnerabilities": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "TOP_RIGHT",
|
||||
"target": "ASSETS",
|
||||
"description": "New vuln",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "ASSETS",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Get vuln",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"description": "Raise ticket",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "COMMS",
|
||||
"description": "Notify owner",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "COMMS",
|
||||
"target": "SHUFFLE",
|
||||
"description": "",
|
||||
"human": true,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "ASSETS",
|
||||
"description": "Auto-patch",
|
||||
"human": false,
|
||||
},
|
||||
]
|
||||
},
|
||||
"Approvals": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "TOP_LEFT",
|
||||
"target": "CASES",
|
||||
"description": "New inquiry",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "CASES",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Get tickets",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "COMMS",
|
||||
"description": "Ask for approval",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "COMMS",
|
||||
"target": "SHUFFLE",
|
||||
"human": true,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "ASSETS",
|
||||
"description": "Add to user",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "IAM",
|
||||
"description": "Approve access",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"human": false,
|
||||
},
|
||||
]
|
||||
},
|
||||
"Enrichment": {
|
||||
"manual": [],
|
||||
"automated": [
|
||||
{
|
||||
"source": "TOP_LEFT",
|
||||
"target": "CASES",
|
||||
"description": "Case updated",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"description": "Get and enrich ticket",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "IAM",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Get access rights",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "ASSETS",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Get relevant assets",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "INTEL",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Get relevant IPs",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "COMMS",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Find relevant mails & chats",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "EDR & AV",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Find incidents for host",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SIEM",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Find info about hostname and user",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "SHUFFLE",
|
||||
"description": "Format info",
|
||||
"human": false,
|
||||
},
|
||||
{
|
||||
"source": "SHUFFLE",
|
||||
"target": "CASES",
|
||||
"description": "",
|
||||
"human": false,
|
||||
},
|
||||
]
|
||||
},
|
||||
"Draw": {
|
||||
}
|
||||
}
|
||||
|
||||
const Framework = (props) => {
|
||||
const {globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, } = props;
|
||||
const [cy, setCy] = React.useState()
|
||||
const [edgesStarted, setEdgesStarted] = React.useState(false)
|
||||
|
||||
const parsedFrameworkData = frameworkData === undefined ?
|
||||
{
|
||||
"Cases": {},
|
||||
"IAM": {},
|
||||
"SIEM": {},
|
||||
"Assets": {},
|
||||
"Intel": {},
|
||||
"Comms": {},
|
||||
"Network": {},
|
||||
"EDR & AV": {},
|
||||
}
|
||||
:
|
||||
frameworkData
|
||||
|
||||
// 0 = automated, 1 = manual
|
||||
const [usecaseType, setUsecaseType] = React.useState(0)
|
||||
const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
|
||||
|
||||
|
||||
const elements = []
|
||||
const surfaceColor = "#27292D"
|
||||
|
||||
const onEdgeSelect = (event) => {
|
||||
console.log("Edge selected!")
|
||||
event.target.remove()
|
||||
}
|
||||
|
||||
const changeUsecase = (value, type) => {
|
||||
//console.log("Value: ", value)
|
||||
if (value === "Draw" && !edgesStarted) {
|
||||
setEdgesStarted(true)
|
||||
cy.edgehandles({
|
||||
handleNodes: (el) => {
|
||||
if (el.isNode() &&
|
||||
!el.data("isButton") &&
|
||||
!el.data("isDescriptor") &&
|
||||
el.data("type") !== "COMMENT") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
preview: false,
|
||||
toggleOffOnLeave: true,
|
||||
loopAllowed: function (node) {
|
||||
return false;
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
setSelectedUsecase(value)
|
||||
|
||||
const allEdges = cy.edges().jsons()
|
||||
for (var key in allEdges) {
|
||||
const newedge = allEdges[key]
|
||||
const foundelement = cy.getElementById(newedge.data.id)
|
||||
if (foundelement !== undefined && foundelement !== null) {
|
||||
foundelement.remove()
|
||||
}
|
||||
}
|
||||
|
||||
var found = false
|
||||
|
||||
var parsedType = type === 1 ? "manual" : "automated"
|
||||
if (usecases === undefined || usecases === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const newedges = usecases[value][parsedType]
|
||||
for (var key in newedges) {
|
||||
newedges[key].label = parseInt(key)+1
|
||||
|
||||
if (newedges[key].description !== undefined && newedges[key].description !== null && newedges[key].description.length > 0) {
|
||||
newedges[key].label = (parseInt(key)+1)+" "+newedges[key].description
|
||||
}
|
||||
|
||||
cy.add({
|
||||
group: "edges",
|
||||
data: newedges[key],
|
||||
})
|
||||
}
|
||||
|
||||
//var a = cy.edges().animate(
|
||||
//{
|
||||
// position: { x: 100, y: 100 },
|
||||
// style: { lineColor: '#a79' }
|
||||
//},
|
||||
//{
|
||||
// duration: 1000,
|
||||
// queue: true
|
||||
//})
|
||||
|
||||
//var b = cy.nodes().animate(
|
||||
// {
|
||||
// position: { x: 100, y: 100 },
|
||||
// style: { backgroundColor: 'blue' }
|
||||
// },
|
||||
// {
|
||||
// duration: 1000, // This goes together in one brace
|
||||
// queue: true // Use a boolean maybe
|
||||
// }
|
||||
//)
|
||||
//
|
||||
//a.animation().play().promise()
|
||||
//.then(() => {
|
||||
// b.animation().play()
|
||||
//})
|
||||
}
|
||||
|
||||
|
||||
if (cy !== undefined && cy.elements().length === 0) {
|
||||
//'background-image': 'data(small_image)',
|
||||
const shiftradius = 115
|
||||
const baselocationX = 285
|
||||
const baselocationY = 50
|
||||
const shiftmodifier = 3
|
||||
|
||||
const svgSize = 40
|
||||
|
||||
console.log("Framework: ", parsedFrameworkData)
|
||||
const defaultSize = "85px"
|
||||
const iconSize = "45px"
|
||||
const textMarginDefault = "14px"
|
||||
const textMarginImage = "60px"
|
||||
const nodes = [
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData.Cases.large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData.Cases.large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData.Cases.large_image === undefined ? '19px' : "50x",
|
||||
width: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData.Cases.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.6408 8.39233H18.0922V10.0287H15.6408V8.39233ZM0.115234 8.39233H2.56663V10.0287H0.115234V8.39233ZM9.92083 0.21051V2.66506H8.28656V0.21051H9.92083ZM3.31839 2.25596L5.05889 4.00687L3.89856 5.16051L2.15807 3.42596L3.31839 2.25596ZM13.1485 3.99869L14.8808 2.25596L16.0493 3.42596L14.3088 5.16051L13.1485 3.99869ZM9.10369 4.30142C10.404 4.30142 11.651 4.81863 12.5705 5.73926C13.4899 6.65989 14.0065 7.90854 14.0065 9.21051C14.0065 11.0269 13.0178 12.6141 11.5551 13.4651V14.9378C11.5551 15.1548 11.469 15.3629 11.3158 15.5163C11.1625 15.6698 10.9547 15.756 10.738 15.756H7.46943C7.25271 15.756 7.04487 15.6698 6.89163 15.5163C6.73839 15.3629 6.6523 15.1548 6.6523 14.9378V13.4651C5.18963 12.6141 4.2009 11.0269 4.2009 9.21051C4.2009 7.90854 4.71744 6.65989 5.63689 5.73926C6.55635 4.81863 7.80339 4.30142 9.10369 4.30142ZM10.738 16.5741V17.3923C10.738 17.6093 10.6519 17.8174 10.4986 17.9709C10.3454 18.1243 10.1375 18.2105 9.92083 18.2105H8.28656C8.06984 18.2105 7.862 18.1243 7.70876 17.9709C7.55552 17.8174 7.46943 17.6093 7.46943 17.3923V16.5741H10.738ZM8.28656 14.1196H9.92083V12.3769C11.3345 12.0169 12.3722 10.7323 12.3722 9.21051C12.3722 8.34253 12.0279 7.5101 11.4149 6.89634C10.8019 6.28259 9.97056 5.93778 9.10369 5.93778C8.23683 5.93778 7.40546 6.28259 6.79249 6.89634C6.17953 7.5101 5.83516 8.34253 5.83516 9.21051C5.83516 10.7323 6.87292 12.0169 8.28656 12.3769V14.1196Z" />
|
||||
</svg>`) : parsedFrameworkData.Cases.large_image,
|
||||
label: securityFramework[0].text.toUpperCase(),
|
||||
id: securityFramework[0].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX,
|
||||
y: baselocationY,
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData.IAM.large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData.IAM.large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData.IAM.large_image === undefined ? '19px' : "0px",
|
||||
width: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData.IAM.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3318 2.223C13.2598 2.223 13.1878 2.205 13.1248 2.169C11.3968 1.278 9.90284 0.9 8.11184 0.9C6.32984 0.9 4.63784 1.323 3.09884 2.169C2.88284 2.286 2.61284 2.205 2.48684 1.989C2.36984 1.773 2.45084 1.494 2.66684 1.377C4.34084 0.468 6.17684 0 8.11184 0C10.0288 0 11.7028 0.423 13.5388 1.368C13.7638 1.485 13.8448 1.755 13.7278 1.971C13.6468 2.133 13.4938 2.223 13.3318 2.223ZM0.452843 6.948C0.362843 6.948 0.272843 6.921 0.191843 6.867C-0.015157 6.723 -0.0601571 6.444 0.0838429 6.237C0.974843 4.977 2.10884 3.987 3.45884 3.294C6.28484 1.836 9.90284 1.827 12.7378 3.285C14.0878 3.978 15.2218 4.959 16.1128 6.21C16.2568 6.408 16.2118 6.696 16.0048 6.84C15.7978 6.984 15.5188 6.939 15.3748 6.732C14.5648 5.598 13.5388 4.707 12.3238 4.086C9.74084 2.763 6.43784 2.763 3.86384 4.095C2.63984 4.725 1.61384 5.625 0.803843 6.759C0.731843 6.885 0.596843 6.948 0.452843 6.948ZM6.07784 17.811C5.96084 17.811 5.84384 17.766 5.76284 17.676C4.97984 16.893 4.55684 16.389 3.95384 15.3C3.33284 14.193 3.00884 12.843 3.00884 11.394C3.00884 8.721 5.29484 6.543 8.10284 6.543C10.9108 6.543 13.1968 8.721 13.1968 11.394C13.1968 11.646 12.9988 11.844 12.7468 11.844C12.4948 11.844 12.2968 11.646 12.2968 11.394C12.2968 9.216 10.4158 7.443 8.10284 7.443C5.78984 7.443 3.90884 9.216 3.90884 11.394C3.90884 12.69 4.19684 13.887 4.74584 14.859C5.32184 15.894 5.71784 16.335 6.41084 17.037C6.58184 17.217 6.58184 17.496 6.41084 17.676C6.31184 17.766 6.19484 17.811 6.07784 17.811ZM12.5308 16.146C11.4598 16.146 10.5148 15.876 9.74084 15.345C8.39984 14.436 7.59884 12.96 7.59884 11.394C7.59884 11.142 7.79684 10.944 8.04884 10.944C8.30084 10.944 8.49884 11.142 8.49884 11.394C8.49884 12.663 9.14684 13.86 10.2448 14.598C10.8838 15.03 11.6308 15.237 12.5308 15.237C12.7468 15.237 13.1068 15.21 13.4668 15.147C13.7098 15.102 13.9438 15.264 13.9888 15.516C14.0338 15.759 13.8718 15.993 13.6198 16.038C13.1068 16.137 12.6568 16.146 12.5308 16.146ZM10.7218 18C10.6858 18 10.6408 17.991 10.6048 17.982C9.17384 17.586 8.23784 17.055 7.25684 16.092C5.99684 14.841 5.30384 13.176 5.30384 11.394C5.30384 9.936 6.54584 8.748 8.07584 8.748C9.60584 8.748 10.8478 9.936 10.8478 11.394C10.8478 12.357 11.6848 13.14 12.7198 13.14C13.7548 13.14 14.5918 12.357 14.5918 11.394C14.5918 8.001 11.6668 5.247 8.06684 5.247C5.51084 5.247 3.17084 6.669 2.11784 8.874C1.76684 9.603 1.58684 10.458 1.58684 11.394C1.58684 12.096 1.64984 13.203 2.18984 14.643C2.27984 14.877 2.16284 15.138 1.92884 15.219C1.69484 15.309 1.43384 15.183 1.35284 14.958C0.911843 13.779 0.695843 12.609 0.695843 11.394C0.695843 10.314 0.902843 9.333 1.30784 8.478C2.50484 5.967 5.15984 4.338 8.06684 4.338C12.1618 4.338 15.4918 7.497 15.4918 11.385C15.4918 12.843 14.2498 14.031 12.7198 14.031C11.1898 14.031 9.94784 12.843 9.94784 11.385C9.94784 10.422 9.11084 9.639 8.07584 9.639C7.04084 9.639 6.20384 10.422 6.20384 11.385C6.20384 12.924 6.79784 14.364 7.88684 15.444C8.74184 16.29 9.56084 16.758 10.8298 17.109C11.0728 17.172 11.2078 17.424 11.1448 17.658C11.0998 17.865 10.9108 18 10.7218 18Z" />,
|
||||
</svg>`) : parsedFrameworkData.IAM.large_image,
|
||||
label: securityFramework[3].text.toUpperCase(),
|
||||
id: securityFramework[3].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX+shiftradius+(shiftradius/shiftmodifier),
|
||||
y: baselocationY+shiftradius-(shiftradius/shiftmodifier),
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData.Assets.large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData.Assets.large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData.Assets.large_image === undefined ? '19px' : "0px",
|
||||
width: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData.Assets.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.223 10.971L3.85195 14.4L7.28095 7.029L14.652 3.6L11.223 10.971ZM9.25195 0C8.07006 0 6.89973 0.232792 5.8078 0.685084C4.71587 1.13738 3.72372 1.80031 2.88799 2.63604C1.20016 4.32387 0.251953 6.61305 0.251953 9C0.251953 11.3869 1.20016 13.6761 2.88799 15.364C3.72372 16.1997 4.71587 16.8626 5.8078 17.3149C6.89973 17.7672 8.07006 18 9.25195 18C11.6389 18 13.9281 17.0518 15.6159 15.364C17.3037 13.6761 18.252 11.3869 18.252 9C18.252 7.8181 18.0192 6.64778 17.5669 5.55585C17.1146 4.46392 16.4516 3.47177 15.6159 2.63604C14.7802 1.80031 13.788 1.13738 12.6961 0.685084C11.6042 0.232792 10.4338 0 9.25195 0ZM9.25195 8.01C8.98939 8.01 8.73758 8.1143 8.55192 8.29996C8.36626 8.48563 8.26195 8.73744 8.26195 9C8.26195 9.26256 8.36626 9.51437 8.55192 9.70004C8.73758 9.8857 8.98939 9.99 9.25195 9.99C9.51452 9.99 9.76633 9.8857 9.95199 9.70004C10.1376 9.51437 10.242 9.26256 10.242 9C10.242 8.73744 10.1376 8.48563 9.95199 8.29996C9.76633 8.1143 9.51452 8.01 9.25195 8.01Z" />,
|
||||
</svg>`) : parsedFrameworkData.Assets.large_image,
|
||||
label: securityFramework[2].text.toUpperCase(),
|
||||
id: securityFramework[2].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX+shiftradius*2,
|
||||
y: baselocationY+shiftradius*2,
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData.Intel.large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData.Intel.large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData.Intel.large_image === undefined ? '19px' : "0px",
|
||||
width: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData.Intel.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.1091 8.57143H14.8234V5.14286C14.8234 4.19143 14.052 3.42857 13.1091 3.42857H9.68052V2.14286C9.68052 1.57454 9.45476 1.02949 9.0529 0.627628C8.65103 0.225765 8.10599 0 7.53767 0C6.96935 0 6.4243 0.225765 6.02244 0.627628C5.62057 1.02949 5.39481 1.57454 5.39481 2.14286V3.42857H1.96624C1.51158 3.42857 1.07555 3.60918 0.754056 3.93067C0.432565 4.25216 0.251953 4.6882 0.251953 5.14286V8.4H1.53767C2.82338 8.4 3.85195 9.42857 3.85195 10.7143C3.85195 12 2.82338 13.0286 1.53767 13.0286H0.251953V16.2857C0.251953 16.7404 0.432565 17.1764 0.754056 17.4979C1.07555 17.8194 1.51158 18 1.96624 18H5.22338V16.7143C5.22338 15.4286 6.25195 14.4 7.53767 14.4C8.82338 14.4 9.85195 15.4286 9.85195 16.7143V18H13.1091C13.5638 18 13.9998 17.8194 14.3213 17.4979C14.6428 17.1764 14.8234 16.7404 14.8234 16.2857V12.8571H16.1091C16.6774 12.8571 17.2225 12.6314 17.6243 12.2295C18.0262 11.8277 18.252 11.2826 18.252 10.7143C18.252 10.146 18.0262 9.60092 17.6243 9.19906C17.2225 8.79719 16.6774 8.57143 16.1091 8.57143Z" />,
|
||||
</svg>`): parsedFrameworkData.Intel.large_image,
|
||||
label: securityFramework[4].text.toUpperCase(),
|
||||
id: securityFramework[4].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX+shiftradius+(shiftradius/shiftmodifier),
|
||||
y: baselocationY+shiftradius*3+(shiftradius/shiftmodifier),
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData.Comms.large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData.Comms.large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData.Comms.large_image === undefined ? '19px' : "0px",
|
||||
width: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData.Comms.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.89516 7.71433H8.60945V5.1429H9.89516V7.71433ZM9.89516 10.2858H8.60945V9.00004H9.89516V10.2858ZM14.3952 2.57147H4.10944C3.76845 2.57147 3.44143 2.70693 3.20031 2.94805C2.95919 3.18917 2.82373 3.51619 2.82373 3.85719V15.4286L5.39516 12.8572H14.3952C14.7362 12.8572 15.0632 12.7217 15.3043 12.4806C15.5454 12.2395 15.6809 11.9125 15.6809 11.5715V3.85719C15.6809 3.14361 15.1023 2.57147 14.3952 2.57147Z" />,
|
||||
</svg>`) : parsedFrameworkData.Comms.large_image,
|
||||
label: securityFramework[5].text.toUpperCase(),
|
||||
id: securityFramework[5].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX,
|
||||
y: baselocationY+shiftradius*4,
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData["EDR & AV"].large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? '19px' : "0px",
|
||||
width: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData["EDR & AV"].large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.1722 8.9957L17.0737 6.60487L17.3661 3.44004L14.2615 2.73483L12.6361 -3.28068e-08L9.71206 1.25561L6.78803 -3.28068e-08L5.16261 2.73483L2.05797 3.43144L2.35038 6.59627L0.251953 8.9957L2.35038 11.3865L2.05797 14.56L5.16261 15.2652L6.78803 18L9.71206 16.7358L12.6361 17.9914L14.2615 15.2566L17.3661 14.5514L17.0737 11.3865L19.1722 8.9957ZM10.5721 13.2957H8.85205V11.5757H10.5721V13.2957ZM10.5721 9.85571H8.85205V4.69565H10.5721V9.85571Z" />,
|
||||
</svg>`) : parsedFrameworkData["EDR & AV"].large_image,
|
||||
label: securityFramework[7].text.toUpperCase(),
|
||||
id: securityFramework[7].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX-shiftradius-(shiftradius/shiftmodifier),
|
||||
y: baselocationY+shiftradius*3+(shiftradius/shiftmodifier),
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData.Network.large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData.Network.large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData.Network.large_image === undefined ? '19px' : "0px",
|
||||
width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData.Network.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.251953 10.6011H3.8391L9.38052 -4.92572e-08L10.8977 11.5696L15.0377 6.28838L19.3191 10.6011H23.3948V13.1836H18.252L15.2562 10.175L9.1491 18L7.88909 8.41894L5.39481 13.1836H0.251953V10.6011Z" />,
|
||||
</svg>`) : parsedFrameworkData.Network.large_image,
|
||||
label: securityFramework[6].text.toUpperCase(),
|
||||
id: securityFramework[6].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX-shiftradius*2,
|
||||
y: baselocationY+shiftradius*2,
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
text_margin_y: parsedFrameworkData.SIEM.large_image === undefined ? textMarginDefault : textMarginImage,
|
||||
margin_x: parsedFrameworkData.SIEM.large_image === undefined ? '32px' : "0px",
|
||||
margin_y: parsedFrameworkData.SIEM.large_image === undefined ? '19px' : "0px",
|
||||
width: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
|
||||
height: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
|
||||
large_image: parsedFrameworkData.SIEM.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" />,
|
||||
</svg>`) : parsedFrameworkData.SIEM.large_image,
|
||||
label: securityFramework[1].text.toUpperCase(),
|
||||
id: securityFramework[1].text.toUpperCase(),
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX-shiftradius-(shiftradius/shiftmodifier),
|
||||
y: baselocationY+shiftradius-(shiftradius/shiftmodifier),
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
// Middlenode
|
||||
nodes.push({
|
||||
group: "nodes",
|
||||
data: {
|
||||
id: "SHUFFLE",
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
middle_node: true,
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX,
|
||||
y: baselocationY+shiftradius*2,
|
||||
}
|
||||
})
|
||||
|
||||
// Extra nodes
|
||||
nodes.push({
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
large_image: encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" />,
|
||||
</svg>`),
|
||||
id: "TOP_LEFT",
|
||||
invisible: true,
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX-shiftradius*2.5,
|
||||
y: baselocationY-50,
|
||||
}
|
||||
})
|
||||
nodes.push({
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
large_image: encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" />,
|
||||
</svg>`),
|
||||
id: "BOTTOM_LEFT",
|
||||
invisible: true,
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX-shiftradius*2.5-10,
|
||||
y: baselocationY+shiftradius*4-10,
|
||||
}
|
||||
})
|
||||
nodes.push({
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
large_image: encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" />,
|
||||
</svg>`),
|
||||
id: "BOTTOM_RIGHT",
|
||||
invisible: true,
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX+shiftradius*2+50,
|
||||
y: baselocationY+shiftradius*4+50,
|
||||
}
|
||||
})
|
||||
nodes.push({
|
||||
group: "nodes",
|
||||
data: {
|
||||
is_valid: true,
|
||||
isValid: true,
|
||||
errors: [],
|
||||
large_image: encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" />,
|
||||
</svg>`),
|
||||
id: "TOP_RIGHT",
|
||||
invisible: true,
|
||||
},
|
||||
renderedPosition: {
|
||||
x: baselocationX+shiftradius*2,
|
||||
y: baselocationY-150,
|
||||
}
|
||||
})
|
||||
|
||||
console.log("NODES: " , nodes)
|
||||
for (var key in nodes) {
|
||||
cy.add(nodes[key]).lock()
|
||||
}
|
||||
|
||||
cy.on("select", "edge", (e) => onEdgeSelect(e));
|
||||
changeUsecase(selectedUsecase, usecaseType)
|
||||
}
|
||||
|
||||
if (selectedOption !== undefined && selectedUsecase !== selectedOption) {
|
||||
setSelectedUsecase(selectedOption)
|
||||
changeUsecase(selectedOption, usecaseType)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{margin: "auto", }}>
|
||||
{showOptions === false ? null :
|
||||
<div style={{textAlign: "center",}}>
|
||||
{Object.keys(usecases).map((data, index) => {
|
||||
return(
|
||||
<Button key={index} color="primary" variant={selectedUsecase === data ? "contained" : "outlined"} style={{margin: 5, }} onClick={() => {
|
||||
changeUsecase(data, usecaseType)
|
||||
}}>
|
||||
{data}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
<CytoscapeComponent
|
||||
elements={elements}
|
||||
minZoom={0.35}
|
||||
maxZoom={2.00}
|
||||
style={{width: 560, height: 560, backgroundColor: "transparent", margin: "auto",}}
|
||||
stylesheet={frameworkStyle}
|
||||
boxSelectionEnabled={false}
|
||||
autounselectify={true}
|
||||
panningEnabled={false}
|
||||
userPanningEnabled={false}
|
||||
showGrid={false}
|
||||
id="cytoscape_view"
|
||||
cy={(incy) => {
|
||||
// FIXME: There's something specific loading when
|
||||
// you do the first hover of a node. Why is this different?
|
||||
//console.log("CY: ", incy)
|
||||
setCy(incy)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Framework;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React, {useState} from 'react';
|
||||
|
||||
|
||||
const FAQItem = (props) => {
|
||||
const { question, answer } = props
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<Paper onClick={() => {
|
||||
setIsExpanded(!isExpanded)
|
||||
}}>
|
||||
<Typography variant="body1">
|
||||
{question}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{answer}
|
||||
</Typography>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
export default FAQItem;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,245 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {isMobile} from "react-device-detect";
|
||||
import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
|
||||
import {Link} from 'react-router-dom';
|
||||
import ReactGA from 'react-ga';
|
||||
|
||||
import { Button, LinearProgress, Typography } from '@material-ui/core';
|
||||
|
||||
export const securityFramework = [
|
||||
{
|
||||
image: <path d="M15.6408 8.39233H18.0922V10.0287H15.6408V8.39233ZM0.115234 8.39233H2.56663V10.0287H0.115234V8.39233ZM9.92083 0.21051V2.66506H8.28656V0.21051H9.92083ZM3.31839 2.25596L5.05889 4.00687L3.89856 5.16051L2.15807 3.42596L3.31839 2.25596ZM13.1485 3.99869L14.8808 2.25596L16.0493 3.42596L14.3088 5.16051L13.1485 3.99869ZM9.10369 4.30142C10.404 4.30142 11.651 4.81863 12.5705 5.73926C13.4899 6.65989 14.0065 7.90854 14.0065 9.21051C14.0065 11.0269 13.0178 12.6141 11.5551 13.4651V14.9378C11.5551 15.1548 11.469 15.3629 11.3158 15.5163C11.1625 15.6698 10.9547 15.756 10.738 15.756H7.46943C7.25271 15.756 7.04487 15.6698 6.89163 15.5163C6.73839 15.3629 6.6523 15.1548 6.6523 14.9378V13.4651C5.18963 12.6141 4.2009 11.0269 4.2009 9.21051C4.2009 7.90854 4.71744 6.65989 5.63689 5.73926C6.55635 4.81863 7.80339 4.30142 9.10369 4.30142ZM10.738 16.5741V17.3923C10.738 17.6093 10.6519 17.8174 10.4986 17.9709C10.3454 18.1243 10.1375 18.2105 9.92083 18.2105H8.28656C8.06984 18.2105 7.862 18.1243 7.70876 17.9709C7.55552 17.8174 7.46943 17.6093 7.46943 17.3923V16.5741H10.738ZM8.28656 14.1196H9.92083V12.3769C11.3345 12.0169 12.3722 10.7323 12.3722 9.21051C12.3722 8.34253 12.0279 7.5101 11.4149 6.89634C10.8019 6.28259 9.97056 5.93778 9.10369 5.93778C8.23683 5.93778 7.40546 6.28259 6.79249 6.89634C6.17953 7.5101 5.83516 8.34253 5.83516 9.21051C5.83516 10.7323 6.87292 12.0169 8.28656 12.3769V14.1196Z" />,
|
||||
text: "Cases",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" />,
|
||||
text: "SIEM",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M11.223 10.971L3.85195 14.4L7.28095 7.029L14.652 3.6L11.223 10.971ZM9.25195 0C8.07006 0 6.89973 0.232792 5.8078 0.685084C4.71587 1.13738 3.72372 1.80031 2.88799 2.63604C1.20016 4.32387 0.251953 6.61305 0.251953 9C0.251953 11.3869 1.20016 13.6761 2.88799 15.364C3.72372 16.1997 4.71587 16.8626 5.8078 17.3149C6.89973 17.7672 8.07006 18 9.25195 18C11.6389 18 13.9281 17.0518 15.6159 15.364C17.3037 13.6761 18.252 11.3869 18.252 9C18.252 7.8181 18.0192 6.64778 17.5669 5.55585C17.1146 4.46392 16.4516 3.47177 15.6159 2.63604C14.7802 1.80031 13.788 1.13738 12.6961 0.685084C11.6042 0.232792 10.4338 0 9.25195 0ZM9.25195 8.01C8.98939 8.01 8.73758 8.1143 8.55192 8.29996C8.36626 8.48563 8.26195 8.73744 8.26195 9C8.26195 9.26256 8.36626 9.51437 8.55192 9.70004C8.73758 9.8857 8.98939 9.99 9.25195 9.99C9.51452 9.99 9.76633 9.8857 9.95199 9.70004C10.1376 9.51437 10.242 9.26256 10.242 9C10.242 8.73744 10.1376 8.48563 9.95199 8.29996C9.76633 8.1143 9.51452 8.01 9.25195 8.01Z" />,
|
||||
text: "Assets",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M13.3318 2.223C13.2598 2.223 13.1878 2.205 13.1248 2.169C11.3968 1.278 9.90284 0.9 8.11184 0.9C6.32984 0.9 4.63784 1.323 3.09884 2.169C2.88284 2.286 2.61284 2.205 2.48684 1.989C2.36984 1.773 2.45084 1.494 2.66684 1.377C4.34084 0.468 6.17684 0 8.11184 0C10.0288 0 11.7028 0.423 13.5388 1.368C13.7638 1.485 13.8448 1.755 13.7278 1.971C13.6468 2.133 13.4938 2.223 13.3318 2.223ZM0.452843 6.948C0.362843 6.948 0.272843 6.921 0.191843 6.867C-0.015157 6.723 -0.0601571 6.444 0.0838429 6.237C0.974843 4.977 2.10884 3.987 3.45884 3.294C6.28484 1.836 9.90284 1.827 12.7378 3.285C14.0878 3.978 15.2218 4.959 16.1128 6.21C16.2568 6.408 16.2118 6.696 16.0048 6.84C15.7978 6.984 15.5188 6.939 15.3748 6.732C14.5648 5.598 13.5388 4.707 12.3238 4.086C9.74084 2.763 6.43784 2.763 3.86384 4.095C2.63984 4.725 1.61384 5.625 0.803843 6.759C0.731843 6.885 0.596843 6.948 0.452843 6.948ZM6.07784 17.811C5.96084 17.811 5.84384 17.766 5.76284 17.676C4.97984 16.893 4.55684 16.389 3.95384 15.3C3.33284 14.193 3.00884 12.843 3.00884 11.394C3.00884 8.721 5.29484 6.543 8.10284 6.543C10.9108 6.543 13.1968 8.721 13.1968 11.394C13.1968 11.646 12.9988 11.844 12.7468 11.844C12.4948 11.844 12.2968 11.646 12.2968 11.394C12.2968 9.216 10.4158 7.443 8.10284 7.443C5.78984 7.443 3.90884 9.216 3.90884 11.394C3.90884 12.69 4.19684 13.887 4.74584 14.859C5.32184 15.894 5.71784 16.335 6.41084 17.037C6.58184 17.217 6.58184 17.496 6.41084 17.676C6.31184 17.766 6.19484 17.811 6.07784 17.811ZM12.5308 16.146C11.4598 16.146 10.5148 15.876 9.74084 15.345C8.39984 14.436 7.59884 12.96 7.59884 11.394C7.59884 11.142 7.79684 10.944 8.04884 10.944C8.30084 10.944 8.49884 11.142 8.49884 11.394C8.49884 12.663 9.14684 13.86 10.2448 14.598C10.8838 15.03 11.6308 15.237 12.5308 15.237C12.7468 15.237 13.1068 15.21 13.4668 15.147C13.7098 15.102 13.9438 15.264 13.9888 15.516C14.0338 15.759 13.8718 15.993 13.6198 16.038C13.1068 16.137 12.6568 16.146 12.5308 16.146ZM10.7218 18C10.6858 18 10.6408 17.991 10.6048 17.982C9.17384 17.586 8.23784 17.055 7.25684 16.092C5.99684 14.841 5.30384 13.176 5.30384 11.394C5.30384 9.936 6.54584 8.748 8.07584 8.748C9.60584 8.748 10.8478 9.936 10.8478 11.394C10.8478 12.357 11.6848 13.14 12.7198 13.14C13.7548 13.14 14.5918 12.357 14.5918 11.394C14.5918 8.001 11.6668 5.247 8.06684 5.247C5.51084 5.247 3.17084 6.669 2.11784 8.874C1.76684 9.603 1.58684 10.458 1.58684 11.394C1.58684 12.096 1.64984 13.203 2.18984 14.643C2.27984 14.877 2.16284 15.138 1.92884 15.219C1.69484 15.309 1.43384 15.183 1.35284 14.958C0.911843 13.779 0.695843 12.609 0.695843 11.394C0.695843 10.314 0.902843 9.333 1.30784 8.478C2.50484 5.967 5.15984 4.338 8.06684 4.338C12.1618 4.338 15.4918 7.497 15.4918 11.385C15.4918 12.843 14.2498 14.031 12.7198 14.031C11.1898 14.031 9.94784 12.843 9.94784 11.385C9.94784 10.422 9.11084 9.639 8.07584 9.639C7.04084 9.639 6.20384 10.422 6.20384 11.385C6.20384 12.924 6.79784 14.364 7.88684 15.444C8.74184 16.29 9.56084 16.758 10.8298 17.109C11.0728 17.172 11.2078 17.424 11.1448 17.658C11.0998 17.865 10.9108 18 10.7218 18Z" />,
|
||||
text: "IAM",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image: <path d="M16.1091 8.57143H14.8234V5.14286C14.8234 4.19143 14.052 3.42857 13.1091 3.42857H9.68052V2.14286C9.68052 1.57454 9.45476 1.02949 9.0529 0.627628C8.65103 0.225765 8.10599 0 7.53767 0C6.96935 0 6.4243 0.225765 6.02244 0.627628C5.62057 1.02949 5.39481 1.57454 5.39481 2.14286V3.42857H1.96624C1.51158 3.42857 1.07555 3.60918 0.754056 3.93067C0.432565 4.25216 0.251953 4.6882 0.251953 5.14286V8.4H1.53767C2.82338 8.4 3.85195 9.42857 3.85195 10.7143C3.85195 12 2.82338 13.0286 1.53767 13.0286H0.251953V16.2857C0.251953 16.7404 0.432565 17.1764 0.754056 17.4979C1.07555 17.8194 1.51158 18 1.96624 18H5.22338V16.7143C5.22338 15.4286 6.25195 14.4 7.53767 14.4C8.82338 14.4 9.85195 15.4286 9.85195 16.7143V18H13.1091C13.5638 18 13.9998 17.8194 14.3213 17.4979C14.6428 17.1764 14.8234 16.7404 14.8234 16.2857V12.8571H16.1091C16.6774 12.8571 17.2225 12.6314 17.6243 12.2295C18.0262 11.8277 18.252 11.2826 18.252 10.7143C18.252 10.146 18.0262 9.60092 17.6243 9.19906C17.2225 8.79719 16.6774 8.57143 16.1091 8.57143Z" />,
|
||||
text: "Intel",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M9.89516 7.71433H8.60945V5.1429H9.89516V7.71433ZM9.89516 10.2858H8.60945V9.00004H9.89516V10.2858ZM14.3952 2.57147H4.10944C3.76845 2.57147 3.44143 2.70693 3.20031 2.94805C2.95919 3.18917 2.82373 3.51619 2.82373 3.85719V15.4286L5.39516 12.8572H14.3952C14.7362 12.8572 15.0632 12.7217 15.3043 12.4806C15.5454 12.2395 15.6809 11.9125 15.6809 11.5715V3.85719C15.6809 3.14361 15.1023 2.57147 14.3952 2.57147Z" />,
|
||||
text: "Comms",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M0.251953 10.6011H3.8391L9.38052 -4.92572e-08L10.8977 11.5696L15.0377 6.28838L19.3191 10.6011H23.3948V13.1836H18.252L15.2562 10.175L9.1491 18L7.88909 8.41894L5.39481 13.1836H0.251953V10.6011Z" />,
|
||||
text: "Network",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M19.1722 8.9957L17.0737 6.60487L17.3661 3.44004L14.2615 2.73483L12.6361 -3.28068e-08L9.71206 1.25561L6.78803 -3.28068e-08L5.16261 2.73483L2.05797 3.43144L2.35038 6.59627L0.251953 8.9957L2.35038 11.3865L2.05797 14.56L5.16261 15.2652L6.78803 18L9.71206 16.7358L12.6361 17.9914L14.2615 15.2566L17.3661 14.5514L17.0737 11.3865L19.1722 8.9957ZM10.5721 13.2957H8.85205V11.5757H10.5721V13.2957ZM10.5721 9.85571H8.85205V4.69565H10.5721V9.85571Z" />,
|
||||
text: "EDR & AV",
|
||||
description: "Case management"
|
||||
},
|
||||
]
|
||||
|
||||
const LandingpageUsecases = (props) => {
|
||||
const [selectedUsecase, setSelectedUsecase] = useState("Phishing")
|
||||
const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases)
|
||||
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
|
||||
const buttonStyle = {borderRadius: 25, height: 50, width: 260, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18, backgroundImage: buttonBackground}
|
||||
|
||||
const HandleTitle = (props) => {
|
||||
const { usecases, selectedUsecase, setSelecedUsecase } = props
|
||||
const [progress, setProgress] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setProgress((oldProgress) => {
|
||||
if (oldProgress >= 105) {
|
||||
const foundIndex = usecasekeys.findIndex(key => key === selectedUsecase)
|
||||
var newitem = usecasekeys[foundIndex+1]
|
||||
if (newitem === undefined || newitem === 0) {
|
||||
newitem = usecasekeys[1]
|
||||
}
|
||||
|
||||
setSelectedUsecase(newitem)
|
||||
return -18
|
||||
}
|
||||
|
||||
if (oldProgress >= 65) {
|
||||
return oldProgress + 3
|
||||
}
|
||||
|
||||
if (oldProgress >= 80) {
|
||||
return oldProgress + 1
|
||||
}
|
||||
|
||||
return oldProgress + 6
|
||||
})
|
||||
}, 165)
|
||||
|
||||
return () => {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (usecases === null || usecases === undefined || usecases.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const modifier = isMobile ? 17 : 22
|
||||
return (
|
||||
<span style={{margin: "auto", textAlign: isMobile ? "center" : "left", width: isMobile ? 280 : "100%",}}>
|
||||
<b>Handle <br/>
|
||||
<span style={{marginBottom: 10}}>
|
||||
<i id="usecase-text">{selectedUsecase}</i>
|
||||
<LinearProgress variant="determinate" value={progress} style={{marginTop: 0, marginBottom: 0, height: 3, width: isMobile ? "100%" : selectedUsecase.length*modifier, borderRadius: 10, }} />
|
||||
</span>
|
||||
with confidence</b>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const parsedWidth = isMobile ? "100%" : 1100
|
||||
return (
|
||||
<div style={{width: isMobile ? null : parsedWidth, margin: isMobile ? "0px 0px 0px 0px" : "auto", color: "white", textAlign: isMobile ? "center" : "left",}}>
|
||||
<div style={{display: "flex", position: "relative",}}>
|
||||
<div style={{maxWidth: isMobile ? "100%" : 420, paddingTop: isMobile ? 0 : 120, zIndex: 1000, margin: "auto",}}>
|
||||
|
||||
<Typography variant="h1" style={{margin: "auto", width: isMobile ? 280 : "100%", marginTop: isMobile ? 50 : 0}}>
|
||||
<HandleTitle usecases={usecases} selectedUsecase={selectedUsecase} setSelectedUsecase={setSelectedUsecase} />
|
||||
|
||||
{/*<b>Security Automation <i>is Hard</i></b>*/}
|
||||
</Typography>
|
||||
<Typography variant="h6" style={{marginTop: isMobile ? 15 : 0,}}>
|
||||
Connecting your everchanging environment is hard. We get it! That's why we built Shuffle, where you can use and share your security workflows to everyones benefit.
|
||||
{/*Shuffle is an automation platform where you don't need to be an expert to automate. Get access to our large pool of security playbooks, apps and people.*/}
|
||||
</Typography>
|
||||
<div style={{display: "flex", textAlign: "center", itemAlign: "center",}}>
|
||||
{isMobile ? null :
|
||||
<Link rel="noopener noreferrer" to={"/pricing"} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_pricing",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground, marginRight: 10,
|
||||
}}>
|
||||
See Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
{isMobile ? null :
|
||||
<Link rel="noopener noreferrer" to={"/register?message=You'll need to sign up first. No name, company or credit card required."} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_try_it_out",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground,
|
||||
}}>
|
||||
Start for free
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{isMobile ? null :
|
||||
<div style={{marginLeft: 200, marginTop: 125, zIndex: 1000}}>
|
||||
<DetectionFramework showOptions={false} selectedOption={selectedUsecase} rolling={true} />
|
||||
</div>
|
||||
}
|
||||
{isMobile ? null :
|
||||
<div style={{position: "absolute", top: 50, right: -200, zIndex: 0, }}>
|
||||
<svg width="351" height="433" viewBox="0 0 351 433" fill="none" xmlns="http://www.w3.org/2000/svg" style={{zIndex: 0, }}>
|
||||
<path d="M167.781 184.839C167.781 235.244 208.625 276.104 259.03 276.104C309.421 276.104 350.28 235.244 350.28 184.839C350.28 134.448 309.421 93.5892 259.03 93.5892C208.625 93.5741 167.781 134.433 167.781 184.839ZM330.387 184.839C330.387 224.263 298.439 256.195 259.03 256.195C219.621 256.195 187.674 224.248 187.674 184.839C187.674 145.43 219.636 113.483 259.03 113.483C298.439 113.483 330.387 145.43 330.387 184.839Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M167.781 387.368C167.781 412.578 188.203 433 213.398 433C238.593 433 259.03 412.578 259.03 387.368C259.03 362.157 238.608 341.735 213.398 341.735C188.187 341.735 167.781 362.172 167.781 387.368ZM249.076 387.368C249.076 407.08 233.095 423.046 213.398 423.046C193.686 423.046 177.72 407.065 177.72 387.368C177.72 367.671 193.686 351.69 213.398 351.69C233.095 351.705 249.076 367.671 249.076 387.368Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M56.8637 0.738726C25.7052 0.738724 0.44632 25.9976 0.446317 57.1561C0.446314 88.3146 25.7052 113.573 56.8637 113.573C88.0221 113.573 113.281 88.3146 113.281 57.1561C113.281 25.9977 88.0222 0.738729 56.8637 0.738726Z" fill="white" fill-opacity="0.2"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div style={{display: "flex", width: isMobile ? "100%" : 300, itemAlign: "center", margin: "auto", marginTop: 20, flexDirection: isMobile ? "column" : "row", textAlign: "center",}}>
|
||||
{isMobile ?
|
||||
<Link rel="noopener noreferrer" to={"/pricing"} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant={isMobile ? "contained" : "outlined"}
|
||||
color={isMobile ? "primary" : "secondary"}
|
||||
style={buttonStyle}
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_pricing",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
>
|
||||
See pricing
|
||||
</Button>
|
||||
</Link>
|
||||
: null
|
||||
}
|
||||
{/*isMobile ?
|
||||
<Link rel="noopener noreferrer" to={"/docs/features"} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_features",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
color="secondary"
|
||||
style={buttonStyle}>
|
||||
Features
|
||||
</Button>
|
||||
</Link>
|
||||
: null*/}
|
||||
</div>
|
||||
{isMobile ? null :
|
||||
<div style={{display: "flex", width: parsedWidth, margin: "auto", marginTop: 150}}>
|
||||
{securityFramework.map((data, index) => {
|
||||
return (
|
||||
<div key={index} style={{flex: 1, textAlign: "center",}}>
|
||||
<span style={{margin: "auto", width: 25,}}>
|
||||
<svg width="25" height="25" fill="white" xmlns="http://www.w3.org/2000/svg" >
|
||||
{data.image}
|
||||
</svg>
|
||||
</span>
|
||||
<Typography variant="body2" style={{color: "white", marginRight: 5}}>
|
||||
{data.text}
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingpageUsecases;
|
||||
@@ -311,10 +311,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<span style={{}}>
|
||||
<b>
|
||||
Oauth2 requires a client ID and secret to authenticate. This is
|
||||
usually made in the remote system.
|
||||
</b>
|
||||
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. Your redirect URL is <b>https://shuffler.io/set_authentication</b>.
|
||||
<a
|
||||
target="_blank"
|
||||
rel="norefferer"
|
||||
@@ -452,32 +449,6 @@ const AuthenticationOauth2 = (props) => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{allscopes.length === 0 ? null : (
|
||||
<Select
|
||||
multiple
|
||||
value={selectedScopes}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
}}
|
||||
onChange={(e) => {
|
||||
handleScopeChange(e);
|
||||
}}
|
||||
fullWidth
|
||||
input={<Input id="select-multiple-native" />}
|
||||
renderValue={(selected) => selected.join(", ")}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{allscopes.map((data, index) => {
|
||||
return (
|
||||
<MenuItem key={index} value={data}>
|
||||
<Checkbox checked={selectedScopes.indexOf(data) > -1} />
|
||||
<ListItemText primary={data} />
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
)}
|
||||
<TextField
|
||||
style={{
|
||||
marginTop: 20,
|
||||
@@ -489,8 +460,8 @@ const AuthenticationOauth2 = (props) => {
|
||||
color: "white",
|
||||
marginLeft: "5px",
|
||||
maxWidth: "95%",
|
||||
height: 50,
|
||||
fontSize: "1em",
|
||||
height: "50px",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
@@ -505,14 +476,15 @@ const AuthenticationOauth2 = (props) => {
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
marginBottom: 10,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
marginLeft: "5px",
|
||||
maxWidth: "95%",
|
||||
height: 50,
|
||||
fontSize: "1em",
|
||||
height: "50px",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
@@ -523,6 +495,36 @@ const AuthenticationOauth2 = (props) => {
|
||||
//authenticationOption.label = event.target.value
|
||||
}}
|
||||
/>
|
||||
{allscopes.length === 0 ? null : (
|
||||
<span style={{marginTop: 10}}>
|
||||
Scopes
|
||||
<Select
|
||||
multiple
|
||||
value={selectedScopes}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
padding: 5,
|
||||
}}
|
||||
onChange={(e) => {
|
||||
handleScopeChange(e);
|
||||
}}
|
||||
fullWidth
|
||||
input={<Input id="select-multiple-native" />}
|
||||
renderValue={(selected) => selected.join(", ")}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{allscopes.map((data, index) => {
|
||||
return (
|
||||
<MenuItem key={index} value={data}>
|
||||
<Checkbox checked={selectedScopes.indexOf(data) > -1} />
|
||||
<ListItemText primary={data} />
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
@@ -532,7 +534,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
disabled={
|
||||
clientSecret.length === 0 || clientId.length === 0 || buttonClicked
|
||||
clientSecret.length === 0 || clientId.length === 0 || buttonClicked || selectedScopes.length === 0
|
||||
}
|
||||
variant="contained"
|
||||
fullWidth
|
||||
|
||||
@@ -6,6 +6,7 @@ import { GetIconInfo } from "../views/Workflows.jsx";
|
||||
import { sortByKey } from "../views/AngularWorkflow.jsx";
|
||||
import { useTheme } from "@material-ui/core/styles";
|
||||
import NestedMenuItem from "material-ui-nested-menu-item";
|
||||
import theme from '../theme';
|
||||
//import NestedMenuItem from "./NestedMenu.jsx";
|
||||
|
||||
import {
|
||||
@@ -80,7 +81,10 @@ import {
|
||||
LockOpen as LockOpenIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
VpnKey as VpnKeyIcon,
|
||||
} from "@material-ui/icons";
|
||||
AutoFixHigh as AutoFixHighIcon,
|
||||
} from '@mui/icons-material';
|
||||
//} from "@material-ui/icons";
|
||||
|
||||
import Autocomplete from "@material-ui/lab/Autocomplete";
|
||||
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
@@ -159,7 +163,7 @@ const ParsedAction = (props) => {
|
||||
getAppAuthentication,
|
||||
} = props;
|
||||
|
||||
const theme = useTheme();
|
||||
//const theme = useTheme();
|
||||
const classes = useStyles();
|
||||
|
||||
const [expansionModalOpen, setExpansionModalOpen] = React.useState(false);
|
||||
@@ -167,6 +171,7 @@ const ParsedAction = (props) => {
|
||||
const [activateHidingBody, setActivateHidingBody] = React.useState(false);
|
||||
const [codedata, setcodedata] = React.useState("");
|
||||
const [fieldCount, setFieldCount] = React.useState(0);
|
||||
const [hiddenDescription, setHiddenDescription] = React.useState(true);
|
||||
|
||||
const keywords = [
|
||||
"len(",
|
||||
@@ -381,10 +386,8 @@ const ParsedAction = (props) => {
|
||||
};
|
||||
|
||||
const AppActionArguments = (props) => {
|
||||
const [selectedActionParameters, setSelectedActionParameters] =
|
||||
React.useState([]);
|
||||
const [selectedVariableParameter, setSelectedVariableParameter] =
|
||||
React.useState("");
|
||||
const [selectedActionParameters, setSelectedActionParameters] = React.useState([]);
|
||||
const [selectedVariableParameter, setSelectedVariableParameter] = React.useState("");
|
||||
const [actionlist, setActionlist] = React.useState([]);
|
||||
const [jsonList, setJsonList] = React.useState([]);
|
||||
const [showDropdown, setShowDropdown] = React.useState(false);
|
||||
@@ -975,6 +978,14 @@ const ParsedAction = (props) => {
|
||||
return helperText
|
||||
}
|
||||
|
||||
console.log("AUTH: ", authenticationType)
|
||||
if (authenticationType.type === "oauth2") {
|
||||
return (
|
||||
<Typography variant="body1" color="textSecondary" style={{marginTop: 15}}>
|
||||
You must authenticate before using oauth2 apps.
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
// FIXME: Issue #40 - selectedActionParameters not reset
|
||||
if (
|
||||
@@ -984,7 +995,24 @@ const ParsedAction = (props) => {
|
||||
var authWritten = false;
|
||||
return (
|
||||
<div style={{ marginTop: hideExtraTypes ? 10 : 30 }}>
|
||||
<b>Parameters</b>
|
||||
<Tooltip
|
||||
color="secondary"
|
||||
title={"Click to learn more about this action"}
|
||||
placement="top"
|
||||
>
|
||||
<Button
|
||||
variant="text"
|
||||
color="secondary"
|
||||
style={{justifyContent: "flex-start", textAlign: "left", textTransform: "none", width: "100%",}}
|
||||
fullWidth
|
||||
disabled={selectedAction.description === undefined || selectedAction.description === null || selectedAction.description.length === 0}
|
||||
onClick={() => {
|
||||
setHiddenDescription(!hiddenDescription)
|
||||
}}
|
||||
>
|
||||
<b>Parameters</b>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{selectedActionParameters.map((data, count) => {
|
||||
if (data.variant === "") {
|
||||
data.variant = "STATIC_VALUE";
|
||||
@@ -1083,16 +1111,20 @@ const ParsedAction = (props) => {
|
||||
var disabled = false;
|
||||
var rows = "5";
|
||||
var openApiHelperText = "This is an OpenAPI specific field";
|
||||
/*
|
||||
if (
|
||||
selectedApp.generated &&
|
||||
data.name === "url" &&
|
||||
data.required &&
|
||||
data.configuration &&
|
||||
hideExtraTypes
|
||||
data.configuration
|
||||
) {
|
||||
console.log("GENERATED WITH DATA: ", data);
|
||||
//&&
|
||||
//hideExtraTypes
|
||||
|
||||
//console.log("GENERATED WITH DATA: ", data);
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
|
||||
if (selectedApp.generated && data.name === "headers") {
|
||||
//console.log("HEADER: ", data)
|
||||
@@ -1867,7 +1899,6 @@ const ParsedAction = (props) => {
|
||||
>
|
||||
{data.configuration === true ? (
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title={`Authenticate ${selectedApp.name}`}
|
||||
placement="top"
|
||||
>
|
||||
@@ -1877,6 +1908,7 @@ const ParsedAction = (props) => {
|
||||
width: 24,
|
||||
height: 24,
|
||||
marginRight: 10,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
}}
|
||||
onClick={() => {
|
||||
setAuthenticationModalOpen(true);
|
||||
@@ -2072,7 +2104,7 @@ const ParsedAction = (props) => {
|
||||
|
||||
const expansionModal = (
|
||||
<Dialog
|
||||
disableEnforceFocus={true}
|
||||
disableEnforceFocus={false}
|
||||
hideBackdrop={true}
|
||||
open={expansionModalOpen}
|
||||
onClose={() => {
|
||||
@@ -2171,7 +2203,7 @@ const ParsedAction = (props) => {
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
paddingLeft: 25,
|
||||
marginLeft: 15,
|
||||
paddingRight: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
@@ -2191,7 +2223,7 @@ const ParsedAction = (props) => {
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
paddingLeft: 25,
|
||||
marginLeft: 15,
|
||||
paddingRight: 0,
|
||||
}}
|
||||
onClick={() => {}}
|
||||
@@ -2211,6 +2243,40 @@ const ParsedAction = (props) => {
|
||||
</Tooltip>
|
||||
</a>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
marginLeft: 15,
|
||||
paddingRight: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
//setAuthenticationModalOpen(true);
|
||||
console.log("Should enable/disable magic!")
|
||||
console.log("Action: ", selectedAction)
|
||||
if (selectedAction.run_magic_output === undefined) {
|
||||
selectedAction.run_magic_output = true
|
||||
} else {
|
||||
if (selectedAction.run_magic_output === true) {
|
||||
selectedAction.run_magic_output = false
|
||||
} else {
|
||||
selectedAction.run_magic_output = true
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedAction(selectedAction)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title={selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "Click to enable magic parsing" : "Click to disable magic parsing"}
|
||||
placement="top"
|
||||
>
|
||||
<AutoFixHighIcon style={{ color: selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "white" : "#f86a3e"}} />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
@@ -2451,7 +2517,7 @@ const ParsedAction = (props) => {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showEnvironment !== undefined && showEnvironment ? (
|
||||
{showEnvironment !== undefined && showEnvironment && environments.length > 1 ? (
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Typography>Environment</Typography>
|
||||
<Select
|
||||
@@ -2480,13 +2546,14 @@ const ParsedAction = (props) => {
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
>
|
||||
{environments.map((data) => {
|
||||
if (data.archived) {
|
||||
{environments.map((data, index) => {
|
||||
if (data.archived === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
key={data.Name}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
@@ -2595,7 +2662,7 @@ const ParsedAction = (props) => {
|
||||
option === undefined ||
|
||||
option === null ||
|
||||
option.name === undefined ||
|
||||
option.name === undefined
|
||||
option.name === null
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -2628,6 +2695,16 @@ const ParsedAction = (props) => {
|
||||
newActionname = data.label;
|
||||
}
|
||||
|
||||
var newActiondescription = data.description;
|
||||
//console.log("DESC: ", newActiondescription)
|
||||
if (
|
||||
data.description === undefined || data.description === null
|
||||
) {
|
||||
newActiondescription = "Description: No description defined for this action"
|
||||
} else {
|
||||
newActiondescription = "Description: "+newActiondescription
|
||||
}
|
||||
|
||||
const iconInfo = GetIconInfo({ name: data.name });
|
||||
const useIcon = iconInfo.originalIcon;
|
||||
|
||||
@@ -2636,32 +2713,40 @@ const ParsedAction = (props) => {
|
||||
newActionname.substring(1)
|
||||
).replaceAll("_", " ");
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex" }}>
|
||||
<span
|
||||
style={{
|
||||
marginRight: 10,
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
}}
|
||||
>
|
||||
{useIcon}
|
||||
</span>
|
||||
<span style={{}}>{newActionname}</span>
|
||||
</div>
|
||||
<Tooltip
|
||||
color="secondary"
|
||||
title={newActiondescription}
|
||||
placement="left"
|
||||
>
|
||||
<div style={{ display: "flex" }}>
|
||||
<span
|
||||
style={{
|
||||
marginRight: 10,
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
}}
|
||||
>
|
||||
{useIcon}
|
||||
</span>
|
||||
<span style={{}}>{newActionname}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => {
|
||||
return (
|
||||
<TextField
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
{...params}
|
||||
label="Find Actions"
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
{...params}
|
||||
label="Find Actions"
|
||||
variant="outlined"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -2702,20 +2787,26 @@ const ParsedAction = (props) => {
|
||||
</Select>
|
||||
: null*/}
|
||||
|
||||
{selectedAction.description !== undefined &&
|
||||
selectedAction.description.length > 0 &&
|
||||
hideExtraTypes !== true ? (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
maxHeight: 60,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{selectedAction.description}
|
||||
</div>
|
||||
) : null}
|
||||
{selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenDescription === false ? (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid rgba(255,255,255,0.6)",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
marginTop: 15,
|
||||
marginBottom: 10,
|
||||
maxHeight: 60,
|
||||
overflow: "hidden",
|
||||
padding: 15,
|
||||
}}
|
||||
>
|
||||
<Typography style={{}}>
|
||||
<b>Description</b>
|
||||
</Typography>
|
||||
<Typography style={{}}>
|
||||
{selectedAction.description}
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import React, {useState } from 'react';
|
||||
import {isMobile} from "react-device-detect";
|
||||
import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
|
||||
import {Link} from 'react-router-dom';
|
||||
import ReactGA from 'react-ga';
|
||||
|
||||
import { Button, LinearProgress, Typography } from '@material-ui/core';
|
||||
|
||||
export const securityFramework = [
|
||||
{
|
||||
image: <path d="M15.6408 8.39233H18.0922V10.0287H15.6408V8.39233ZM0.115234 8.39233H2.56663V10.0287H0.115234V8.39233ZM9.92083 0.21051V2.66506H8.28656V0.21051H9.92083ZM3.31839 2.25596L5.05889 4.00687L3.89856 5.16051L2.15807 3.42596L3.31839 2.25596ZM13.1485 3.99869L14.8808 2.25596L16.0493 3.42596L14.3088 5.16051L13.1485 3.99869ZM9.10369 4.30142C10.404 4.30142 11.651 4.81863 12.5705 5.73926C13.4899 6.65989 14.0065 7.90854 14.0065 9.21051C14.0065 11.0269 13.0178 12.6141 11.5551 13.4651V14.9378C11.5551 15.1548 11.469 15.3629 11.3158 15.5163C11.1625 15.6698 10.9547 15.756 10.738 15.756H7.46943C7.25271 15.756 7.04487 15.6698 6.89163 15.5163C6.73839 15.3629 6.6523 15.1548 6.6523 14.9378V13.4651C5.18963 12.6141 4.2009 11.0269 4.2009 9.21051C4.2009 7.90854 4.71744 6.65989 5.63689 5.73926C6.55635 4.81863 7.80339 4.30142 9.10369 4.30142ZM10.738 16.5741V17.3923C10.738 17.6093 10.6519 17.8174 10.4986 17.9709C10.3454 18.1243 10.1375 18.2105 9.92083 18.2105H8.28656C8.06984 18.2105 7.862 18.1243 7.70876 17.9709C7.55552 17.8174 7.46943 17.6093 7.46943 17.3923V16.5741H10.738ZM8.28656 14.1196H9.92083V12.3769C11.3345 12.0169 12.3722 10.7323 12.3722 9.21051C12.3722 8.34253 12.0279 7.5101 11.4149 6.89634C10.8019 6.28259 9.97056 5.93778 9.10369 5.93778C8.23683 5.93778 7.40546 6.28259 6.79249 6.89634C6.17953 7.5101 5.83516 8.34253 5.83516 9.21051C5.83516 10.7323 6.87292 12.0169 8.28656 12.3769V14.1196Z" />,
|
||||
text: "Cases",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" />,
|
||||
text: "SIEM",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M11.223 10.971L3.85195 14.4L7.28095 7.029L14.652 3.6L11.223 10.971ZM9.25195 0C8.07006 0 6.89973 0.232792 5.8078 0.685084C4.71587 1.13738 3.72372 1.80031 2.88799 2.63604C1.20016 4.32387 0.251953 6.61305 0.251953 9C0.251953 11.3869 1.20016 13.6761 2.88799 15.364C3.72372 16.1997 4.71587 16.8626 5.8078 17.3149C6.89973 17.7672 8.07006 18 9.25195 18C11.6389 18 13.9281 17.0518 15.6159 15.364C17.3037 13.6761 18.252 11.3869 18.252 9C18.252 7.8181 18.0192 6.64778 17.5669 5.55585C17.1146 4.46392 16.4516 3.47177 15.6159 2.63604C14.7802 1.80031 13.788 1.13738 12.6961 0.685084C11.6042 0.232792 10.4338 0 9.25195 0ZM9.25195 8.01C8.98939 8.01 8.73758 8.1143 8.55192 8.29996C8.36626 8.48563 8.26195 8.73744 8.26195 9C8.26195 9.26256 8.36626 9.51437 8.55192 9.70004C8.73758 9.8857 8.98939 9.99 9.25195 9.99C9.51452 9.99 9.76633 9.8857 9.95199 9.70004C10.1376 9.51437 10.242 9.26256 10.242 9C10.242 8.73744 10.1376 8.48563 9.95199 8.29996C9.76633 8.1143 9.51452 8.01 9.25195 8.01Z" />,
|
||||
text: "Assets",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M13.3318 2.223C13.2598 2.223 13.1878 2.205 13.1248 2.169C11.3968 1.278 9.90284 0.9 8.11184 0.9C6.32984 0.9 4.63784 1.323 3.09884 2.169C2.88284 2.286 2.61284 2.205 2.48684 1.989C2.36984 1.773 2.45084 1.494 2.66684 1.377C4.34084 0.468 6.17684 0 8.11184 0C10.0288 0 11.7028 0.423 13.5388 1.368C13.7638 1.485 13.8448 1.755 13.7278 1.971C13.6468 2.133 13.4938 2.223 13.3318 2.223ZM0.452843 6.948C0.362843 6.948 0.272843 6.921 0.191843 6.867C-0.015157 6.723 -0.0601571 6.444 0.0838429 6.237C0.974843 4.977 2.10884 3.987 3.45884 3.294C6.28484 1.836 9.90284 1.827 12.7378 3.285C14.0878 3.978 15.2218 4.959 16.1128 6.21C16.2568 6.408 16.2118 6.696 16.0048 6.84C15.7978 6.984 15.5188 6.939 15.3748 6.732C14.5648 5.598 13.5388 4.707 12.3238 4.086C9.74084 2.763 6.43784 2.763 3.86384 4.095C2.63984 4.725 1.61384 5.625 0.803843 6.759C0.731843 6.885 0.596843 6.948 0.452843 6.948ZM6.07784 17.811C5.96084 17.811 5.84384 17.766 5.76284 17.676C4.97984 16.893 4.55684 16.389 3.95384 15.3C3.33284 14.193 3.00884 12.843 3.00884 11.394C3.00884 8.721 5.29484 6.543 8.10284 6.543C10.9108 6.543 13.1968 8.721 13.1968 11.394C13.1968 11.646 12.9988 11.844 12.7468 11.844C12.4948 11.844 12.2968 11.646 12.2968 11.394C12.2968 9.216 10.4158 7.443 8.10284 7.443C5.78984 7.443 3.90884 9.216 3.90884 11.394C3.90884 12.69 4.19684 13.887 4.74584 14.859C5.32184 15.894 5.71784 16.335 6.41084 17.037C6.58184 17.217 6.58184 17.496 6.41084 17.676C6.31184 17.766 6.19484 17.811 6.07784 17.811ZM12.5308 16.146C11.4598 16.146 10.5148 15.876 9.74084 15.345C8.39984 14.436 7.59884 12.96 7.59884 11.394C7.59884 11.142 7.79684 10.944 8.04884 10.944C8.30084 10.944 8.49884 11.142 8.49884 11.394C8.49884 12.663 9.14684 13.86 10.2448 14.598C10.8838 15.03 11.6308 15.237 12.5308 15.237C12.7468 15.237 13.1068 15.21 13.4668 15.147C13.7098 15.102 13.9438 15.264 13.9888 15.516C14.0338 15.759 13.8718 15.993 13.6198 16.038C13.1068 16.137 12.6568 16.146 12.5308 16.146ZM10.7218 18C10.6858 18 10.6408 17.991 10.6048 17.982C9.17384 17.586 8.23784 17.055 7.25684 16.092C5.99684 14.841 5.30384 13.176 5.30384 11.394C5.30384 9.936 6.54584 8.748 8.07584 8.748C9.60584 8.748 10.8478 9.936 10.8478 11.394C10.8478 12.357 11.6848 13.14 12.7198 13.14C13.7548 13.14 14.5918 12.357 14.5918 11.394C14.5918 8.001 11.6668 5.247 8.06684 5.247C5.51084 5.247 3.17084 6.669 2.11784 8.874C1.76684 9.603 1.58684 10.458 1.58684 11.394C1.58684 12.096 1.64984 13.203 2.18984 14.643C2.27984 14.877 2.16284 15.138 1.92884 15.219C1.69484 15.309 1.43384 15.183 1.35284 14.958C0.911843 13.779 0.695843 12.609 0.695843 11.394C0.695843 10.314 0.902843 9.333 1.30784 8.478C2.50484 5.967 5.15984 4.338 8.06684 4.338C12.1618 4.338 15.4918 7.497 15.4918 11.385C15.4918 12.843 14.2498 14.031 12.7198 14.031C11.1898 14.031 9.94784 12.843 9.94784 11.385C9.94784 10.422 9.11084 9.639 8.07584 9.639C7.04084 9.639 6.20384 10.422 6.20384 11.385C6.20384 12.924 6.79784 14.364 7.88684 15.444C8.74184 16.29 9.56084 16.758 10.8298 17.109C11.0728 17.172 11.2078 17.424 11.1448 17.658C11.0998 17.865 10.9108 18 10.7218 18Z" />,
|
||||
text: "IAM",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image: <path d="M16.1091 8.57143H14.8234V5.14286C14.8234 4.19143 14.052 3.42857 13.1091 3.42857H9.68052V2.14286C9.68052 1.57454 9.45476 1.02949 9.0529 0.627628C8.65103 0.225765 8.10599 0 7.53767 0C6.96935 0 6.4243 0.225765 6.02244 0.627628C5.62057 1.02949 5.39481 1.57454 5.39481 2.14286V3.42857H1.96624C1.51158 3.42857 1.07555 3.60918 0.754056 3.93067C0.432565 4.25216 0.251953 4.6882 0.251953 5.14286V8.4H1.53767C2.82338 8.4 3.85195 9.42857 3.85195 10.7143C3.85195 12 2.82338 13.0286 1.53767 13.0286H0.251953V16.2857C0.251953 16.7404 0.432565 17.1764 0.754056 17.4979C1.07555 17.8194 1.51158 18 1.96624 18H5.22338V16.7143C5.22338 15.4286 6.25195 14.4 7.53767 14.4C8.82338 14.4 9.85195 15.4286 9.85195 16.7143V18H13.1091C13.5638 18 13.9998 17.8194 14.3213 17.4979C14.6428 17.1764 14.8234 16.7404 14.8234 16.2857V12.8571H16.1091C16.6774 12.8571 17.2225 12.6314 17.6243 12.2295C18.0262 11.8277 18.252 11.2826 18.252 10.7143C18.252 10.146 18.0262 9.60092 17.6243 9.19906C17.2225 8.79719 16.6774 8.57143 16.1091 8.57143Z" />,
|
||||
text: "Intel",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M9.89516 7.71433H8.60945V5.1429H9.89516V7.71433ZM9.89516 10.2858H8.60945V9.00004H9.89516V10.2858ZM14.3952 2.57147H4.10944C3.76845 2.57147 3.44143 2.70693 3.20031 2.94805C2.95919 3.18917 2.82373 3.51619 2.82373 3.85719V15.4286L5.39516 12.8572H14.3952C14.7362 12.8572 15.0632 12.7217 15.3043 12.4806C15.5454 12.2395 15.6809 11.9125 15.6809 11.5715V3.85719C15.6809 3.14361 15.1023 2.57147 14.3952 2.57147Z" />,
|
||||
text: "Comms",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M0.251953 10.6011H3.8391L9.38052 -4.92572e-08L10.8977 11.5696L15.0377 6.28838L19.3191 10.6011H23.3948V13.1836H18.252L15.2562 10.175L9.1491 18L7.88909 8.41894L5.39481 13.1836H0.251953V10.6011Z" />,
|
||||
text: "Network",
|
||||
description: "Case management"
|
||||
},
|
||||
{
|
||||
image:
|
||||
<path d="M19.1722 8.9957L17.0737 6.60487L17.3661 3.44004L14.2615 2.73483L12.6361 -3.28068e-08L9.71206 1.25561L6.78803 -3.28068e-08L5.16261 2.73483L2.05797 3.43144L2.35038 6.59627L0.251953 8.9957L2.35038 11.3865L2.05797 14.56L5.16261 15.2652L6.78803 18L9.71206 16.7358L12.6361 17.9914L14.2615 15.2566L17.3661 14.5514L17.0737 11.3865L19.1722 8.9957ZM10.5721 13.2957H8.85205V11.5757H10.5721V13.2957ZM10.5721 9.85571H8.85205V4.69565H10.5721V9.85571Z" />,
|
||||
text: "EDR & AV",
|
||||
description: "Case management"
|
||||
},
|
||||
]
|
||||
|
||||
const LandingpageUsecases = (props) => {
|
||||
const [selectedUsecase, setSelectedUsecase] = useState("Phishing")
|
||||
const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases)
|
||||
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
|
||||
const buttonStyle = {borderRadius: 25, height: 50, width: 260, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18, backgroundImage: buttonBackground}
|
||||
|
||||
const HandleTitle = (props) => {
|
||||
const { usecases, selectedUsecase, setSelecedUsecase } = props
|
||||
const [progress, setProgress] = useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setProgress((oldProgress) => {
|
||||
if (oldProgress >= 105) {
|
||||
const foundIndex = usecasekeys.findIndex(key => key === selectedUsecase)
|
||||
var newitem = usecasekeys[foundIndex+1]
|
||||
if (newitem === undefined || newitem === 0) {
|
||||
newitem = usecasekeys[1]
|
||||
}
|
||||
|
||||
setSelectedUsecase(newitem)
|
||||
return -18
|
||||
}
|
||||
|
||||
if (oldProgress >= 65) {
|
||||
return oldProgress + 3
|
||||
}
|
||||
|
||||
if (oldProgress >= 80) {
|
||||
return oldProgress + 1
|
||||
}
|
||||
|
||||
return oldProgress + 6
|
||||
})
|
||||
}, 165)
|
||||
|
||||
return () => {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (usecases === null || usecases === undefined || usecases.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const modifier = isMobile ? 17 : 22
|
||||
return (
|
||||
<span style={{margin: "auto", textAlign: isMobile ? "center" : "left", width: isMobile ? 280 : "100%",}}>
|
||||
<b>Handle <br/>
|
||||
<span style={{marginBottom: 10}}>
|
||||
<i id="usecase-text">{selectedUsecase}</i>
|
||||
<LinearProgress variant="determinate" value={progress} style={{marginTop: 0, marginBottom: 0, height: 3, width: isMobile ? "100%" : selectedUsecase.length*modifier, borderRadius: 10, }} />
|
||||
</span>
|
||||
with confidence</b>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const parsedWidth = isMobile ? "100%" : 1100
|
||||
return (
|
||||
<div style={{width: isMobile ? null : parsedWidth, margin: isMobile ? "0px 0px 0px 0px" : "auto", color: "white", textAlign: isMobile ? "center" : "left",}}>
|
||||
<div style={{display: "flex", position: "relative",}}>
|
||||
<div style={{maxWidth: isMobile ? "100%" : 420, paddingTop: isMobile ? 0 : 120, zIndex: 1000, margin: "auto",}}>
|
||||
|
||||
<Typography variant="h1" style={{margin: "auto", width: isMobile ? 280 : "100%", marginTop: isMobile ? 50 : 0}}>
|
||||
<HandleTitle usecases={usecases} selectedUsecase={selectedUsecase} setSelectedUsecase={setSelectedUsecase} />
|
||||
|
||||
{/*<b>Security Automation <i>is Hard</i></b>*/}
|
||||
</Typography>
|
||||
<Typography variant="h6" style={{marginTop: isMobile ? 15 : 0,}}>
|
||||
Connecting your everchanging environment is hard. We get it! That's why we built Shuffle, where you can use and share your security workflows to everyones benefit.
|
||||
{/*Shuffle is an automation platform where you don't need to be an expert to automate. Get access to our large pool of security playbooks, apps and people.*/}
|
||||
</Typography>
|
||||
<div style={{display: "flex", textAlign: "center", itemAlign: "center",}}>
|
||||
{isMobile ? null :
|
||||
<Link rel="noopener noreferrer" to={"/pricing"} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_pricing",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground, marginRight: 10,
|
||||
}}>
|
||||
See Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
{isMobile ? null :
|
||||
<Link rel="noopener noreferrer" to={"/register?message=You'll need to sign up first. No name, company or credit card required."} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_try_it_out",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground,
|
||||
}}>
|
||||
Start for free
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{isMobile ? null :
|
||||
<div style={{marginLeft: 200, marginTop: 125, zIndex: 1000}}>
|
||||
<DetectionFramework showOptions={false} selectedOption={selectedUsecase} rolling={true} />
|
||||
</div>
|
||||
}
|
||||
{isMobile ? null :
|
||||
<div style={{position: "absolute", top: 50, right: -200, zIndex: 0, }}>
|
||||
<svg width="351" height="433" viewBox="0 0 351 433" fill="none" xmlns="http://www.w3.org/2000/svg" style={{zIndex: 0, }}>
|
||||
<path d="M167.781 184.839C167.781 235.244 208.625 276.104 259.03 276.104C309.421 276.104 350.28 235.244 350.28 184.839C350.28 134.448 309.421 93.5892 259.03 93.5892C208.625 93.5741 167.781 134.433 167.781 184.839ZM330.387 184.839C330.387 224.263 298.439 256.195 259.03 256.195C219.621 256.195 187.674 224.248 187.674 184.839C187.674 145.43 219.636 113.483 259.03 113.483C298.439 113.483 330.387 145.43 330.387 184.839Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M167.781 387.368C167.781 412.578 188.203 433 213.398 433C238.593 433 259.03 412.578 259.03 387.368C259.03 362.157 238.608 341.735 213.398 341.735C188.187 341.735 167.781 362.172 167.781 387.368ZM249.076 387.368C249.076 407.08 233.095 423.046 213.398 423.046C193.686 423.046 177.72 407.065 177.72 387.368C177.72 367.671 193.686 351.69 213.398 351.69C233.095 351.705 249.076 367.671 249.076 387.368Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M56.8637 0.738726C25.7052 0.738724 0.44632 25.9976 0.446317 57.1561C0.446314 88.3146 25.7052 113.573 56.8637 113.573C88.0221 113.573 113.281 88.3146 113.281 57.1561C113.281 25.9977 88.0222 0.738729 56.8637 0.738726Z" fill="white" fill-opacity="0.2"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div style={{display: "flex", width: isMobile ? "100%" : 300, itemAlign: "center", margin: "auto", marginTop: 20, flexDirection: isMobile ? "column" : "row", textAlign: "center",}}>
|
||||
{isMobile ?
|
||||
<Link rel="noopener noreferrer" to={"/pricing"} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant={isMobile ? "contained" : "outlined"}
|
||||
color={isMobile ? "primary" : "secondary"}
|
||||
style={buttonStyle}
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_pricing",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
>
|
||||
See pricing
|
||||
</Button>
|
||||
</Link>
|
||||
: null
|
||||
}
|
||||
{/*isMobile ?
|
||||
<Link rel="noopener noreferrer" to={"/docs/features"} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: "landingpage",
|
||||
action: "click_main_features",
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
color="secondary"
|
||||
style={buttonStyle}>
|
||||
Features
|
||||
</Button>
|
||||
</Link>
|
||||
: null*/}
|
||||
</div>
|
||||
{isMobile ? null :
|
||||
<div style={{display: "flex", width: parsedWidth, margin: "auto", marginTop: 150}}>
|
||||
{securityFramework.map((data, index) => {
|
||||
return (
|
||||
<div key={index} style={{flex: 1, textAlign: "center",}}>
|
||||
<span style={{margin: "auto", width: 25,}}>
|
||||
<svg width="25" height="25" fill="white" xmlns="http://www.w3.org/2000/svg" >
|
||||
{data.image}
|
||||
</svg>
|
||||
</span>
|
||||
<Typography variant="body2" style={{color: "white", marginRight: 5}}>
|
||||
{data.text}
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingpageUsecases;
|
||||
@@ -19,14 +19,10 @@ import 'codemirror/keymap/sublime';
|
||||
import 'codemirror/theme/gruvbox-dark.css';
|
||||
|
||||
const CodeEditor = (props) => {
|
||||
const {fieldCount, setFieldCount} = props
|
||||
const {actionlist} = props
|
||||
const {changeActionParameterCodeMirror} = props
|
||||
const {expansionModalOpen, setExpansionModalOpen} = props
|
||||
const {codedata, setcodedata} = props
|
||||
const { fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata } = props
|
||||
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
||||
// const {codelang, setcodelang} = props
|
||||
const theme = useTheme();
|
||||
// const {codelang, setcodelang} = props
|
||||
const theme = useTheme();
|
||||
|
||||
const [validation, setvalidation] = React.useState(" ");
|
||||
function IsJsonString(str) {
|
||||
@@ -191,7 +187,7 @@ const CodeEditor = (props) => {
|
||||
setExpansionModalOpen(false)
|
||||
}}
|
||||
>
|
||||
CANCEL
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
@@ -214,7 +210,7 @@ const CodeEditor = (props) => {
|
||||
setcodedata(localcodedata)
|
||||
}}
|
||||
>
|
||||
DONE
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
const data = [{
|
||||
selector: 'node',
|
||||
css: {
|
||||
'label': 'data(label)',
|
||||
'text-valign': 'center',
|
||||
'font-family': 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif',
|
||||
'font-weight': 'lighter',
|
||||
'font-size': '12px',
|
||||
'text-algin': 'center',
|
||||
'width': '85px',
|
||||
'height': '85px',
|
||||
'border-width': '1px',
|
||||
'border-color': '#8a8a8a',
|
||||
'color': '#f85a3e',
|
||||
'text-margin-x': '0px',
|
||||
'text-margin-y': 'data(text_margin_y)',
|
||||
'background-color': '#27292d',
|
||||
'background-image': 'data(large_image)',
|
||||
'background-width': 'data(width)',
|
||||
'background-height': 'data(height)',
|
||||
'background-position-x': 'data(margin_x)',
|
||||
'background-position-y': 'data(margin_y)',
|
||||
'background-clip': "node",
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
css: {
|
||||
'target-arrow-shape': 'triangle',
|
||||
'target-arrow-color': '#8a8a8a',
|
||||
'curve-style': 'bezier',
|
||||
'label': 'data(label)',
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': '120px',
|
||||
"color": "rgba(255,255,255,0.7)",
|
||||
'line-style': 'dashed',
|
||||
"line-fill": "linear-gradient",
|
||||
"line-gradient-stop-positions": ["0.0", "100"],
|
||||
"line-gradient-stop-colors": ["#8a8a8a", "#8a8a8a"],
|
||||
'width': '1px',
|
||||
'z-compound-depth': 'top',
|
||||
'font-size': '13px',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `edge[?human]`,
|
||||
css: {
|
||||
'target-arrow-color': '#6d9eeb',
|
||||
'line-style': 'solid',
|
||||
"line-gradient-stop-positions": ["0.0", "100"],
|
||||
"line-gradient-stop-colors": ["#6d9eeb", "#6d9eeb"],
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: `node[?middle_node]`,
|
||||
css: {
|
||||
'background-image': '/images/Shuffle_logo.png',
|
||||
'height': '105px',
|
||||
'width': '105px',
|
||||
'background-width': '105px',
|
||||
'background-height': '105px',
|
||||
'background-position-x': '0px',
|
||||
'background-position-y': '0px',
|
||||
'border-width': '2px',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[?invisible]`,
|
||||
css: {
|
||||
'height': '10x',
|
||||
'width': '10px',
|
||||
'background-position-x': '0px',
|
||||
'background-position-y': '0px',
|
||||
'border-width': '0px',
|
||||
'font-size': '0px',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[?font_size]`,
|
||||
css: {
|
||||
'font-size': 'data(font_size)',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: ".eh-preview, .eh-ghost-edge",
|
||||
style: {
|
||||
"background-color": "#337ab7",
|
||||
"line-color": "#337ab7",
|
||||
"target-arrow-color": "#337ab7",
|
||||
"source-arrow-color": "#337ab7",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export default data
|
||||
+139
-82
@@ -86,7 +86,7 @@ const MenuProps = {
|
||||
|
||||
|
||||
const Admin = (props) => {
|
||||
const { globalUrl, userdata } = props;
|
||||
const { globalUrl, userdata, serverside} = props;
|
||||
|
||||
var upload = "";
|
||||
var to_be_copied = "";
|
||||
@@ -560,11 +560,15 @@ const Admin = (props) => {
|
||||
if (responseJson.reason !== undefined) {
|
||||
alert.error(responseJson.reason);
|
||||
} else {
|
||||
alert.error("Failed creating suborg");
|
||||
alert.error("Failed creating suborg. Please try again");
|
||||
}
|
||||
} else {
|
||||
alert.success("Successfully created suborg!");
|
||||
alert.success("Successfully created suborg. Reloading in 3 seconds!");
|
||||
setSelectedUserModalOpen(false);
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
setOrgName("");
|
||||
@@ -864,7 +868,7 @@ const Admin = (props) => {
|
||||
const abortEnvironmentWorkflows = (environment) => {
|
||||
//console.log("Aborting all workflows started >10 minutes ago, not finished");
|
||||
|
||||
fetch(`${globalUrl}/api/v1/environments/${environment}/stop`, {
|
||||
fetch(`${globalUrl}/api/v1/environments/${environment.id}/stop?deleteall=true`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
})
|
||||
@@ -1094,6 +1098,41 @@ const Admin = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const deleteFile = (file) => {
|
||||
fetch(globalUrl + "/api/v1/files/" + file.id, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for file delete :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
alert.info("Successfully deleted file "+file.name)
|
||||
|
||||
} else if (responseJson.reason !== undefined && responseJson.reason !== null) {
|
||||
alert.error("Failed to delete file: " + responseJson.reason)
|
||||
|
||||
}
|
||||
setTimeout(() => {
|
||||
getFiles();
|
||||
}, 1500);
|
||||
|
||||
console.log(responseJson)
|
||||
})
|
||||
.catch((error) => {
|
||||
alert.error(error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const downloadFile = (file) => {
|
||||
fetch(globalUrl + "/api/v1/files/" + file.id + "/content", {
|
||||
method: "GET",
|
||||
@@ -1322,12 +1361,12 @@ const Admin = (props) => {
|
||||
3: "files",
|
||||
4: "schedules",
|
||||
5: "environments",
|
||||
6: "categories",
|
||||
6: "suborgs",
|
||||
};
|
||||
const setConfig = (event, newValue) => {
|
||||
//console.log("Value: ", newValue)
|
||||
const setConfig = (event, inputValue) => {
|
||||
const newValue = parseInt(inputValue)
|
||||
|
||||
setCurTab(parseInt(newValue));
|
||||
setCurTab(newValue)
|
||||
if (newValue === 1) {
|
||||
document.title = "Shuffle - admin - users";
|
||||
getUsers();
|
||||
@@ -1350,18 +1389,13 @@ const Admin = (props) => {
|
||||
document.title = "Shuffle - admin";
|
||||
}
|
||||
|
||||
console.log("NEWVALUE: ", newValue)
|
||||
|
||||
if (newValue === 6) {
|
||||
console.log("Should get apps for categories.");
|
||||
}
|
||||
|
||||
//var theURL = window.location.pathname
|
||||
//FIXME: Add url edits
|
||||
//var theURL = window.location
|
||||
//theURL.replace(`/${views[curTab]}`, `/${views[newValue]}`)
|
||||
//window.history.pushState({"html":response.html,"pageTitle":response.pageTitle},"", urlPath);
|
||||
|
||||
//console.log(newpath)
|
||||
//window.location.pathame = newpath
|
||||
props.history.push(`/admin?tab=${views[newValue]}`)
|
||||
|
||||
setModalUser({});
|
||||
};
|
||||
@@ -1373,13 +1407,24 @@ const Admin = (props) => {
|
||||
getUsers();
|
||||
} else {
|
||||
getSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if (props.match.params.key !== undefined) {
|
||||
//const tmpitem = views[props.match.params.key]
|
||||
setConfig("", props.match.params.key);
|
||||
}
|
||||
}
|
||||
if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
const params = Object.fromEntries(urlSearchParams.entries())
|
||||
const foundTab = params["tab"]
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
for (var key in Object.keys(views)) {
|
||||
const value = views[key]
|
||||
console.log(key, value)
|
||||
if (value === foundTab) {
|
||||
setConfig("", key)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
selectedOrganization.id === undefined &&
|
||||
@@ -1764,16 +1809,14 @@ const Admin = (props) => {
|
||||
run2FASetup(userdata);
|
||||
}}
|
||||
disabled={
|
||||
(selectedUser.role === "admin" &&
|
||||
selectedUser.username !== userdata.username) ||
|
||||
selectedUser.active === false
|
||||
(selectedUser.role === "admin" && selectedUser.username !== userdata.username)
|
||||
}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
>
|
||||
{selectedUser.mfa_info !== undefined &&
|
||||
selectedUser.mfa_info !== null &&
|
||||
selectedUser.mfa_info.active === true
|
||||
{ selectedUser.mfa_info !== undefined &&
|
||||
selectedUser.mfa_info !== null &&
|
||||
selectedUser.mfa_info.active === true
|
||||
? "Disable 2FA"
|
||||
: "Enable 2FA"}
|
||||
</Button>
|
||||
@@ -3120,7 +3163,6 @@ const Admin = (props) => {
|
||||
style={{ minWidth: 125, maxWidth: 125 }}
|
||||
/>
|
||||
<ListItemText primary="Actions" />
|
||||
<ListItemText primary="File ID" />
|
||||
</ListItem>
|
||||
{files === undefined || files === null || files.length === 0
|
||||
? null
|
||||
@@ -3237,11 +3279,13 @@ const Admin = (props) => {
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary=<Tooltip
|
||||
title={"Download file"}
|
||||
style={{}}
|
||||
aria-label={"Download"}
|
||||
>
|
||||
primary=
|
||||
<span style={{display: "flex"}}>
|
||||
<Tooltip
|
||||
title={"Download file"}
|
||||
style={{}}
|
||||
aria-label={"Download"}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
disabled={file.status !== "active"}
|
||||
@@ -3258,56 +3302,69 @@ const Admin = (props) => {
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={"Delete file"}
|
||||
style={{}}
|
||||
aria-label={"Delete"}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
disabled={file.status !== "active"}
|
||||
onClick={() => {
|
||||
deleteFile(file);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon
|
||||
style={{
|
||||
color:
|
||||
file.status === "active" ? "white" : "grey",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={"Copy file ID"}
|
||||
style={{}}
|
||||
aria-label={"copy"}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
const elementName = "copy_element_shuffle";
|
||||
var copyText = document.getElementById(elementName);
|
||||
if (copyText !== null && copyText !== undefined) {
|
||||
const clipboard = navigator.clipboard;
|
||||
if (clipboard === undefined) {
|
||||
alert.error(
|
||||
"Can only copy over HTTPS (port 3443)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(file.id);
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(
|
||||
0,
|
||||
99999
|
||||
); /* For mobile devices */
|
||||
|
||||
/* Copy the text inside the text field */
|
||||
document.execCommand("copy");
|
||||
|
||||
alert.info(file.id + " copied to clipboard");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FileCopyIcon style={{ color: "white" }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</span>
|
||||
style={{
|
||||
minWidth: 75,
|
||||
maxWidth: 75,
|
||||
minWidth: 150,
|
||||
maxWidth: 150,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
{/*
|
||||
<ListItemText>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled
|
||||
onClick={() => deleteSchedule(file)}
|
||||
>
|
||||
Stop schedule
|
||||
</Button>
|
||||
</ListItemText>
|
||||
*/}
|
||||
<ListItemText
|
||||
primary=<IconButton
|
||||
onClick={() => {
|
||||
const elementName = "copy_element_shuffle";
|
||||
var copyText = document.getElementById(elementName);
|
||||
if (copyText !== null && copyText !== undefined) {
|
||||
const clipboard = navigator.clipboard;
|
||||
if (clipboard === undefined) {
|
||||
alert.error(
|
||||
"Can only copy over HTTPS (port 3443)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(file.id);
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(
|
||||
0,
|
||||
99999
|
||||
); /* For mobile devices */
|
||||
|
||||
/* Copy the text inside the text field */
|
||||
document.execCommand("copy");
|
||||
|
||||
alert.info(file.id + " copied to clipboard");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FileCopyIcon style={{ color: "white" }} />
|
||||
</IconButton>
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
@@ -3811,7 +3868,7 @@ const Admin = (props) => {
|
||||
? environment.running_ip === undefined ||
|
||||
environment.running_ip === null ||
|
||||
environment.running_ip.length === 0
|
||||
? "Not started"
|
||||
? "Not running"
|
||||
: environment.running_ip
|
||||
: "N/A"
|
||||
}
|
||||
@@ -4097,7 +4154,7 @@ const Admin = (props) => {
|
||||
<Tabs
|
||||
value={curTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
textColor="secondary"
|
||||
onChange={setConfig}
|
||||
aria-label="disabled tabs example"
|
||||
>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+359
-177
@@ -23,6 +23,7 @@ import {
|
||||
CircularProgress,
|
||||
Chip,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import {
|
||||
LockOpen as LockOpenIcon,
|
||||
FileCopy as FileCopyIcon,
|
||||
@@ -521,16 +522,16 @@ const AppCreator = (props) => {
|
||||
|
||||
if (data.info["x-logo"] !== undefined) {
|
||||
if (data.info["x-logo"].url !== undefined) {
|
||||
console.log("PARSED LOGO: ", data.info["x-logo"].url);
|
||||
//console.log("PARSED LOGO: ", data.info["x-logo"].url);
|
||||
setFileBase64(data.info["x-logo"].url);
|
||||
} else {
|
||||
setFileBase64(data.info["x-logo"]);
|
||||
}
|
||||
console.log("");
|
||||
console.log("");
|
||||
console.log("LOGO: ", data.info["x-logo"]);
|
||||
console.log("");
|
||||
console.log("");
|
||||
//console.log("");
|
||||
//console.log("");
|
||||
//console.log("LOGO: ", data.info["x-logo"]);
|
||||
//console.log("");
|
||||
//console.log("");
|
||||
}
|
||||
|
||||
if (data.info.contact !== undefined) {
|
||||
@@ -660,14 +661,14 @@ const AppCreator = (props) => {
|
||||
|
||||
// Typescript? I think not ;)
|
||||
if (methodvalue["requestBody"] !== undefined) {
|
||||
//console.log("Handle requestbody: ", methodvalue["requestBody"])
|
||||
console.log("Handle requestbody: ", methodvalue["requestBody"])
|
||||
if (methodvalue["requestBody"]["content"] !== undefined) {
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["application/json"] !==
|
||||
undefined
|
||||
) {
|
||||
newaction["headers"] =
|
||||
"Content-Type=application/json\nAccept=application/json";
|
||||
//newaction["headers"] = ""
|
||||
//"Content-Type=application/json\nAccept=application/json";
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["application/json"][
|
||||
"schema"
|
||||
@@ -733,8 +734,8 @@ const AppCreator = (props) => {
|
||||
undefined
|
||||
) {
|
||||
console.log("METHOD XML: ", methodvalue);
|
||||
newaction["headers"] =
|
||||
"Content-Type=application/xml\nAccept=application/xml";
|
||||
//newaction["headers"] = ""
|
||||
//"Content-Type=application/xml\nAccept=application/xml";
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["application/xml"][
|
||||
"schema"
|
||||
@@ -772,7 +773,6 @@ const AppCreator = (props) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//console.log("REQUESTBODY: ", methodvalue["requestBody"]["content"])
|
||||
if (
|
||||
methodvalue["requestBody"]["content"]["example"] !== undefined
|
||||
) {
|
||||
@@ -787,7 +787,9 @@ const AppCreator = (props) => {
|
||||
];
|
||||
//JSON.stringify(tmpobject, null, 2)
|
||||
}
|
||||
} else if (
|
||||
}
|
||||
|
||||
if (
|
||||
methodvalue["requestBody"]["content"][
|
||||
"multipart/form-data"
|
||||
] !== undefined
|
||||
@@ -1332,6 +1334,7 @@ const AppCreator = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (data.servers !== undefined && data.servers.length > 0) {
|
||||
var firstUrl = data.servers[0].url;
|
||||
if (
|
||||
@@ -1366,123 +1369,128 @@ const AppCreator = (props) => {
|
||||
//console.log("SECURITY: ", securitySchemes)
|
||||
//if (Object.entries(securitySchemes) > 1 &&
|
||||
var newauth = [];
|
||||
for (const [key, value] of Object.entries(securitySchemes)) {
|
||||
console.log(key, value);
|
||||
if (key === "jwt") {
|
||||
setAuthenticationOption("JWT");
|
||||
setAuthenticationRequired(true);
|
||||
try {
|
||||
for (const [key, value] of Object.entries(securitySchemes)) {
|
||||
console.log(key, value);
|
||||
if (key === "jwt") {
|
||||
setAuthenticationOption("JWT");
|
||||
setAuthenticationRequired(true);
|
||||
|
||||
if (
|
||||
value.in !== undefined &&
|
||||
value.in !== null &&
|
||||
value.in.length > 0
|
||||
) {
|
||||
setParameterName(value.in);
|
||||
}
|
||||
} else if (value.scheme === "bearer") {
|
||||
setAuthenticationOption("Bearer auth");
|
||||
setAuthenticationRequired(true);
|
||||
} else if (key === "Oauth2" || key === "Oauth2c") {
|
||||
//alert.info("Can't handle Oauth2 auth yet.")
|
||||
setAuthenticationOption("Oauth2");
|
||||
setAuthenticationRequired(true);
|
||||
if (
|
||||
value.in !== undefined &&
|
||||
value.in !== null &&
|
||||
value.in.length > 0
|
||||
) {
|
||||
setParameterName(value.in);
|
||||
}
|
||||
} else if (value.scheme === "bearer") {
|
||||
setAuthenticationOption("Bearer auth");
|
||||
setAuthenticationRequired(true);
|
||||
} else if (key === "Oauth2" || key === "Oauth2c") {
|
||||
//alert.info("Can't handle Oauth2 auth yet.")
|
||||
setAuthenticationOption("Oauth2");
|
||||
setAuthenticationRequired(true);
|
||||
|
||||
//console.log("FLOW-1: ", value)
|
||||
const flowkey = value.flow === undefined ? "flows" : "flow";
|
||||
//console.log("FLOW: ", value[flowkey])
|
||||
const basekey =
|
||||
value[flowkey].authorizationCode !== undefined
|
||||
? "authorizationCode"
|
||||
: "implicit";
|
||||
//console.log("FLOW2: ", value[flowkey][basekey])
|
||||
if (
|
||||
value[flowkey] !== undefined &&
|
||||
value[flowkey][basekey] !== undefined
|
||||
) {
|
||||
if (
|
||||
value[flowkey][basekey].authorizationUrl !== undefined &&
|
||||
parameterName.length === 0
|
||||
) {
|
||||
setParameterName(value[flowkey][basekey].authorizationUrl);
|
||||
}
|
||||
//console.log("FLOW-1: ", value)
|
||||
const flowkey = value.flow === undefined ? "flows" : "flow";
|
||||
//console.log("FLOW: ", value[flowkey])
|
||||
const basekey =
|
||||
value[flowkey].authorizationCode !== undefined
|
||||
? "authorizationCode"
|
||||
: "implicit";
|
||||
//console.log("FLOW2: ", value[flowkey][basekey])
|
||||
if (
|
||||
value[flowkey] !== undefined &&
|
||||
value[flowkey][basekey] !== undefined
|
||||
) {
|
||||
if (
|
||||
value[flowkey][basekey].authorizationUrl !== undefined &&
|
||||
parameterName.length === 0
|
||||
) {
|
||||
setParameterName(value[flowkey][basekey].authorizationUrl);
|
||||
}
|
||||
|
||||
var tokenUrl = "";
|
||||
if (value[flowkey][basekey].tokenUrl !== undefined) {
|
||||
setParameterLocation(value[flowkey][basekey].tokenUrl);
|
||||
tokenUrl = value[flowkey][basekey].tokenUrl;
|
||||
} else {
|
||||
setParameterLocation("");
|
||||
}
|
||||
var tokenUrl = "";
|
||||
if (value[flowkey][basekey].tokenUrl !== undefined) {
|
||||
setParameterLocation(value[flowkey][basekey].tokenUrl);
|
||||
tokenUrl = value[flowkey][basekey].tokenUrl;
|
||||
} else {
|
||||
setParameterLocation("");
|
||||
}
|
||||
|
||||
if (value[flowkey][basekey].refreshUrl !== undefined) {
|
||||
setRefreshUrl(value[flowkey][basekey].refreshUrl);
|
||||
} else if (tokenUrl.length > 0) {
|
||||
setRefreshUrl(tokenUrl);
|
||||
}
|
||||
if (value[flowkey][basekey].refreshUrl !== undefined) {
|
||||
setRefreshUrl(value[flowkey][basekey].refreshUrl);
|
||||
} else if (tokenUrl.length > 0) {
|
||||
setRefreshUrl(tokenUrl);
|
||||
}
|
||||
|
||||
if (
|
||||
value[flowkey][basekey].scopes !== undefined &&
|
||||
value[flowkey][basekey].scopes !== null
|
||||
) {
|
||||
if (value[flowkey][basekey].scopes.length > 0) {
|
||||
setOauth2Scopes(value[flowkey][basekey].scopes);
|
||||
} else {
|
||||
var newscopes = [];
|
||||
for (let [scopekey, scopevalue] of Object.entries(
|
||||
value[flowkey][basekey].scopes
|
||||
)) {
|
||||
if (scopekey.startsWith("http")) {
|
||||
const scopekeysplit = scopekey.split("/");
|
||||
if (scopekeysplit.length < 5) {
|
||||
console.log("Skipping scope: ", scopekey);
|
||||
alert.info("Skipping scope: " + scopekey);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
value[flowkey][basekey].scopes !== undefined &&
|
||||
value[flowkey][basekey].scopes !== null
|
||||
) {
|
||||
if (value[flowkey][basekey].scopes.length > 0) {
|
||||
setOauth2Scopes(value[flowkey][basekey].scopes);
|
||||
} else {
|
||||
var newscopes = [];
|
||||
for (let [scopekey, scopevalue] of Object.entries(
|
||||
value[flowkey][basekey].scopes
|
||||
)) {
|
||||
if (scopekey.startsWith("http")) {
|
||||
const scopekeysplit = scopekey.split("/");
|
||||
if (scopekeysplit.length < 5) {
|
||||
console.log("Skipping scope: ", scopekey);
|
||||
alert.info("Skipping scope: " + scopekey);
|
||||
continue;
|
||||
}
|
||||
|
||||
//console.log("Checking scope for: ", scopekey, scopekeysplit.length)
|
||||
}
|
||||
//console.log("Checking scope for: ", scopekey, scopekeysplit.length)
|
||||
}
|
||||
|
||||
newscopes.push(scopekey);
|
||||
}
|
||||
newscopes.push(scopekey);
|
||||
}
|
||||
|
||||
setOauth2Scopes(newscopes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"Bad flowkey and basekey for oauth2: ",
|
||||
flowkey,
|
||||
basekey
|
||||
);
|
||||
}
|
||||
} else if (key === "ApiKeyAuth") {
|
||||
setAuthenticationOption("API key");
|
||||
setOauth2Scopes(newscopes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"Bad flowkey and basekey for oauth2: ",
|
||||
flowkey,
|
||||
basekey
|
||||
);
|
||||
}
|
||||
} else if (key === "ApiKeyAuth" || key === "Token") {
|
||||
setAuthenticationOption("API key");
|
||||
|
||||
value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1);
|
||||
setParameterLocation(value.in);
|
||||
if (!apikeySelection.includes(value.in)) {
|
||||
console.log("APIKEY SELECT: ", apikeySelection);
|
||||
alert.error("Might be error in setting up API key authentication");
|
||||
}
|
||||
value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1);
|
||||
setParameterLocation(value.in);
|
||||
if (!apikeySelection.includes(value.in)) {
|
||||
console.log("APIKEY SELECT: ", apikeySelection);
|
||||
alert.error("Might be error in setting up API key authentication");
|
||||
}
|
||||
|
||||
console.log("PARAM NAME: ", value.name);
|
||||
setParameterName(value.name);
|
||||
setAuthenticationRequired(true);
|
||||
} else if (value.scheme === "basic") {
|
||||
setAuthenticationOption("Basic auth");
|
||||
setAuthenticationRequired(true);
|
||||
} else if (value.scheme === "oauth2") {
|
||||
setAuthenticationOption("Oauth2");
|
||||
setAuthenticationRequired(true);
|
||||
} else {
|
||||
alert.error("Couldn't handle AUTH type: ", key);
|
||||
//newauth.push({
|
||||
// "name": key,
|
||||
// "type": value.in,
|
||||
// "example": "",
|
||||
//})
|
||||
}
|
||||
}
|
||||
console.log("PARAM NAME: ", value.name);
|
||||
setParameterName(value.name);
|
||||
setAuthenticationRequired(true);
|
||||
} else if (value.scheme === "basic") {
|
||||
setAuthenticationOption("Basic auth");
|
||||
setAuthenticationRequired(true);
|
||||
} else if (value.scheme === "oauth2") {
|
||||
setAuthenticationOption("Oauth2");
|
||||
setAuthenticationRequired(true);
|
||||
} else {
|
||||
alert.error("Couldn't handle AUTH type: ", key);
|
||||
//newauth.push({
|
||||
// "name": key,
|
||||
// "type": value.in,
|
||||
// "example": "",
|
||||
//})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
alert.error("Failed to handle auth")
|
||||
console.log("Error: ", e)
|
||||
}
|
||||
|
||||
if (newauth.length > 0) {
|
||||
setExtraAuth(newauth);
|
||||
@@ -1711,6 +1719,10 @@ const AppCreator = (props) => {
|
||||
},
|
||||
};
|
||||
|
||||
if (queryitem.example !== undefined) {
|
||||
newitem.example = queryitem.example
|
||||
}
|
||||
|
||||
if (queryitem.description !== undefined) {
|
||||
newitem.description = queryitem.description;
|
||||
}
|
||||
@@ -2122,7 +2134,7 @@ const AppCreator = (props) => {
|
||||
};
|
||||
|
||||
const addPathQuery = () => {
|
||||
urlPathQueries.push({ name: "", required: true });
|
||||
urlPathQueries.push({ name: "", required: true, example: "", });
|
||||
if (updater === "addupdater") {
|
||||
setUpdater("updater");
|
||||
} else {
|
||||
@@ -2191,8 +2203,9 @@ const AppCreator = (props) => {
|
||||
//console.log("Option: ", authenticationOption)
|
||||
//console.log("Location: ", parameterLocation)
|
||||
//console.log("Name: ", parameterName)
|
||||
const extraKeys = (
|
||||
<div style={{ marginTop: 50 }}>
|
||||
//const extraKeys = authenticationOption === "Oauth2" ? null :
|
||||
const extraKeys =
|
||||
<div style={{ marginTop: 50, marginRight: 25, }}>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Typography variant="body1">Extra authentication</Typography>
|
||||
{extraAuth.length === 0 ? (
|
||||
@@ -2356,7 +2369,7 @@ const AppCreator = (props) => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
//);
|
||||
|
||||
const jwtAuth =
|
||||
authenticationOption === "JWT" ? (
|
||||
@@ -2406,7 +2419,15 @@ const AppCreator = (props) => {
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
Base Authorization URL
|
||||
Find the Authorization URL, Token URL and scopes in question for the API. Ensure the app in question is pointed at https://shuffler.io/set_authentication
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
Base Authorization URL for Oauth2
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
@@ -2419,6 +2440,26 @@ const AppCreator = (props) => {
|
||||
variant="outlined"
|
||||
value={parameterName}
|
||||
onChange={(e) => setParameterName(e.target.value)}
|
||||
onBlur={(event) => {
|
||||
var tmpstring = event.target.value.trim();
|
||||
|
||||
if (
|
||||
tmpstring.length > 4 &&
|
||||
!tmpstring.startsWith("http") &&
|
||||
!tmpstring.startsWith("ftp")
|
||||
) {
|
||||
alert.error("Auth URL must start with http(s)://");
|
||||
}
|
||||
|
||||
if (tmpstring.includes("?")) {
|
||||
var newtmp = tmpstring.split("?")
|
||||
if (tmpstring.length > 1) {
|
||||
tmpstring = newtmp[0]
|
||||
}
|
||||
}
|
||||
|
||||
setParameterName(tmpstring)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
@@ -2433,7 +2474,7 @@ const AppCreator = (props) => {
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
Token URL
|
||||
Token URL for Oauth2
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
@@ -2445,7 +2486,29 @@ const AppCreator = (props) => {
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
value={parameterLocation}
|
||||
onChange={(e) => setParameterLocation(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setParameterLocation(e.target.value)
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
var tmpstring = event.target.value.trim();
|
||||
|
||||
if (
|
||||
tmpstring.length > 4 &&
|
||||
!tmpstring.startsWith("http") &&
|
||||
!tmpstring.startsWith("ftp")
|
||||
) {
|
||||
alert.error("Token URL must start with http(s)://");
|
||||
}
|
||||
|
||||
if (tmpstring.includes("?")) {
|
||||
var newtmp = tmpstring.split("?")
|
||||
if (tmpstring.length > 1) {
|
||||
tmpstring = newtmp[0]
|
||||
}
|
||||
}
|
||||
|
||||
setParameterLocation(tmpstring)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
@@ -2460,7 +2523,7 @@ const AppCreator = (props) => {
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
Refresh-token URL
|
||||
Refresh-token URL for Oauth2 (Optional)
|
||||
</Typography>
|
||||
<TextField
|
||||
style={{ margin: 0, flex: "1", backgroundColor: inputColor }}
|
||||
@@ -2472,6 +2535,26 @@ const AppCreator = (props) => {
|
||||
variant="outlined"
|
||||
value={refreshUrl}
|
||||
onChange={(e) => setRefreshUrl(e.target.value)}
|
||||
onBlur={(event) => {
|
||||
var tmpstring = event.target.value.trim();
|
||||
|
||||
if (
|
||||
tmpstring.length > 4 &&
|
||||
!tmpstring.startsWith("http") &&
|
||||
!tmpstring.startsWith("ftp")
|
||||
) {
|
||||
alert.error("Refresh URL must start with http(s)://");
|
||||
}
|
||||
|
||||
if (tmpstring.includes("?")) {
|
||||
var newtmp = tmpstring.split("?")
|
||||
if (tmpstring.length > 1) {
|
||||
tmpstring = newtmp[0]
|
||||
}
|
||||
}
|
||||
|
||||
setRefreshUrl(tmpstring)
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
@@ -2483,10 +2566,11 @@ const AppCreator = (props) => {
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
Scopes
|
||||
Scopes for Oauth2
|
||||
</Typography>
|
||||
<ChipInput
|
||||
style={{}}
|
||||
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}}
|
||||
required
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
@@ -2494,7 +2578,7 @@ const AppCreator = (props) => {
|
||||
},
|
||||
}}
|
||||
style={{ maxHeight: 80, overflowX: "hidden", overflowY: "auto" }}
|
||||
placeholder="Scopes"
|
||||
placeholder="Available Oauth2 Scopes"
|
||||
color="primary"
|
||||
fullWidth
|
||||
value={oauth2Scopes}
|
||||
@@ -2606,6 +2690,55 @@ const AppCreator = (props) => {
|
||||
return (
|
||||
<Paper key={index} style={actionListStyle}>
|
||||
<div style={{ marginLeft: "5px", width: "100%" }}>
|
||||
<div style={{display: "flex"}}>
|
||||
<TextField
|
||||
required
|
||||
fullWidth={true}
|
||||
defaultValue={data.name}
|
||||
placeholder={"Query name (key)"}
|
||||
label={"Query Key"}
|
||||
helperText={
|
||||
<span style={{ color: "white", marginBottom: "2px" }}>
|
||||
Click required to flip
|
||||
</span>
|
||||
}
|
||||
onBlur={(e) => {
|
||||
console.log("IN BLUR: ", e.target.value);
|
||||
urlPathQueries[index].name = e.target.value.replaceAll(
|
||||
"=",
|
||||
""
|
||||
);
|
||||
|
||||
setUrlPathQueries(urlPathQueries);
|
||||
}}
|
||||
style={{flex: 3}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth={true}
|
||||
defaultValue={data.example}
|
||||
placeholder={"Default value"}
|
||||
label={"Example"}
|
||||
onBlur={(e) => {
|
||||
urlPathQueries[index].example = e.target.value.replaceAll(
|
||||
"=",
|
||||
""
|
||||
)
|
||||
|
||||
setUrlPathQueries(urlPathQueries)
|
||||
}}
|
||||
style={{flex: 2}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
@@ -2617,31 +2750,6 @@ const AppCreator = (props) => {
|
||||
{data.required.toString()}
|
||||
</div>
|
||||
</div>
|
||||
<TextField
|
||||
required
|
||||
fullWidth={true}
|
||||
defaultValue={data.name}
|
||||
placeholder={"Query name"}
|
||||
helperText={
|
||||
<span style={{ color: "white", marginBottom: "2px" }}>
|
||||
Click required switch
|
||||
</span>
|
||||
}
|
||||
onBlur={(e) => {
|
||||
console.log("IN BLUR: ", e.target.value);
|
||||
urlPathQueries[index].name = e.target.value.replaceAll(
|
||||
"=",
|
||||
""
|
||||
);
|
||||
|
||||
setUrlPathQueries(urlPathQueries);
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{ float: "right", color: "#f85a3e", cursor: "pointer" }}
|
||||
@@ -2649,7 +2757,7 @@ const AppCreator = (props) => {
|
||||
deletePathQuery(index);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
<DeleteIcon />
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
@@ -3290,7 +3398,6 @@ const AppCreator = (props) => {
|
||||
onChange={(e) => {
|
||||
setActionField("url", e.target.value);
|
||||
setUrlPath(e.target.value);
|
||||
console.log(e.target.value);
|
||||
}}
|
||||
helperText={
|
||||
<span style={{ color: "white", marginBottom: "2px" }}>
|
||||
@@ -3369,6 +3476,23 @@ const AppCreator = (props) => {
|
||||
if (request.header !== undefined && request.header !== null) {
|
||||
var headers = [];
|
||||
for (let [key, value] of Object.entries(request.header)) {
|
||||
if (value === undefined) {
|
||||
if (key.includes(":")) {
|
||||
const keysplit = key.split(":")
|
||||
key = keysplit[0].trim()
|
||||
value = keysplit[1].trim()
|
||||
|
||||
} else if (key.includes("=")) {
|
||||
const keysplit = key.split("=")
|
||||
key = keysplit[0].trim()
|
||||
value = keysplit[1].trim()
|
||||
|
||||
} else {
|
||||
alert.error("Removed key: ", key)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
parameterName !== undefined &&
|
||||
key.toLowerCase() === parameterName.toLowerCase()
|
||||
@@ -3376,17 +3500,14 @@ const AppCreator = (props) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
key === "Authorization" &&
|
||||
authenticationOption === "Bearer auth"
|
||||
) {
|
||||
if (key === "Authorization") {
|
||||
continue;
|
||||
}
|
||||
|
||||
headers += key + "=" + value + "\n";
|
||||
}
|
||||
|
||||
setActionField("headers", headers);
|
||||
setActionField("headers", headers.trim());
|
||||
}
|
||||
|
||||
if (request.body !== undefined && request.body !== null) {
|
||||
@@ -3444,7 +3565,38 @@ const AppCreator = (props) => {
|
||||
}
|
||||
|
||||
if (parsedurl.includes("?")) {
|
||||
parsedurl = parsedurl.split("?")[0]
|
||||
const parsedurlsplit = parsedurl.split("?")
|
||||
parsedurl = parsedurlsplit[0]
|
||||
|
||||
//var newqueries = selectedAction.queries === undefined || selectedAction.queries === null ? [] : selectedAction.queries
|
||||
|
||||
const datasplit = parsedurlsplit[1].split("&")
|
||||
for (var key in datasplit) {
|
||||
console.log("Data: ", datasplit[key])
|
||||
var actualkey = datasplit[key]
|
||||
var example = ""
|
||||
if (datasplit[key].includes("=")) {
|
||||
actualkey = datasplit[key].split("=")[0]
|
||||
example = datasplit[key].split("=")[1]
|
||||
}
|
||||
|
||||
const foundPath = urlPathQueries.find(data => data.name === actualkey)
|
||||
if (foundPath === null || foundPath === undefined) {
|
||||
urlPathQueries.push({ name: actualkey, example: example, required: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Found that dashes in the URL doesn't work
|
||||
parsedurl = parsedurl.replace("-", "_")
|
||||
console.log("Actions: ", actions)
|
||||
|
||||
if (baseUrl.length === 0 && parsedurl.includes("http")) {
|
||||
const newurl = new URL(encodeURI(parsedurl))
|
||||
newurl.searchParams.delete(parameterName)
|
||||
console.log("New url: ", newurl)
|
||||
parsedurl = newurl.pathname
|
||||
setBaseUrl(newurl.origin)
|
||||
}
|
||||
|
||||
if (event.target.value !== parsedurl) {
|
||||
@@ -3578,7 +3730,7 @@ const AppCreator = (props) => {
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
variant={urlPath.length > 0 ? "contained" : "outlined"}
|
||||
style={{ borderRadius: "0px" }}
|
||||
onClick={() => {
|
||||
//console.log(urlPathQueries)
|
||||
@@ -3656,15 +3808,21 @@ const AppCreator = (props) => {
|
||||
}
|
||||
style={{ backgroundColor: inputColor, color: "white", height: "50px" }}
|
||||
>
|
||||
{categories.map((data, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
style={{ backgroundColor: inputColor, color: "white" }}
|
||||
value={data}
|
||||
>
|
||||
{data}
|
||||
</MenuItem>
|
||||
))}
|
||||
{categories.map((data, index) => {
|
||||
if (data === undefined || data === null || data === "") {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
style={{ backgroundColor: inputColor, color: "white" }}
|
||||
value={data}
|
||||
>
|
||||
{data}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
<h4>Tags</h4>
|
||||
<ChipInput
|
||||
@@ -4290,7 +4448,7 @@ const AppCreator = (props) => {
|
||||
<Button
|
||||
color="primary"
|
||||
style={{ marginTop: "20px", borderRadius: "0px" }}
|
||||
variant="outlined"
|
||||
variant={actions.length === 0 ? "contained" : "outlined"}
|
||||
onClick={() => {
|
||||
setCurrentAction({
|
||||
name: "",
|
||||
@@ -4394,7 +4552,7 @@ const AppCreator = (props) => {
|
||||
|
||||
const imageInfo = (
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
crossOrigin="anonymous"
|
||||
src={imageData}
|
||||
id="logo"
|
||||
style={{
|
||||
@@ -4676,12 +4834,15 @@ const AppCreator = (props) => {
|
||||
for (var key in invalid) {
|
||||
if (e.target.value.includes(invalid[key])) {
|
||||
alert.error("Can't use " + invalid[key] + " in name");
|
||||
setName(e.target.value.replaceAll(".", "").replaceAll("#", "").replaceAll(":", "").replaceAll(",", ""))
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.target.value.length > 29) {
|
||||
alert.error("Choose a shorter name (max 29).");
|
||||
setName(e.target.value.slice(0,28))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4784,6 +4945,7 @@ const AppCreator = (props) => {
|
||||
if (tmpstring.endsWith("/")) {
|
||||
tmpstring = tmpstring.slice(0, -1);
|
||||
}
|
||||
|
||||
if (
|
||||
tmpstring.length > 4 &&
|
||||
!tmpstring.startsWith("http") &&
|
||||
@@ -4792,7 +4954,12 @@ const AppCreator = (props) => {
|
||||
alert.error("URL must start with http(s)://");
|
||||
}
|
||||
|
||||
//if (authenticationOption === "No authentication" &&
|
||||
if (tmpstring.includes("?")) {
|
||||
var newtmp = tmpstring.split("?")
|
||||
if (tmpstring.length > 1) {
|
||||
tmpstring = newtmp[0]
|
||||
}
|
||||
}
|
||||
|
||||
setBaseUrl(tmpstring);
|
||||
}}
|
||||
@@ -4800,6 +4967,13 @@ const AppCreator = (props) => {
|
||||
<div style={{padding: 25, border: "2px solid rgba(255,255,255,0.7)", borderRadius: theme.palette.borderRadius, }}>
|
||||
<FormControl style={{ }} variant="outlined">
|
||||
<Typography variant="h6">Authentication</Typography>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://shuffler.io/docs/app_creation#authentication"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
Learn more about app authentication
|
||||
</a>
|
||||
<Select
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
@@ -4809,6 +4983,14 @@ const AppCreator = (props) => {
|
||||
} else {
|
||||
setAuthenticationRequired(true);
|
||||
}
|
||||
|
||||
if (e.target.value === "Oauth2") {
|
||||
if (parameterLocation === "Header") {
|
||||
setParameterLocation("")
|
||||
}
|
||||
|
||||
setExtraAuth([])
|
||||
}
|
||||
}}
|
||||
value={authenticationOption}
|
||||
style={{
|
||||
|
||||
@@ -18,12 +18,13 @@ import {
|
||||
import { Link as LinkIcon, Edit as EditIcon } from "@material-ui/icons";
|
||||
|
||||
const Body = {
|
||||
maxWidth: "1000px",
|
||||
minWidth: "768px",
|
||||
maxWidth: 1000,
|
||||
minWidth: 768,
|
||||
margin: "auto",
|
||||
display: "flex",
|
||||
height: "100%",
|
||||
color: "white",
|
||||
position: "relative",
|
||||
//textAlign: "center",
|
||||
};
|
||||
|
||||
@@ -72,14 +73,20 @@ const Docs = (props) => {
|
||||
position: "relative",
|
||||
padding: 30,
|
||||
paddingTop: 15,
|
||||
height: "80vh",
|
||||
marginTop: 15,
|
||||
minHeight: "50vh",
|
||||
//height: "50vh",
|
||||
};
|
||||
|
||||
const SideBar = {
|
||||
maxWidth: 250,
|
||||
flex: "1",
|
||||
position: "fixed",
|
||||
flex: 1,
|
||||
position: "sticky",
|
||||
top: 100,
|
||||
maxHeight: "83vh",
|
||||
overflowX: "hidden",
|
||||
overflowY: "auto",
|
||||
zIndex: 10003,
|
||||
};
|
||||
|
||||
const fetchDocList = () => {
|
||||
@@ -295,8 +302,8 @@ const Docs = (props) => {
|
||||
flex: "1",
|
||||
maxWidth: mobile ? "100%" : 750,
|
||||
overflow: "hidden",
|
||||
paddingBottom: 200,
|
||||
marginLeft: mobile ? 0 : 275,
|
||||
paddingBottom: 100,
|
||||
marginLeft: mobile ? 0 : 50,
|
||||
};
|
||||
|
||||
function OuterLink(props) {
|
||||
@@ -649,8 +656,9 @@ const Docs = (props) => {
|
||||
// </Dialog>
|
||||
// {imageModal}
|
||||
|
||||
// Padding and zIndex etc set because of footer in cloud.
|
||||
const loadedCheck = (
|
||||
<div>
|
||||
<div style={{minHeight: 1000, paddingBottom: 100, zIndex: 50000, }}>
|
||||
<BrowserView>{postDataBrowser}</BrowserView>
|
||||
<MobileView>{postDataMobile}</MobileView>
|
||||
</div>
|
||||
|
||||
@@ -384,12 +384,12 @@ const Settings = (props) => {
|
||||
<Grid item xs={4} style={{ borderRadius: theme.palette.borderRadius }}>
|
||||
<Paper style={innerPaperStyle}>
|
||||
<img
|
||||
src={data.image_thumbnail_url}
|
||||
src={data.image}
|
||||
alt={data.name}
|
||||
style={{ width: "100%", marginBottom: 10 }}
|
||||
/>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{data.collection.name}
|
||||
{data.collection}
|
||||
</Typography>
|
||||
<Typography variant="body2">{data.name}</Typography>
|
||||
</Paper>
|
||||
@@ -746,7 +746,7 @@ const Settings = (props) => {
|
||||
>
|
||||
<Typography>
|
||||
<img
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Ethereum-icon-purple.svg/480px-Ethereum-icon-purple.svg.png"
|
||||
src="/images/social/ethereum.png"
|
||||
alt="ethereum-icon"
|
||||
style={{ height: 30 }}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useContext } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { useTheme } from "@material-ui/core/styles";
|
||||
|
||||
import SecurityFramework from '../components/SecurityFramework.jsx';
|
||||
import { ShepherdTour, ShepherdTourContext } from 'react-shepherd'
|
||||
|
||||
|
||||
import {
|
||||
Badge,
|
||||
Avatar,
|
||||
@@ -153,7 +157,7 @@ export const GetIconInfo = (action) => {
|
||||
},
|
||||
{
|
||||
key: "repeat",
|
||||
values: ["repeat", "retry", "pause", "skip", "copy", "replicat"],
|
||||
values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo", ],
|
||||
},
|
||||
{ key: "execute", values: ["execute", "run", "play", "raise"] },
|
||||
{ key: "extract", values: ["extract", "unpack", "decompress", "open"] },
|
||||
@@ -398,12 +402,31 @@ export const validateJson = (showResult) => {
|
||||
|
||||
var result = showResult;
|
||||
try {
|
||||
const result = jsonvalid ? JSON.parse(showResult) : showResult;
|
||||
result = jsonvalid ? JSON.parse(showResult) : showResult;
|
||||
} catch (e) {
|
||||
//console.log("Failed parsing JSON even though its valid: ", e)
|
||||
////console.log("Failed parsing JSON even though its valid: ", e)
|
||||
jsonvalid = false;
|
||||
}
|
||||
|
||||
if (jsonvalid === false) {
|
||||
|
||||
if (typeof showResult === 'string') {
|
||||
showResult = showResult.trim()
|
||||
}
|
||||
|
||||
try {
|
||||
var newstr = showResult.replaceAll("'", '"')
|
||||
|
||||
//console.log("Try replacements and trimming with new value: ", newstr)
|
||||
result = JSON.parse(newstr)
|
||||
jsonvalid = true
|
||||
} catch (e) {
|
||||
|
||||
//console.log("Failed parsing JSON even though its valid (2): ", e)
|
||||
jsonvalid = false
|
||||
}
|
||||
}
|
||||
|
||||
//console.log("VALID: ", jsonvalid, result)
|
||||
return {
|
||||
valid: jsonvalid,
|
||||
@@ -458,6 +481,8 @@ const Workflows = (props) => {
|
||||
const [submitLoading, setSubmitLoading] = React.useState(false);
|
||||
const [actionImageList, setActionImageList] = React.useState([]);
|
||||
|
||||
const [firstLoad, setFirstLoad] = React.useState(true);
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
@@ -839,6 +864,11 @@ const Workflows = (props) => {
|
||||
|
||||
setFilteredWorkflows(responseJson);
|
||||
setWorkflowDone(true);
|
||||
|
||||
// Ensures the zooming happens only once per load
|
||||
setTimeout(() => {
|
||||
setFirstLoad(false)
|
||||
}, 100)
|
||||
} else {
|
||||
if (isLoggedIn) {
|
||||
alert.error("An error occurred while loading workflows");
|
||||
@@ -1764,6 +1794,7 @@ const Workflows = (props) => {
|
||||
if (file.type !== "application/json") {
|
||||
if (file.type !== undefined) {
|
||||
alert.error("File has to contain valid json");
|
||||
setImportLoading(false);
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -2410,7 +2441,104 @@ const Workflows = (props) => {
|
||||
</span>
|
||||
);
|
||||
|
||||
const tourOptions = {
|
||||
defaultStepOptions: {
|
||||
classes: "shadow-md bg-purple-dark",
|
||||
scrollTo: true
|
||||
},
|
||||
useModalOverlay: true,
|
||||
tourName: workflows,
|
||||
exitOnEsc: true,
|
||||
}
|
||||
|
||||
//classes: "custom-class-name-1 custom-class-name-2",
|
||||
const newSteps = [
|
||||
{
|
||||
id: "intro",
|
||||
scrollTo: true,
|
||||
beforeShowPromise: function() {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
window.scrollTo(0, 0);
|
||||
resolve();
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
classes: "shepherd-button-primary",
|
||||
style: {
|
||||
backgroundColor: "red",
|
||||
color: "white",
|
||||
},
|
||||
text: "Next",
|
||||
type: "next"
|
||||
}
|
||||
],
|
||||
highlightClass: "highlight",
|
||||
showCancelLink: true,
|
||||
text: [
|
||||
"React-Shepherd is a JavaScript library for guiding users through your React app."
|
||||
],
|
||||
when: {
|
||||
show: () => {
|
||||
console.log("show step 1");
|
||||
},
|
||||
hide: () => {
|
||||
console.log("hide step 1");
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "second",
|
||||
attachTo: {
|
||||
element: "second-step",
|
||||
on: "top"
|
||||
},
|
||||
text: [
|
||||
"Yuk eksplorasi hasil Tes Minat Bakat-mu dan rekomendasi <b>Jurusan</b> dan Karier."
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
classes: "btn btn-info",
|
||||
text: "Kembali",
|
||||
type: "back"
|
||||
},
|
||||
{
|
||||
classes: "btn btn-success",
|
||||
text: "Saya Mengerti",
|
||||
type: "cancel"
|
||||
}
|
||||
],
|
||||
when: {
|
||||
show: () => {
|
||||
console.log("show stepp");
|
||||
},
|
||||
hide: () => {
|
||||
console.log("complete step");
|
||||
}
|
||||
},
|
||||
showCancelLink: false,
|
||||
scrollTo: true,
|
||||
modalOverlayOpeningPadding: 4,
|
||||
useModalOverlay: false,
|
||||
canClickTarget: false
|
||||
}
|
||||
]
|
||||
|
||||
function TourButton() {
|
||||
const tour = useContext(ShepherdTourContext);
|
||||
|
||||
return (
|
||||
<Button variant="contained" color="primary" onClick={tour.start}>
|
||||
Start Tour
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
//import { ShepherdTour, ShepherdTourContext } from 'react-shepherd'
|
||||
const WorkflowView = () => {
|
||||
/*
|
||||
if (workflows.length === 0) {
|
||||
return (
|
||||
<div style={emptyWorkflowStyle}>
|
||||
@@ -2438,6 +2566,7 @@ const Workflows = (props) => {
|
||||
</div>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
id="second-step"
|
||||
color="primary"
|
||||
style={{ marginTop: "20px" }}
|
||||
variant="outlined"
|
||||
@@ -2458,11 +2587,15 @@ const Workflows = (props) => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
var workflowDelay = -150
|
||||
var appDelay = -75
|
||||
var appDelay = -75
|
||||
|
||||
|
||||
return (
|
||||
<div style={viewStyle}>
|
||||
|
||||
<div style={workflowViewStyle}>
|
||||
<div style={{ display: "flex" }}>
|
||||
<div style={{ flex: 3 }}>
|
||||
@@ -2521,8 +2654,8 @@ const Workflows = (props) => {
|
||||
<Typography style={{ marginTop: 7, marginBottom: "auto" }}>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://shuffler.io/docs/workflows"
|
||||
target="_blank"
|
||||
href="https://shuffler.io/docs/workflows"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
Learn more about Workflows
|
||||
@@ -2581,7 +2714,11 @@ const Workflows = (props) => {
|
||||
data.large_image = theme.palette.defaultImage;
|
||||
}
|
||||
|
||||
appDelay += 75
|
||||
if (firstLoad) {
|
||||
appDelay += 75
|
||||
} else {
|
||||
appDelay = 0
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom key={index} in={true} style={{ transitionDelay: `${appDelay}ms` }}>
|
||||
@@ -2650,7 +2787,11 @@ const Workflows = (props) => {
|
||||
<NewWorkflowPaper />
|
||||
</Zoom>
|
||||
{filteredWorkflows.map((data, index) => {
|
||||
workflowDelay += 75
|
||||
if (firstLoad) {
|
||||
workflowDelay += 75
|
||||
} else {
|
||||
workflowDelay = 0
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
|
||||
@@ -2858,6 +2999,11 @@ const Workflows = (props) => {
|
||||
const loadedCheck =
|
||||
isLoaded && isLoggedIn && workflowDone ? (
|
||||
<div>
|
||||
{/*
|
||||
<ShepherdTour steps={newSteps} tourOptions={tourOptions}>
|
||||
<TourButton />
|
||||
</ShepherdTour>
|
||||
*/}
|
||||
<Dropzone
|
||||
style={{
|
||||
maxWidth: window.innerWidth > 1366 ? 1366 : 1200,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
docker network create -d overlay shuffle_prod
|
||||
@@ -0,0 +1,39 @@
|
||||
version: '3.4'
|
||||
services:
|
||||
orborus:
|
||||
image: ghcr.io/frikky/shuffle-orborus:nightly
|
||||
#hostname: shuffle-orborus
|
||||
environment:
|
||||
#SHUFFLE_WORKER_VERSION: nightly
|
||||
SHUFFLE_APP_SDK_VERSION: 0.8.97
|
||||
SHUFFLE_WORKER_VERSION: nightly
|
||||
BASE_URL: http://<BACKEND>:5001
|
||||
#BASE_URL: http://192.168.86.37:5001
|
||||
CLEANUP: 'true'
|
||||
DOCKER_API_VERSION: '1.40'
|
||||
ENVIRONMENT_NAME: Shuffle
|
||||
HTTPS_PROXY: ''
|
||||
HTTP_PROXY: ''
|
||||
ORG_ID: Shuffle
|
||||
SHUFFLE_BASE_IMAGE_NAME: frikky
|
||||
SHUFFLE_BASE_IMAGE_REGISTRY: ghcr.io
|
||||
SHUFFLE_BASE_IMAGE_TAG_SUFFIX: -0.8.80
|
||||
SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY: '50'
|
||||
SHUFFLE_ORBORUS_EXECUTION_TIMEOUT: '800'
|
||||
SHUFFLE_PASS_APP_PROXY: 'FALSE'
|
||||
SHUFFLE_PASS_WORKER_PROXY: 'TRUE'
|
||||
SHUFFLE_SCALE_REPLICAS: 5
|
||||
SHUFFLE_SWARM_NETWORK_NAME: shuffle_prod
|
||||
SHUFFLE_SWARM_CONFIG: "run"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- shuffle_prod
|
||||
#- reverseproxy
|
||||
logging:
|
||||
driver: json-file
|
||||
|
||||
networks:
|
||||
shuffle_prod:
|
||||
driver: overlay
|
||||
external: true
|
||||
@@ -1,3 +1,4 @@
|
||||
docker swarm init
|
||||
chown 1000:1000 -R shuffle-database/
|
||||
docker network create -d overlay shuffle_prod
|
||||
docker stack deploy --compose-file=docker-compose.yml shuffle_swarm
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
docker swarm init
|
||||
chown 1000:1000 -R shuffle-database/
|
||||
docker network create -d overlay shuffle_prod
|
||||
docker stack deploy --compose-file=orborus.yml shuffle_orborus
|
||||
@@ -15,7 +15,7 @@ RUN go get github.com/docker/docker/api/types && \
|
||||
RUN go build
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
|
||||
|
||||
FROM alpine:3.14.2
|
||||
FROM alpine:3.15.0
|
||||
RUN apk add --no-cache bash tzdata
|
||||
COPY --from=builder /app/ /
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
NAME=shuffle-orborus
|
||||
VERSION=0.9.35
|
||||
VERSION=0.9.45
|
||||
|
||||
echo "Running docker build with $NAME:$VERSION"
|
||||
#docker rmi frikky/shuffle:$NAME --force
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
version: '3'
|
||||
services:
|
||||
orborus:
|
||||
image: ghcr.io/frikky/shuffle-orborus:nightly
|
||||
container_name: shuffle-orborus
|
||||
hostname: shuffle-orborus
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- SHUFFLE_APP_SDK_VERSION=nightly
|
||||
- SHUFFLE_WORKER_VERSION=nightly
|
||||
- ORG_ID=Shuffle
|
||||
- ENVIRONMENT_NAME=Shuffle
|
||||
- BASE_URL=http://192.168.86.39:5001
|
||||
- DOCKER_API_VERSION=1.40
|
||||
- SHUFFLE_SCALE_REPLICAS=5
|
||||
- SHUFFLE_SWARM_CONFIG=run
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- shuffle-executions
|
||||
networks:
|
||||
shuffle-executions:
|
||||
driver: overlay
|
||||
external: true
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -27,7 +28,7 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
//"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
//"github.com/docker/docker/api/types/filters"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
@@ -152,7 +153,7 @@ func cleanupExistingNodes(ctx context.Context) error {
|
||||
//log.Printf("\n\nFound %d contaienrs", len(services))
|
||||
|
||||
for _, service := range services {
|
||||
log.Printf("[INFO] Service: %#v", service.Spec.Annotations.Name)
|
||||
//log.Printf("[INFO] Service: %#v", service.Spec.Annotations.Name)
|
||||
|
||||
//portFound := false
|
||||
//for _, endpoint := range service.Spec.EndpointSpec.Ports {
|
||||
@@ -186,7 +187,7 @@ func cleanupExistingNodes(ctx context.Context) error {
|
||||
|
||||
func deployServiceWorkers(image string) {
|
||||
log.Printf("[DEBUG] Validating deployment of workers as services IF swarmConfig = run (value: %#v)", swarmConfig)
|
||||
if swarmConfig == "run" {
|
||||
if swarmConfig == "run" || swarmConfig == "swarm" {
|
||||
ctx := context.Background()
|
||||
// Looks for and cleans up all existing items in swarm we can't re-use (Shuffle only)
|
||||
cleanupExistingNodes(ctx)
|
||||
@@ -198,7 +199,8 @@ func deployServiceWorkers(image string) {
|
||||
|
||||
//docker network create --driver=overlay workers
|
||||
networkCreateOptions := types.NetworkCreate{
|
||||
Driver: "overlay",
|
||||
Driver: "overlay",
|
||||
Attachable: true,
|
||||
}
|
||||
_, err := dockercli.NetworkCreate(
|
||||
ctx,
|
||||
@@ -208,11 +210,29 @@ func deployServiceWorkers(image string) {
|
||||
|
||||
if err != nil {
|
||||
if strings.Contains(fmt.Sprintf("%s", err), "already exists") {
|
||||
// Try patching for attachable
|
||||
|
||||
} else {
|
||||
log.Printf("[DEBUG] Failed to create network %s for workers: %s. This is not critical, and containers will still be added", networkName, err)
|
||||
}
|
||||
}
|
||||
|
||||
defaultNetworkAttach := false
|
||||
if containerId != "" {
|
||||
log.Printf("[WARNING] Should connect orborus container to worker network as it's running in Docker with name %#v!", containerId)
|
||||
// https://pkg.go.dev/github.com/docker/docker@v20.10.12+incompatible/api/types/network#EndpointSettings
|
||||
networkConfig := &network.EndpointSettings{}
|
||||
err := dockercli.NetworkConnect(ctx, networkName, containerId, networkConfig)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed connecting to Orborus to docker network %s: %s", networkName, err)
|
||||
}
|
||||
|
||||
if len(containerId) == 64 && baseUrl == "http://shuffle-backend:5001" {
|
||||
log.Printf("[WARNING] Network MAY not work due to backend being %s and container length 64. Will try to attach shuffle_shuffle network", baseUrl)
|
||||
defaultNetworkAttach = true
|
||||
}
|
||||
}
|
||||
|
||||
//serviceOptions := types.ServiceCreateOptions{}
|
||||
//service, err := dockercli.ServiceCreate(
|
||||
// context.Background(),
|
||||
@@ -250,7 +270,7 @@ func deployServiceWorkers(image string) {
|
||||
log.Printf("[DEBUG] Found %d node(s) to replicate over. Defaulting to 1 IF we can't auto-discover them.", cnt)
|
||||
replicatedJobs := uint64(replicas * nodeCount)
|
||||
|
||||
log.Printf("[DEBUG] Deploying %d containers for worker with swarm to each node. Service name: %s. Image: %s", replicas, innerContainerName, image)
|
||||
log.Printf("[DEBUG] Deploying %d container(s) for worker with swarm to each node. Service name: %s. Image: %s", replicas, innerContainerName, image)
|
||||
|
||||
if timezone == "" {
|
||||
timezone = "Europe/Amsterdam"
|
||||
@@ -316,6 +336,15 @@ func deployServiceWorkers(image string) {
|
||||
},
|
||||
}
|
||||
|
||||
if defaultNetworkAttach == true {
|
||||
serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{
|
||||
Target: "shuffle_shuffle",
|
||||
})
|
||||
|
||||
// FIXM: Remove this if deployment fails?
|
||||
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=shuffle_shuffle"))
|
||||
}
|
||||
|
||||
if dockerApiVersion != "" {
|
||||
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion))
|
||||
}
|
||||
@@ -332,12 +361,27 @@ func deployServiceWorkers(image string) {
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] Successfully deployed workers with %d replica(s) on %d nodes", replicas, cnt)
|
||||
log.Printf("[DEBUG] Successfully deployed workers with %d replica(s) on %d node(s)", replicas, cnt)
|
||||
//time.Sleep(time.Duration(10) * time.Second)
|
||||
//log.Printf("[DEBUG] Servicecreate request: %#v %#v", service, err)
|
||||
} else {
|
||||
if !strings.Contains(fmt.Sprintf("%s", err), "Already Exists") && !strings.Contains(fmt.Sprintf("%s", err), "is already in use by service") {
|
||||
log.Printf("[ERROR] Failed making service: %s", err)
|
||||
} else {
|
||||
log.Printf("[WARNING] Failed deploying workers: %s", err)
|
||||
if len(serviceSpec.Networks) > 1 {
|
||||
serviceSpec.Networks = []swarm.NetworkAttachmentConfig{
|
||||
swarm.NetworkAttachmentConfig{
|
||||
Target: "shuffle_shuffle",
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = dockercli.ServiceCreate(
|
||||
ctx,
|
||||
serviceSpec,
|
||||
serviceOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +421,7 @@ func deployWorker(image string, identifier string, env []string, executionReques
|
||||
|
||||
//var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG")
|
||||
parsedUuid := uuid.NewV4()
|
||||
if swarmConfig == "run" {
|
||||
if swarmConfig == "run" || swarmConfig == "swarm" {
|
||||
go func() {
|
||||
err := sendWorkerRequest(executionRequest)
|
||||
if err != nil {
|
||||
@@ -393,8 +437,8 @@ func deployWorker(image string, identifier string, env []string, executionReques
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] Started worker from request with name: %s", executionRequest.ExecutionId)
|
||||
executionIds = append(executionIds, executionRequest.ExecutionId)
|
||||
// FIXME: Readd this? Removed for rerun reasons
|
||||
// executionIds = append(executionIds, executionRequest.ExecutionId)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -624,6 +668,42 @@ func findActiveSwarmNodes() (int64, error) {
|
||||
*/
|
||||
}
|
||||
|
||||
// Get IP
|
||||
func getLocalIP() string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, address := range addrs {
|
||||
// check the address type and if it is not a loopback the display it
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkSwarmService(ctx context.Context) {
|
||||
// https://docs.docker.com/engine/reference/commandline/swarm_init/
|
||||
ip := getLocalIP()
|
||||
log.Printf("[DEBUG] Attempting swarm setup on %s", ip)
|
||||
req := swarm.InitRequest{
|
||||
ListenAddr: fmt.Sprintf("0.0.0.0:2377", ip),
|
||||
AdvertiseAddr: fmt.Sprintf("%s:2377", ip),
|
||||
}
|
||||
|
||||
ret, err := dockercli.SwarmInit(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Swarm init: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Swarm info: %s\n\n", ret)
|
||||
}
|
||||
|
||||
// Initial loop etc
|
||||
func main() {
|
||||
log.Println("[INFO] Setting up execution environment")
|
||||
@@ -685,6 +765,10 @@ func main() {
|
||||
log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
|
||||
|
||||
initializeImages()
|
||||
if swarmConfig == "run" || swarmConfig == "swarm" {
|
||||
checkSwarmService(ctx)
|
||||
|
||||
}
|
||||
|
||||
//workerName := "worker"
|
||||
//workerVersion := "0.1.0"
|
||||
@@ -727,20 +811,20 @@ func main() {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed making request builder: %s", err)
|
||||
log.Printf("[ERROR] Failed making request builder during init: %s", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
zombiecounter := 0
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Org-Id", orgId)
|
||||
log.Printf("[INFO] Waiting for executions at %s", fullUrl)
|
||||
log.Printf("[INFO] Waiting for executions at %s with Org ID %s", fullUrl, orgId)
|
||||
hasStarted := false
|
||||
for {
|
||||
//log.Printf("Prerequest")
|
||||
//go getStats()
|
||||
newresp, err := client.Do(req)
|
||||
//log.Printf("Prerequest")
|
||||
//log.Printf("Postrequest")
|
||||
newresp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed making request: %s", err)
|
||||
zombiecounter += 1
|
||||
@@ -758,6 +842,10 @@ func main() {
|
||||
log.Printf("[WARNING] Bad statuscode: %d", newresp.StatusCode)
|
||||
}
|
||||
} else {
|
||||
if !hasStarted {
|
||||
log.Printf("[DEBUG] Starting iteration. Got statuscode %d from backend on first request", newresp.StatusCode)
|
||||
}
|
||||
|
||||
hasStarted = true
|
||||
}
|
||||
|
||||
@@ -793,7 +881,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Skipping throttling with swarm
|
||||
if swarmConfig != "run" {
|
||||
if swarmConfig != "run" && swarmConfig != "swarm" {
|
||||
if len(executionRequests.Data) == 0 {
|
||||
zombiecounter += 1
|
||||
if zombiecounter*sleepTime > workerTimeout {
|
||||
@@ -1008,7 +1096,7 @@ func getRunningWorkers(ctx context.Context, workerTimeout int) int {
|
||||
// Should it check what happened to the execution? idk
|
||||
func zombiecheck(ctx context.Context, workerTimeout int) error {
|
||||
executionIds = []string{}
|
||||
if swarmConfig == "run" {
|
||||
if swarmConfig == "run" || swarmConfig == "swarm" {
|
||||
//log.Printf("[DEBUG] Skipping Zombie check due to new execution model (swarm)")
|
||||
return nil
|
||||
}
|
||||
@@ -1160,10 +1248,11 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error {
|
||||
|
||||
body, err := ioutil.ReadAll(newresp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed reading body in worker request: %s", err)
|
||||
log.Printf("[ERROR] Failed reading body in worker request body: %s", err)
|
||||
return err
|
||||
}
|
||||
_ = body
|
||||
|
||||
log.Printf("[DEBUG] NEWRESP (from worker request %s): %s (Status: %d)", workflowExecution.ExecutionId, string(body), newresp.StatusCode)
|
||||
log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s.\n\n DEBUGGING: docker service logs shuffle-workers | grep %s\n\n", workflowExecution.ExecutionId, streamUrl, workflowExecution.ExecutionId)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ WORKDIR /app
|
||||
#RUN go env -w GO111MODULE=auto
|
||||
COPY worker.go /app/worker.go
|
||||
COPY go.mod /app/go.mod
|
||||
#COPY go.sum /app/go.sum
|
||||
#RUN go
|
||||
#COPY go.sum /app/go.sum
|
||||
RUN go get
|
||||
@@ -26,7 +27,7 @@ RUN go build
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker .
|
||||
|
||||
## ALPINE IMAGE
|
||||
FROM alpine:3.14.2
|
||||
FROM alpine:3.15.0
|
||||
|
||||
ENV SHUFFLE_BASE_IMAGE_REGISTRY=docker.io
|
||||
ENV SHUFFLE_BASE_IMAGE_NAME=frikky/shuffle
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
NAME=shuffle-worker
|
||||
VERSION=0.9.36
|
||||
VERSION=0.9.45
|
||||
|
||||
echo "Running docker build with $NAME:$VERSION"
|
||||
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
|
||||
|
||||
@@ -10,6 +10,6 @@ require (
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/shuffle/shuffle-shared v0.1.35
|
||||
github.com/shuffle/shuffle-shared v0.1.60
|
||||
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||
)
|
||||
|
||||
@@ -574,6 +574,12 @@ github.com/shuffle/shuffle-shared v0.1.33 h1:1U0yKWNfW7K7EKOj2aqSmd20UIA+nJeIurG
|
||||
github.com/shuffle/shuffle-shared v0.1.33/go.mod h1:0QrK51T12CpCj/be8hXduj/RtDnoeaZ3rfogELZE2IU=
|
||||
github.com/shuffle/shuffle-shared v0.1.35 h1:CoCur/G+TaM2xiLgDCVdVxPhFffNK/4YRWTtzRBprvg=
|
||||
github.com/shuffle/shuffle-shared v0.1.35/go.mod h1:2ndjLm4ZOvY6arGFwOgGnkQ457Ke7gka9HDF/EkdIxQ=
|
||||
github.com/shuffle/shuffle-shared v0.1.54 h1:dHpwot+5RPX8k9EC/8Yd+QYsFqCsqsv+J1wC+EtGxzI=
|
||||
github.com/shuffle/shuffle-shared v0.1.54/go.mod h1:2ndjLm4ZOvY6arGFwOgGnkQ457Ke7gka9HDF/EkdIxQ=
|
||||
github.com/shuffle/shuffle-shared v0.1.55 h1:feHtTN7Uhr1aMxkMIo3xZbr97599VB2eLekogTj/9Z4=
|
||||
github.com/shuffle/shuffle-shared v0.1.55/go.mod h1:2ndjLm4ZOvY6arGFwOgGnkQ457Ke7gka9HDF/EkdIxQ=
|
||||
github.com/shuffle/shuffle-shared v0.1.60 h1:Jjb6TfE/KnVfCryIL2vtRHBnBX307slVlGBjaEqgwW4=
|
||||
github.com/shuffle/shuffle-shared v0.1.60/go.mod h1:2ndjLm4ZOvY6arGFwOgGnkQ457Ke7gka9HDF/EkdIxQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
|
||||
+441
-119
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user