Resolved the conflicts
This commit is contained in:
@@ -40,6 +40,7 @@ BACKEND_HOSTNAME=shuffle-backend
|
|||||||
BACKEND_PORT=5001
|
BACKEND_PORT=5001
|
||||||
FRONTEND_PORT=3001
|
FRONTEND_PORT=3001
|
||||||
FRONTEND_PORT_HTTPS=3443
|
FRONTEND_PORT_HTTPS=3443
|
||||||
|
AUTH_FOR_ORBORUS =
|
||||||
|
|
||||||
# CHANGE THIS IF YOU WANT GOOD LOCAL EXECUTIONS:
|
# CHANGE THIS IF YOU WANT GOOD LOCAL EXECUTIONS:
|
||||||
OUTER_HOSTNAME=shuffle-backend
|
OUTER_HOSTNAME=shuffle-backend
|
||||||
@@ -97,14 +98,15 @@ SHUFFLE_MAX_EXECUTION_DEPTH=
|
|||||||
DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
||||||
#SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200
|
#SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200
|
||||||
SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200
|
SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200
|
||||||
SHUFFLE_OPENSEARCH_USERNAME="admin"
|
|
||||||
SHUFFLE_OPENSEARCH_PASSWORD="StrongShufflePassword321!"
|
|
||||||
SHUFFLE_OPENSEARCH_CERTIFICATE_FILE=
|
SHUFFLE_OPENSEARCH_CERTIFICATE_FILE=
|
||||||
SHUFFLE_OPENSEARCH_APIKEY=
|
SHUFFLE_OPENSEARCH_APIKEY=
|
||||||
SHUFFLE_OPENSEARCH_CLOUDID=
|
SHUFFLE_OPENSEARCH_CLOUDID=
|
||||||
SHUFFLE_OPENSEARCH_PROXY=
|
SHUFFLE_OPENSEARCH_PROXY=
|
||||||
SHUFFLE_OPENSEARCH_INDEX_PREFIX=
|
SHUFFLE_OPENSEARCH_INDEX_PREFIX=
|
||||||
SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true
|
SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true
|
||||||
|
SHUFFLE_OPENSEARCH_USERNAME="admin"
|
||||||
|
SHUFFLE_OPENSEARCH_PASSWORD="StrongShufflePassword321!" # In use for the first time setup of OpenSearch + backend of Shuffle
|
||||||
|
OPENSEARCH_INITIAL_ADMIN_PASSWORD="StrongShufflePassword321!" # In use for the first time setup of OpenSearch
|
||||||
|
|
||||||
#Tenzir related
|
#Tenzir related
|
||||||
SHUFFLE_TENZIR_URL=
|
SHUFFLE_TENZIR_URL=
|
||||||
|
|||||||
@@ -632,6 +632,7 @@ class AppBase:
|
|||||||
|
|
||||||
# I wonder if this actually works
|
# I wonder if this actually works
|
||||||
url = "%s%s" % (self.base_url, stream_path)
|
url = "%s%s" % (self.base_url, stream_path)
|
||||||
|
self.logger.info(f"[DEBUG][%s] Sending result to %s" % (self.current_execution_id, url))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log_contents = "disabled: add env SHUFFLE_LOGS_DISABLED=true to Orborus to re-enable logs for apps. Can not be enabled natively in Cloud except in Hybrid mode."
|
log_contents = "disabled: add env SHUFFLE_LOGS_DISABLED=true to Orborus to re-enable logs for apps. Can not be enabled natively in Cloud except in Hybrid mode."
|
||||||
@@ -656,6 +657,13 @@ class AppBase:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Check if type of headers is right
|
||||||
|
if not isinstance(headers, dict):
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
if not "User-Agent" in headers:
|
||||||
|
headers["User-Agent"] = "Shuffle App"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
finished = False
|
finished = False
|
||||||
ret = {}
|
ret = {}
|
||||||
@@ -684,23 +692,23 @@ class AppBase:
|
|||||||
headerauth = headers["Authorization"]
|
headerauth = headers["Authorization"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}'. Execution ID: %d, Authorization: %d, Header Auth: %d" % (len(action_result["execution_id"]), len(action_result["authorization"]), len(headerauth)))
|
self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}'. Execution ID: %d, Authorization: %d, Header Auth: %d" % (len(action_result["execution_id"]), len(action_result["authorization"]), len(headerauth)))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}' (no detail)")
|
self.logger.info(f"[ERROR] Bad resp ({ret.status_code}) in send_result for url '{url}' (no detail)")
|
||||||
pass
|
|
||||||
|
|
||||||
time.sleep(sleeptime)
|
time.sleep(sleeptime)
|
||||||
|
|
||||||
|
|
||||||
# Proxyerrror
|
# Proxyerrror
|
||||||
except requests.exceptions.ProxyError as e:
|
except requests.exceptions.ProxyError as e:
|
||||||
|
self.logger.info(f"[ERROR][{self.current_execution_id}] Proxy error in send_result for url '{url}': {e}")
|
||||||
|
|
||||||
self.proxy_config = {}
|
self.proxy_config = {}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
time.sleep(sleeptime)
|
self.logger.info(f"[ERROR][{self.current_execution_id}] Request error in send_result for url '{url}': {e}")
|
||||||
|
|
||||||
# Check if we have a read timeout. If we do, exit as we most likely sent the result without getting a good result
|
# Check if we have a read timeout. If we do, exit as we most likely sent the result without getting a good result
|
||||||
if "Read timed out" in str(e):
|
if "Read timed out" in str(e):
|
||||||
@@ -713,24 +721,34 @@ class AppBase:
|
|||||||
finished = True
|
finished = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
time.sleep(sleeptime)
|
||||||
|
|
||||||
#time.sleep(5)
|
#time.sleep(5)
|
||||||
continue
|
continue
|
||||||
except TimeoutError as e:
|
except TimeoutError as e:
|
||||||
|
self.logger.info(f"[ERROR][{self.current_execution_id}] Timeout error in send_result for url '{url}': {e}")
|
||||||
|
|
||||||
time.sleep(sleeptime)
|
time.sleep(sleeptime)
|
||||||
|
|
||||||
#time.sleep(5)
|
#time.sleep(5)
|
||||||
continue
|
continue
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
self.logger.info(f"[ERROR][{self.current_execution_id}] Connection error in send_result for url '{url}': {e}")
|
||||||
|
|
||||||
time.sleep(sleeptime)
|
time.sleep(sleeptime)
|
||||||
|
|
||||||
#time.sleep(5)
|
#time.sleep(5)
|
||||||
continue
|
continue
|
||||||
except http.client.RemoteDisconnected as e:
|
except http.client.RemoteDisconnected as e:
|
||||||
|
self.logger.info(f"[ERROR][{self.current_execution_id}] RemoteDisconnected error in send_result for url '{url}': {e}")
|
||||||
|
|
||||||
time.sleep(sleeptime)
|
time.sleep(sleeptime)
|
||||||
|
|
||||||
#time.sleep(5)
|
#time.sleep(5)
|
||||||
continue
|
continue
|
||||||
except urllib3.exceptions.ProtocolError as e:
|
except urllib3.exceptions.ProtocolError as e:
|
||||||
|
self.logger.info(f"[ERROR][{self.current_execution_id}] ProtocolError error in send_result for url '{url}': {e}")
|
||||||
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
#time.sleep(5)
|
#time.sleep(5)
|
||||||
@@ -3668,7 +3686,7 @@ class AppBase:
|
|||||||
#self.logger.info()
|
#self.logger.info()
|
||||||
|
|
||||||
if not multiexecution:
|
if not multiexecution:
|
||||||
self.logger.info("NOT MULTI EXEC")
|
#self.logger.info("NOT MULTI EXEC")
|
||||||
# Runs a single iteration here
|
# Runs a single iteration here
|
||||||
new_params = self.validate_unique_fields(params)
|
new_params = self.validate_unique_fields(params)
|
||||||
if isinstance(new_params, list) and len(new_params) == 1:
|
if isinstance(new_params, list) and len(new_params) == 1:
|
||||||
|
|||||||
+110
-7
@@ -1978,7 +1978,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
|
||||||
if request.Method != "POST" {
|
if request.Method != "POST" {
|
||||||
request.Method = "POST"
|
request.Method = "POST"
|
||||||
}
|
}
|
||||||
@@ -1999,7 +1998,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
location := strings.Split(request.URL.String(), "/")
|
location := strings.Split(request.URL.String(), "/")
|
||||||
|
|
||||||
var pipelineId string
|
var pipelineId string
|
||||||
|
|
||||||
if location[1] == "api" {
|
if location[1] == "api" {
|
||||||
if len(location) <= 4 {
|
if len(location) <= 4 {
|
||||||
log.Printf("[INFO] Couldn't handle location. Too short in pipeline: %d", len(location))
|
log.Printf("[INFO] Couldn't handle location. Too short in pipeline: %d", len(location))
|
||||||
@@ -2013,7 +2012,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
userAgent := request.Header.Get("User-Agent")
|
userAgent := request.Header.Get("User-Agent")
|
||||||
if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") {
|
if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") {
|
||||||
log.Printf("[AUDIT] Blocking googlebot and microsoftbot for pielines. UA: '%s'", userAgent)
|
log.Printf("[AUDIT] Blocking googlebot and microsoftbot for pipelines. UA: '%s'", userAgent)
|
||||||
resp.WriteHeader(400)
|
resp.WriteHeader(400)
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Google/Microsoft preview bots not allowed. Please change the useragent."}`))
|
resp.Write([]byte(`{"success": false, "reason": "Google/Microsoft preview bots not allowed. Please change the useragent."}`))
|
||||||
return
|
return
|
||||||
@@ -2058,11 +2057,27 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedBody := shuffle.GetExecutionbody(body)
|
// Parse concatenated JSON logs
|
||||||
|
jsonList, err := parseConcatenatedJSONLogs(string(body))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[DEBUG] JSON parsing error: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedBody, err := json.Marshal(jsonList)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to marshal jsonList: %s", err)
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
newBody := shuffle.ExecutionStruct{
|
newBody := shuffle.ExecutionStruct{
|
||||||
Start: pipeline.StartNode,
|
Start: pipeline.StartNode,
|
||||||
ExecutionSource: "pipeline",
|
ExecutionSource: "pipeline",
|
||||||
ExecutionArgument: parsedBody,
|
ExecutionArgument: string(parsedBody),
|
||||||
}
|
}
|
||||||
|
|
||||||
workflow, err := shuffle.GetWorkflow(ctx, pipeline.WorkflowId)
|
workflow, err := shuffle.GetWorkflow(ctx, pipeline.WorkflowId)
|
||||||
@@ -2093,8 +2108,7 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(pipeline.StartNode) == 0 {
|
if len(pipeline.StartNode) == 0 {
|
||||||
log.Printf("[WARNING] No start node for pipeline %s - running with workflow default.", pipeline.TriggerId)
|
log.Printf("[WARNING] No start node for pipeline %s - running with workflow default.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newRequest := &http.Request{
|
newRequest := &http.Request{
|
||||||
@@ -2108,6 +2122,9 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
|
||||||
|
|
||||||
|
// Track Sigma rules
|
||||||
|
trackSigmaRules(ctx, pipeline.OrgId, jsonList)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2115,6 +2132,82 @@ func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseConcatenatedJSONLogs(logs string) ([]map[string]interface{}, error) {
|
||||||
|
var jsonList []map[string]interface{}
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(logs))
|
||||||
|
|
||||||
|
for decoder.More() {
|
||||||
|
var jsonObject map[string]interface{}
|
||||||
|
if err := decoder.Decode(&jsonObject); err != nil {
|
||||||
|
log.Printf("[WARNING] JSON decoding error: %s. Skipping this object.", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jsonList = append(jsonList, jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
||||||
|
return nil, fmt.Errorf("error after decoding all JSON objects: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackSigmaRules(ctx context.Context, orgId string, jsonList []map[string]interface{}) {
|
||||||
|
ruleCount := make(map[string]int)
|
||||||
|
for _, logEntry := range jsonList {
|
||||||
|
if rule, ok := logEntry["rule"].(map[string]interface{}); ok {
|
||||||
|
if ruleName, ok := rule["title"].(string); ok {
|
||||||
|
ruleCount[ruleName]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ruleName, count := range ruleCount {
|
||||||
|
shuffle.IncrementCache(ctx, orgId, ruleName, count)
|
||||||
|
log.Printf("[INFO] Rule %s incremented by %d", ruleName, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTenzirHealthUpdate(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
if request.Method != "POST" {
|
||||||
|
request.Method = "POST"
|
||||||
|
}
|
||||||
|
|
||||||
|
type HealthUpdate struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var healthUpdate HealthUpdate
|
||||||
|
err := json.NewDecoder(request.Body).Decode(&healthUpdate)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(resp, "Failed to decode JSON: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
status := healthUpdate.Status
|
||||||
|
|
||||||
|
result, err := shuffle.GetDisabledRules(ctx)
|
||||||
|
if (err != nil && err.Error() == "rules doesn't exist") || err == nil {
|
||||||
|
result.IsTenzirActive = status
|
||||||
|
result.LastActive = time.Now().Unix()
|
||||||
|
|
||||||
|
err = shuffle.StoreDisabledRules(ctx, *result)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.WriteHeader(500)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error {
|
func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error {
|
||||||
data, err := json.Marshal(action)
|
data, err := json.Marshal(action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -5114,6 +5207,7 @@ func initHandlers() {
|
|||||||
// PS: For cloud, this has to use cloud storage.
|
// PS: For cloud, this has to use cloud storage.
|
||||||
// https://developer.box.com/reference/get-files-id-content/
|
// https://developer.box.com/reference/get-files-id-content/
|
||||||
r.HandleFunc("/api/v1/files/download_remote", shuffle.HandleDownloadRemoteFiles).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/files/download_remote", shuffle.HandleDownloadRemoteFiles).Methods("POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/files/download_remote_enhanced", shuffle.HandleEnhancedDownloadRemoteFiles).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/files/create", shuffle.HandleCreateFile).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/files/create", shuffle.HandleCreateFile).Methods("POST", "OPTIONS")
|
||||||
@@ -5122,6 +5216,15 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleGetFileMeta).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleGetFileMeta).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/files/detection/sigma_rules", shuffle.HandleGetSigmaRules).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/files/detection/{fileId}/{action}", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/files/detection/{action}", shuffle.HandleFolderToggle).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
|
r.HandleFunc("/api/v1/detection/siem/connect", shuffle.HandleConnectSiem).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/detection/siem/node_health", handleTenzirHealthUpdate).Methods("POST","OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/detection/{triggerId}/selected_rules", shuffle.HandleGetSelectedRules).Methods("GET","OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/detection/{triggerId}/selected_rules/save", shuffle.HandleSaveSelectedRules).Methods("POST","OPTIONS")
|
||||||
|
|
||||||
|
|
||||||
// Introduced in 0.9.21 to handle notifications for e.g. failed Workflow
|
// Introduced in 0.9.21 to handle notifications for e.g. failed Workflow
|
||||||
r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS")
|
||||||
|
|||||||
+14
-13
@@ -1,7 +1,7 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
image: ghcr.io/shuffle/shuffle-frontend:latest
|
image: ghcr.io/shuffle/shuffle-frontend:nightly
|
||||||
container_name: shuffle-frontend
|
container_name: shuffle-frontend
|
||||||
hostname: shuffle-frontend
|
hostname: shuffle-frontend
|
||||||
ports:
|
ports:
|
||||||
@@ -15,7 +15,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
backend:
|
backend:
|
||||||
image: ghcr.io/shuffle/shuffle-backend:latest
|
image: ghcr.io/shuffle/shuffle-backend:nightly
|
||||||
container_name: shuffle-backend
|
container_name: shuffle-backend
|
||||||
hostname: ${BACKEND_HOSTNAME}
|
hostname: ${BACKEND_HOSTNAME}
|
||||||
# Here for debugging:
|
# Here for debugging:
|
||||||
@@ -34,7 +34,7 @@ services:
|
|||||||
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
orborus:
|
orborus:
|
||||||
image: ghcr.io/shuffle/shuffle-orborus:latest
|
image: ghcr.io/shuffle/shuffle-orborus:nightly
|
||||||
container_name: shuffle-orborus
|
container_name: shuffle-orborus
|
||||||
hostname: shuffle-orborus
|
hostname: shuffle-orborus
|
||||||
networks:
|
networks:
|
||||||
@@ -48,9 +48,6 @@ services:
|
|||||||
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
||||||
- BASE_URL=http://${OUTER_HOSTNAME}:5001
|
- BASE_URL=http://${OUTER_HOSTNAME}:5001
|
||||||
- DOCKER_API_VERSION=1.40
|
- DOCKER_API_VERSION=1.40
|
||||||
- SHUFFLE_BASE_IMAGE_NAME=${SHUFFLE_BASE_IMAGE_NAME}
|
|
||||||
- SHUFFLE_BASE_IMAGE_REGISTRY=${SHUFFLE_BASE_IMAGE_REGISTRY}
|
|
||||||
- SHUFFLE_BASE_IMAGE_TAG_SUFFIX=${SHUFFLE_BASE_IMAGE_TAG_SUFFIX}
|
|
||||||
- HTTP_PROXY=${HTTP_PROXY}
|
- HTTP_PROXY=${HTTP_PROXY}
|
||||||
- HTTPS_PROXY=${HTTPS_PROXY}
|
- HTTPS_PROXY=${HTTPS_PROXY}
|
||||||
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
||||||
@@ -74,7 +71,6 @@ services:
|
|||||||
- node.name=shuffle-opensearch
|
- node.name=shuffle-opensearch
|
||||||
- node.store.allow_mmap=false
|
- node.store.allow_mmap=false
|
||||||
- discovery.seed_hosts=shuffle-opensearch
|
- discovery.seed_hosts=shuffle-opensearch
|
||||||
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=${SHUFFLE_OPENSEARCH_PASSWORD}
|
|
||||||
ulimits:
|
ulimits:
|
||||||
memlock:
|
memlock:
|
||||||
soft: -1
|
soft: -1
|
||||||
@@ -83,7 +79,7 @@ services:
|
|||||||
soft: 65536
|
soft: 65536
|
||||||
hard: 65536
|
hard: 65536
|
||||||
volumes:
|
volumes:
|
||||||
- ${DB_LOCATION}:/usr/share/opensearch/data:z
|
- shuffle-database:/usr/share/opensearch/data:z
|
||||||
ports:
|
ports:
|
||||||
- 9200:9200
|
- 9200:9200
|
||||||
networks:
|
networks:
|
||||||
@@ -129,13 +125,18 @@ services:
|
|||||||
# networks:
|
# networks:
|
||||||
# - shuffle
|
# - shuffle
|
||||||
#
|
#
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
shuffle-database:
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
device: ${DB_LOCATION}
|
||||||
|
o: bind
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
shuffle:
|
shuffle:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
# uncomment to set MTU for swarm mode.
|
|
||||||
# MTU should be whatever is your host's preferred MTU is.
|
|
||||||
# Refer to this doc to figure out what your host's MTU is:
|
|
||||||
# https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors
|
|
||||||
# driver_opts:
|
# driver_opts:
|
||||||
# com.docker.network.driver.mtu: 1460
|
# com.docker.network.driver.mtu: 1460
|
||||||
|
# uncomment to set MTU for swarm mode. MTU should be whatever is your host's preferred MTU is: https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors
|
||||||
|
|||||||
+14
-7
@@ -1,6 +1,8 @@
|
|||||||
# Build environment
|
# Build environment
|
||||||
FROM node:21 as builder
|
FROM node:21 as builder
|
||||||
|
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
|
||||||
RUN mkdir /usr/src/app
|
RUN mkdir /usr/src/app
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||||
@@ -25,28 +27,33 @@ COPY ./*.json /usr/src/app/
|
|||||||
RUN npm run build --loglevel verbose 2>&1
|
RUN npm run build --loglevel verbose 2>&1
|
||||||
|
|
||||||
# Production environment
|
# Production environment
|
||||||
FROM nginx:1.21.5
|
FROM nginx:1.26.0
|
||||||
|
|
||||||
RUN mkdir -p /usr/share/nginx/html/build
|
RUN mkdir -p /usr/share/nginx/html/build
|
||||||
RUN mkdir -p /usr/share/nginx/html/css
|
RUN mkdir -p /usr/share/nginx/html/css
|
||||||
RUN mkdir -p /usr/share/nginx/html/js
|
RUN mkdir -p /usr/share/nginx/html/js
|
||||||
RUN mkdir -p /usr/share/nginx/html/img
|
RUN mkdir -p /usr/share/nginx/html/img
|
||||||
|
|
||||||
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
|
|
||||||
|
|
||||||
#Localhost certificate challenge: Y#XwrJ#DoZGz2w6x
|
# Localhost certificate challenge: Y#XwrJ#DoZGz2w6x
|
||||||
|
# Cert challenge doesn't matter to be here or not, as ALL production setups should be using their own certificates + reverse proxy: https://shuffler.io/docs/configuration#using-the-nginx-reverse-proxy-for-tls/ssl
|
||||||
|
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
|
||||||
COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem
|
COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem
|
||||||
COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem
|
COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem
|
||||||
|
|
||||||
# install CONFD
|
# install CONFD
|
||||||
ENV CONFD_VERSION 0.16.0
|
|
||||||
RUN apt-get update && apt-get install -y curl && apt-get clean
|
RUN apt-get update && apt-get install -y curl && apt-get clean
|
||||||
RUN curl -sSL https://github.com/kelseyhightower/confd/releases/download/v${CONFD_VERSION}/confd-${CONFD_VERSION}-linux-amd64 -o /usr/local/bin/confd && \
|
COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf.tmpl
|
||||||
chmod +x /usr/local/bin/confd
|
|
||||||
COPY ./confd /etc/confd
|
|
||||||
|
|
||||||
|
## OLD CONFD THINGS (not compatible with arm)
|
||||||
|
#ENV CONFD_VERSION 0.16.0
|
||||||
|
#RUN curl -sSL https://github.com/kelseyhightower/confd/releases/download/v${CONFD_VERSION}/confd-${CONFD_VERSION}-linux-amd64 -o /usr/local/bin/confd && \
|
||||||
|
# chmod +x /usr/local/bin/confd
|
||||||
|
#COPY ./confd /etc/confd
|
||||||
# rewrite command & entrypoint with ours
|
# rewrite command & entrypoint with ours
|
||||||
|
|
||||||
COPY ./entrypoint.sh /
|
COPY ./entrypoint.sh /
|
||||||
|
ENV BACKEND_HOSTNAME="shuffle-backend"
|
||||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ~ /api/v(1|2) {
|
location ~ /api/v(1|2) {
|
||||||
proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001;
|
proxy_pass http://${BACKEND_HOSTNAME}:5001;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
@@ -113,7 +113,8 @@ http {
|
|||||||
|
|
||||||
# Get the hostname from environment here?
|
# Get the hostname from environment here?
|
||||||
location ~ /api/v(1|2) {
|
location ~ /api/v(1|2) {
|
||||||
proxy_pass http://{{ getenv "BACKEND_HOSTNAME" "shuffle-backend" }}:5001;
|
proxy_pass http://${BACKEND_HOSTNAME}:5001;
|
||||||
|
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
# generate configs
|
envsubst '${BACKEND_HOSTNAME}' < /etc/nginx/nginx.conf.tmpl > /etc/nginx/nginx.conf
|
||||||
/usr/local/bin/confd -backend="env" -confdir="/etc/confd" -onetime
|
|
||||||
|
|
||||||
# run main command
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import HealthPage from "./components/HealthPage.jsx";
|
|||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import Apps from "./views/Apps";
|
import Apps from "./views/Apps";
|
||||||
import AppCreator from "./views/AppCreator";
|
import AppCreator from "./views/AppCreator";
|
||||||
|
import DetectionDashBoard from "./views/DetectionDashboard.jsx";
|
||||||
|
|
||||||
import Welcome from "./views/Welcome.jsx";
|
import Welcome from "./views/Welcome.jsx";
|
||||||
import Dashboard from "./views/Dashboard.jsx";
|
import Dashboard from "./views/Dashboard.jsx";
|
||||||
@@ -414,6 +415,11 @@ const App = (message, props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/detections/sigma"
|
||||||
|
element={<DetectionDashBoard globalUrl={globalUrl} />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path="/workflows"
|
path="/workflows"
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ const Billing = (props) => {
|
|||||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props;
|
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props;
|
||||||
//const alert = useAlert();
|
//const alert = useAlert();
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false);
|
const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false);
|
||||||
const [dealList, setDealList] = React.useState([]);
|
const [dealList, setDealList] = React.useState([]);
|
||||||
const [dealName, setDealName] = React.useState("");
|
const [dealName, setDealName] = React.useState("");
|
||||||
@@ -1055,10 +1054,10 @@ const Billing = (props) => {
|
|||||||
Consultation & Management
|
Consultation & Management
|
||||||
</Typography>
|
</Typography>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
|
<Typography variant="body2" color="textSecondary" style={{ marginTop: userdata.support ? 5 : 10, }}>
|
||||||
You currently have a total of {inputHour} hours and {inputMinutes} minutes of professional services available by our experts.
|
You currently have a total of {inputHour} hours and {inputMinutes} minutes of professional services available by our experts.
|
||||||
</Typography>
|
</Typography>
|
||||||
<div style={{ display: "flex", minWidth: 340, justifyContent: 'center', marginTop: userdata.support ? 0 : 10 }}>
|
<div style={{ display: "flex", minWidth: 340, justifyContent: 'center', marginTop: userdata.support ? 0 : 15 }}>
|
||||||
{editConsultation ?
|
{editConsultation ?
|
||||||
<>
|
<>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -1106,7 +1105,7 @@ const Billing = (props) => {
|
|||||||
{editConsultation && <Button variant="contained" color="primary" style={{ marginLeft: 5, textTransform: 'none' }} onClick={handleSave}>Save</Button>}
|
{editConsultation && <Button variant="contained" color="primary" style={{ marginLeft: 5, textTransform: 'none' }} onClick={handleSave}>Save</Button>}
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: userdata.support ? 5 : 10, }}>
|
<Typography variant="body2" color="textSecondary" style={{ marginTop: userdata.support ? 5 : 25, }}>
|
||||||
Features
|
Features
|
||||||
</Typography>
|
</Typography>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -1331,7 +1330,7 @@ const Billing = (props) => {
|
|||||||
Become a Shuffle Expert
|
Become a Shuffle Expert
|
||||||
</Typography>
|
</Typography>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
|
<Typography variant="body2" color="textSecondary" style={{ marginTop: 25, }}>
|
||||||
Public Training
|
Public Training
|
||||||
</Typography>
|
</Typography>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -1346,7 +1345,7 @@ const Billing = (props) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
|
<Typography variant="body2" color="textSecondary" style={{ marginTop: 20, }}>
|
||||||
Private Training
|
Private Training
|
||||||
</Typography>
|
</Typography>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -3,15 +3,25 @@ import ReactGA from 'react-ga4';
|
|||||||
import theme from "../theme.jsx";
|
import theme from "../theme.jsx";
|
||||||
import { ToastContainer, toast } from "react-toastify"
|
import { ToastContainer, toast } from "react-toastify"
|
||||||
|
|
||||||
|
import {
|
||||||
|
CheckCircle as CheckCircleIcon,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
Divider,
|
Divider,
|
||||||
Button,
|
Button,
|
||||||
|
Tooltip,
|
||||||
Grid,
|
Grid,
|
||||||
Card,
|
Card,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import {
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
} from "../views/AngularWorkflow.jsx"
|
||||||
|
|
||||||
//import { useAlert
|
//import { useAlert
|
||||||
|
|
||||||
const Branding = (props) => {
|
const Branding = (props) => {
|
||||||
@@ -45,7 +55,7 @@ const Branding = (props) => {
|
|||||||
toast("Failed updating org: ", responseJson.reason);
|
toast("Failed updating org: ", responseJson.reason);
|
||||||
} else {
|
} else {
|
||||||
if (joinStatus == "join") {
|
if (joinStatus == "join") {
|
||||||
setPublishingInfo("Your organization is now part of the Creator Incentive Program. You can now create and publish content to your organization's page. You can also create a creator account to manage your organization's content.")
|
setPublishingInfo("Your organization is now part of the Partner Program. You can now create, publish and manage content for your organization's public page.")
|
||||||
} else {
|
} else {
|
||||||
setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.")
|
setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.")
|
||||||
}
|
}
|
||||||
@@ -70,7 +80,15 @@ const Branding = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isOrganizationReady = () => {
|
const isOrganizationReady = () => {
|
||||||
console.log("Is organization ready?")
|
|
||||||
|
// Check if it's a suborg
|
||||||
|
if (selectedOrganization.creator_org !== "") {
|
||||||
|
const comment = "Child orgs can't become creators"
|
||||||
|
if (!publishRequirements.includes(comment)) {
|
||||||
|
setPublishRequirements([...publishRequirements, comment])
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// A simple checklist to ensure the button shows up properly
|
// A simple checklist to ensure the button shows up properly
|
||||||
if (selectedOrganization.name === selectedOrganization.org) {
|
if (selectedOrganization.name === selectedOrganization.org) {
|
||||||
@@ -82,15 +100,6 @@ const Branding = (props) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a suborg
|
|
||||||
if (selectedOrganization.creator_org !== "") {
|
|
||||||
const comment = "Child orgs can't become creators"
|
|
||||||
if (!publishRequirements.includes(comment)) {
|
|
||||||
setPublishRequirements([...publishRequirements, comment])
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedOrganization.large_image === "" || selectedOrganization.large_image === theme.palette.defaultImage) {
|
if (selectedOrganization.large_image === "" || selectedOrganization.large_image === theme.palette.defaultImage) {
|
||||||
const comment = "Add a logo for your organization"
|
const comment = "Add a logo for your organization"
|
||||||
if (!publishRequirements.includes(comment)) {
|
if (!publishRequirements.includes(comment)) {
|
||||||
@@ -102,6 +111,14 @@ const Branding = (props) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPublished = selectedOrganization.creator_id === ""
|
||||||
|
const leadinfo = selectedOrganization.lead_info === undefined || selectedOrganization.lead_info === null || selectedOrganization.lead_info === "" ? "" : JSON.stringify(selectedOrganization.lead_info)
|
||||||
|
const isPartner = leadinfo.includes("partner")
|
||||||
|
|
||||||
|
console.log("LEADINFO: ", leadinfo)
|
||||||
|
|
||||||
|
console.log("SELECTEDORGANIZATION: ", selectedOrganization)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: clickedFromOrgTab? 1030: "auto", padding: 27, height: "auto", backgroundColor: '#212121', borderRadius: '16px', }}>
|
<div style={{ width: clickedFromOrgTab? 1030: "auto", padding: 27, height: "auto", backgroundColor: '#212121', borderRadius: '16px', }}>
|
||||||
<h2 style={{marginTop: clickedFromOrgTab ?0:null,}}>
|
<h2 style={{marginTop: clickedFromOrgTab ?0:null,}}>
|
||||||
@@ -111,27 +128,46 @@ const Branding = (props) => {
|
|||||||
You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more.
|
You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||||
|
{isPublished ? <CheckCircleIcon style={{color: red, }} /> : <CheckCircleIcon style={{color: green, }} />}
|
||||||
|
<span style={{marginLeft: 10, color: isPublished ? red : green, }}>{isPublished ? "Not Published" : "Published"}</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<a href="https://shuffler.io/partners" target="_blank" style={{ textDecoration: "none", }}>
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||||
|
{!isPartner ? <CheckCircleIcon style={{color: red, }} /> : <CheckCircleIcon style={{color: green, }} />}
|
||||||
|
<Tooltip title="Official Partner Program (manual verification)" placement="top" arrow>
|
||||||
|
<span style={{marginLeft: 10, color: !isPartner ? red : green, }}>{!isPartner? "Not Officially Partnered" : "Officially Partnered"}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Typography>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href={`/partners/${selectedOrganization.creator_id}/edit`} target="_blank">
|
||||||
|
<Button disabled={isPublished} variant="contained" style={{ marginTop: 20, marginBottom: 10, }} onClick={() => {
|
||||||
|
}}>
|
||||||
|
Modify Public Partner Details
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
|
||||||
<Divider style={{marginTop: 50, marginBottom: 50, }} />
|
<Divider style={{marginTop: 50, marginBottom: 50, }} />
|
||||||
<h2>
|
<h2>
|
||||||
Creator Incentive Program
|
Partner Program
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{ display: "flex", width: 900, }}>
|
<div style={{ display: "flex", width: 900, }}>
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
<Typography variant="body1" color="textSecondary">
|
<Typography variant="body1" color="textSecondary">
|
||||||
By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be added as a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/creators">creator account</a>. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization is reversible.<div/>Support: <a href="mailto:support@shuffler.io"target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>support@shuffler.io</a>
|
By changing publishing settings, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your organization's non-sensitive data will be added as a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/creators">creator account</a>. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization IS reversible.<div/>Support: <a href="mailto:support@shuffler.io"target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>support@shuffler.io</a>
|
||||||
</Typography>
|
</Typography>
|
||||||
{selectedOrganization.creator_id == "" ?
|
{selectedOrganization.creator_id == "" ?
|
||||||
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
|
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
|
||||||
|
|
||||||
</Typography>
|
</Typography>
|
||||||
:
|
:
|
||||||
<Typography variant="h6" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "grey", }}>
|
null
|
||||||
|
|
||||||
<a href={`/creators/${selectedOrganization.creator_id}`} target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Modify your creator organization</a>
|
|
||||||
</Typography>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style={{ height: 40, marginTop: 10, width: 300, }}
|
style={{ height: 40, marginTop: 10, width: 300, }}
|
||||||
variant={selectedOrganization.creator_id == "" ? "contained" : "outlined"}
|
variant={selectedOrganization.creator_id == "" ? "contained" : "outlined"}
|
||||||
@@ -141,7 +177,7 @@ const Branding = (props) => {
|
|||||||
handleChangePublishing();
|
handleChangePublishing();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedOrganization.creator_id == "" ? "Join" : "Leave"} Creators
|
{selectedOrganization.creator_id == "" ? "Join" : "Leave"} Partner Program
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "white", }}>
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20, marginBottom: 10, color: "white", }}>
|
||||||
|
|||||||
@@ -690,7 +690,7 @@ const LicencePopup = (props) => {
|
|||||||
const priceItem = window.location.origin === "https://shuffler.io" ?
|
const priceItem = window.location.origin === "https://shuffler.io" ?
|
||||||
shuffleVariant === 0 ? "app_executions" : "cores"
|
shuffleVariant === 0 ? "app_executions" : "cores"
|
||||||
:
|
:
|
||||||
shuffleVariant === 0 ? "price_1PbO0cEJjT17t98NsfEMUlMn" : "price_1PbNnaEJjT17t98NLadq6Lhq"
|
shuffleVariant === 0 ? "price_1PZPSSEJjT17t98NLJoTMYja" : "price_1PZPQuEJjT17t98N3yORUtd9"
|
||||||
|
|
||||||
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
||||||
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
|
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ const Header = (props) => {
|
|||||||
const [subAnchorEl, setSubAnchorEl] = React.useState(null);
|
const [subAnchorEl, setSubAnchorEl] = React.useState(null);
|
||||||
const [upgradeHovered, setUpgradeHovered] = React.useState(false);
|
const [upgradeHovered, setUpgradeHovered] = React.useState(false);
|
||||||
const [showTopbar, setShowTopbar] = useState(false)
|
const [showTopbar, setShowTopbar] = useState(false)
|
||||||
const stripeKey = typeof window === 'undefined' || window.location === undefined ? "" : window.location.origin === "https://shuffler.io" ? "pk_live_XAxwE2Fp9DEbEcNYw4UKmyby00vIlIPPRp" : "pk_test_51PXYYMEJjT17t98NbDkojZ3DRvsFUQBs35LGMx3i436BXwEBVFKB9nCvHt0Q3M4MG3dz4mHheuWvfoYvpaL3GmsG00k1Rb2ksO"
|
const stripeKey = typeof window === 'undefined' || window.location === undefined ? "" : window.location.origin === "https://shuffler.io" ? "pk_live_51PXYYMEJjT17t98N20qEqItyt1fLQjrnn41lPeG2PjnSlZHTDNKHuisAbW00s4KAn86nGuqB9uSVU4ds8MutbnMU00DPXpZ8ZD" : "pk_test_51PXYYMEJjT17t98NbDkojZ3DRvsFUQBs35LGMx3i436BXwEBVFKB9nCvHt0Q3M4MG3dz4mHheuWvfoYvpaL3GmsG00k1Rb2ksO"
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
@@ -171,8 +171,10 @@ const Header = (props) => {
|
|||||||
setTooltipOpen(true);
|
setTooltipOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const topbar_var = "topbar_closed2"
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const topbar = localStorage.getItem("topbar_closed")
|
const topbar = localStorage.getItem(topbar_var)
|
||||||
if (topbar === "true") {
|
if (topbar === "true") {
|
||||||
setShowTopbar(false)
|
setShowTopbar(false)
|
||||||
} else {
|
} else {
|
||||||
@@ -919,8 +921,8 @@ const Header = (props) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<div className={classes.divider} />
|
<div className={classes.divider} />
|
||||||
<MenuItem className={classes.dropdownMenuItem} onClick={() => handleMenuItemClick('/professional-support')}>
|
<MenuItem className={classes.dropdownMenuItem} onClick={() => handleMenuItemClick('/professional-services')}>
|
||||||
<Link to="/professional-support" style={hrefStyle}>
|
<Link to="/professional-services" style={hrefStyle}>
|
||||||
Professional Services
|
Professional Services
|
||||||
</Link>
|
</Link>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -929,9 +931,19 @@ const Header = (props) => {
|
|||||||
<Link to="/training" style={hrefStyle}>
|
<Link to="/training" style={hrefStyle}>
|
||||||
Training Courses
|
Training Courses
|
||||||
</Link>
|
</Link>
|
||||||
|
</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<MenuItem className={classes.dropdownMenuItem} onClick={() => handleMenuItemClick('/partners')}>
|
||||||
|
<Link to="/partners" style={hrefStyle}>
|
||||||
|
Partner Program
|
||||||
|
</Link>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* <ListItem style={{ textAlign: "center", marginLeft: 0, paddingRight: 0 }}>
|
{/* <ListItem style={{ textAlign: "center", marginLeft: 0, paddingRight: 0 }}>
|
||||||
<Link rel="noopener noreferrer" to="/training" style={hrefStyle}>
|
<Link rel="noopener noreferrer" to="/training" style={hrefStyle}>
|
||||||
<Button
|
<Button
|
||||||
@@ -1627,7 +1639,7 @@ const Header = (props) => {
|
|||||||
|
|
||||||
const topbarHeight = showTopbar ? 40 : 0
|
const topbarHeight = showTopbar ? 40 : 0
|
||||||
const topbar = !isCloud || !showTopbar ? null :
|
const topbar = !isCloud || !showTopbar ? null :
|
||||||
curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" || curpath === "/professional-support" ?
|
curpath === "/" || curpath.includes("/docs") || curpath === "/pricing" || curpath === "/contact" || curpath === "/search" || curpath === "/usecases" || curpath === "/training" || curpath === "/professional-services" ?
|
||||||
<span style={{ zIndex: 50001, }}>
|
<span style={{ zIndex: 50001, }}>
|
||||||
<div style={{ position: "relative", height: topbarHeight, backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", overflow: "hidden", }}>
|
<div style={{ position: "relative", height: topbarHeight, backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)", overflow: "hidden", }}>
|
||||||
<Typography variant="body1" style={{ paddingTop: 7, margin: "auto", textAlign: "center", color: "white", }}>
|
<Typography variant="body1" style={{ paddingTop: 7, margin: "auto", textAlign: "center", color: "white", }}>
|
||||||
@@ -1653,7 +1665,7 @@ const Header = (props) => {
|
|||||||
setShowTopbar(false)
|
setShowTopbar(false)
|
||||||
|
|
||||||
// Set storage that it's clicked
|
// Set storage that it's clicked
|
||||||
localStorage.setItem("topbar_closed", "true")
|
localStorage.setItem(topbar_var, "true")
|
||||||
}}>
|
}}>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import { toast } from 'react-toastify';
|
|||||||
import { makeStyles, createStyles } from "@mui/styles";
|
import { makeStyles, createStyles } from "@mui/styles";
|
||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
|
|
||||||
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||||
import { GetParsedPaths } from "../views/Apps.jsx";
|
import { GetParsedPaths } from "../views/Apps.jsx";
|
||||||
import { sortByKey } from "../views/AngularWorkflow.jsx";
|
import { sortByKey } from "../views/AngularWorkflow.jsx";
|
||||||
import { NestedMenuItem } from "mui-nested-menu";
|
import { NestedMenuItem } from "mui-nested-menu";
|
||||||
import { parsedDatatypeImages } from "../components/AppFramework.jsx";
|
import { parsedDatatypeImages } from "../components/AppFramework.jsx";
|
||||||
|
import { green, yellow, red } from "../views/AngularWorkflow.jsx"
|
||||||
//import { useAlert
|
//import { useAlert
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -46,7 +47,8 @@ import {
|
|||||||
CircularProgress,
|
CircularProgress,
|
||||||
Switch,
|
Switch,
|
||||||
Collapse,
|
Collapse,
|
||||||
Autocomplete
|
Autocomplete,
|
||||||
|
Box
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -175,7 +177,9 @@ const ParsedAction = (props) => {
|
|||||||
setAiQueryModalOpen,
|
setAiQueryModalOpen,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
let navigate = useNavigate();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const [hideBody, setHideBody] = React.useState(true)
|
const [hideBody, setHideBody] = React.useState(true)
|
||||||
const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false)
|
const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false)
|
||||||
const [appActionName, setAppActionName] = React.useState(selectedAction?.label);
|
const [appActionName, setAppActionName] = React.useState(selectedAction?.label);
|
||||||
@@ -183,23 +187,18 @@ const ParsedAction = (props) => {
|
|||||||
const [prevActionName, setPrevActionName] = React.useState(selectedAction?.label);
|
const [prevActionName, setPrevActionName] = React.useState(selectedAction?.label);
|
||||||
const [fieldCount, setFieldCount] = React.useState(0);
|
const [fieldCount, setFieldCount] = React.useState(0);
|
||||||
const [hiddenDescription, setHiddenDescription] = React.useState(true);
|
const [hiddenDescription, setHiddenDescription] = React.useState(true);
|
||||||
|
const [hiddenParameters, setHiddenParameters] = React.useState(true);
|
||||||
const [autoCompleting, setAutocompleting] = React.useState(false);
|
const [autoCompleting, setAutocompleting] = React.useState(false);
|
||||||
const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []);
|
const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []);
|
||||||
const [selectedVariableParameter, setSelectedVariableParameter] = React.useState("");
|
const [selectedVariableParameter, setSelectedVariableParameter] = React.useState("");
|
||||||
const [paramValues, setParamValues] = React.useState(
|
const [paramUpdate, setParamUpdate] = React.useState("");
|
||||||
selectedAction?.parameters?.map((param) => {
|
|
||||||
return {
|
|
||||||
name: param.name,
|
|
||||||
value: param.value,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const [actionlist, setActionlist] = React.useState([]);
|
const [actionlist, setActionlist] = React.useState([]);
|
||||||
const [jsonList, setJsonList] = React.useState([]);
|
const [jsonList, setJsonList] = React.useState([]);
|
||||||
const [showDropdown, setShowDropdown] = React.useState(false);
|
const [showDropdown, setShowDropdown] = React.useState(false);
|
||||||
const [showDropdownNumber, setShowDropdownNumber] = React.useState(0);
|
const [showDropdownNumber, setShowDropdownNumber] = React.useState(0);
|
||||||
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
|
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
|
||||||
const [menuPosition, setMenuPosition] = useState(null);
|
const [menuPosition, setMenuPosition] = useState(null);
|
||||||
|
const [uiBox, setUiBox] = useState(null);
|
||||||
const isIntegration = selectedAction.app_id === "integration"
|
const isIntegration = selectedAction.app_id === "integration"
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -208,16 +207,36 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
}, [expansionModalOpen])
|
}, [expansionModalOpen])
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// setParamValues(selectedAction.parameters?.map((param) => {
|
useEffect(() => {
|
||||||
// return {
|
if (selectedActionEnvironment === undefined || selectedActionEnvironment === null || Object.keys(selectedActionEnvironment).length === 0) {
|
||||||
// name: param.name,
|
|
||||||
// value: param.value,
|
if (environments !== undefined && environments !== null && environments.length > 0) {
|
||||||
// }
|
if (selectedAction.environment !== undefined && selectedAction.environment !== null) {
|
||||||
// }))
|
|
||||||
// },[
|
const foundenv = environments.find(env => env.id === selectedAction.environment || selectedAction.environment === env.Name)
|
||||||
// selectedAction, selectedApp,setNewSelectedAction, workflow,
|
|
||||||
// ])
|
if (foundenv !== undefined && foundenv !== null) {
|
||||||
|
setSelectedActionEnvironment(foundenv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
/*
|
||||||
|
useEffect(() => {
|
||||||
|
setParamValues(selectedAction.parameters.map((param) => {
|
||||||
|
return {
|
||||||
|
name: param.name,
|
||||||
|
value: param.value,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},[
|
||||||
|
selectedAction, selectedApp,setNewSelectedAction, workflow,
|
||||||
|
])
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedAction.parameters === null || selectedAction.parameters === undefined) {
|
if (selectedAction.parameters === null || selectedAction.parameters === undefined) {
|
||||||
@@ -418,8 +437,8 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only set selected action parameters if they have changed
|
// Only set selected action parameters if they have changed
|
||||||
if (selectedAction.parameters && selectedAction.parameters.length > 0) {
|
if (selectedAction?.parameters && selectedAction?.parameters.length > 0) {
|
||||||
setSelectedActionParameters(selectedAction.parameters);
|
setSelectedActionParameters(selectedAction?.parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only set selected variable parameter if it is null or undefined
|
// Only set selected variable parameter if it is null or undefined
|
||||||
@@ -434,6 +453,7 @@ const ParsedAction = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newActionList = [];
|
const newActionList = [];
|
||||||
|
const parentActionList = [];
|
||||||
|
|
||||||
// Process workflowExecutions
|
// Process workflowExecutions
|
||||||
if (workflowExecutions.length > 0) {
|
if (workflowExecutions.length > 0) {
|
||||||
@@ -561,88 +581,66 @@ const ParsedAction = (props) => {
|
|||||||
autocomplete: parentNode.label.split(" ").join("_"),
|
autocomplete: parentNode.label.split(" ").join("_"),
|
||||||
example: exampleData,
|
example: exampleData,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the actionlist state
|
parentActionList.push({
|
||||||
setActionlist(newActionList);
|
|
||||||
}, [workflow.execution_variables, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents]);
|
|
||||||
|
|
||||||
|
|
||||||
const memoizedParam = useMemo(() => {
|
|
||||||
let appActions = [];
|
|
||||||
if (getParents) {
|
|
||||||
const parents = getParents(selectedAction);
|
|
||||||
if (parents.length > 1) {
|
|
||||||
const labels = [];
|
|
||||||
for (let parentNode of parents) {
|
|
||||||
if (parentNode.label !== "Execution Argument" && !labels.includes(parentNode.label)) {
|
|
||||||
labels.push(parentNode.label);
|
|
||||||
let exampleData = parentNode.example ?? "";
|
|
||||||
if (!exampleData && workflowExecutions.length > 0) {
|
|
||||||
for (let exec of workflowExecutions) {
|
|
||||||
const foundResult = exec.results?.find(result => result.action.id === parentNode.id);
|
|
||||||
if (foundResult) {
|
|
||||||
const valid = validateJson(foundResult.result);
|
|
||||||
if (valid.valid && valid.result.success !== false) {
|
|
||||||
exampleData = valid.result;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appActions.push({
|
|
||||||
type: "action",
|
type: "action",
|
||||||
id: parentNode.id,
|
id: parentNode.id,
|
||||||
name: parentNode.label,
|
name: parentNode.label,
|
||||||
autocomplete: parentNode.label.split(" ").join("_"),
|
autocomplete: parentNode.label.split(" ").join("_"),
|
||||||
example: exampleData,
|
example: exampleData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let newParameters = selectedAction.parameters?.map((param) => {
|
let newParameters = selectedAction?.parameters?.map((param) => {
|
||||||
let paramvalue = param.value;
|
let paramvalue = param.value;
|
||||||
|
let errorVars = [];
|
||||||
if(paramvalue.includes("$")){
|
if(paramvalue.includes("$")){
|
||||||
let actions = workflow.actions?.map((action) => {
|
let actions = workflow.actions?.map((action) => {
|
||||||
return "$"+action.label.toLowerCase();
|
return "$"+action.label.toLowerCase();
|
||||||
})
|
})
|
||||||
if(actionlist.length > 0){
|
if(newActionList?.length > 0){
|
||||||
let appParentActions = appActions?.map(action => "$" + action.name.toLowerCase());
|
let appParentActions = parentActionList?.map(action => "$" + action.name.toLowerCase());
|
||||||
let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action))
|
let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action))
|
||||||
console.log("ACTIONS: ", actions)
|
|
||||||
console.log("APP ACTIONS: ", appParentActions)
|
|
||||||
console.log("NOT PRESENT: ", notPresentAction)
|
|
||||||
notPresentAction?.forEach((action) => {
|
notPresentAction?.forEach((action) => {
|
||||||
console.log("Not included Action: ", action)
|
action = action.replace(" ", "_");
|
||||||
if(paramvalue.includes(action)){
|
if(paramvalue.includes(action)){
|
||||||
paramvalue = paramvalue.replace(action, "")
|
errorVars.push(action);
|
||||||
paramvalue = paramvalue.replace(/^\s*[\r\n]/gm, "");
|
// paramvalue = paramvalue.replace(action, "")
|
||||||
|
// paramvalue = paramvalue.replace(/^\s*[\r\n]/gm, "");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("After removing param value: ", paramvalue)
|
|
||||||
return {...param, value: paramvalue}
|
|
||||||
});
|
|
||||||
selectedAction.parameters = newParameters;
|
|
||||||
setSelectedActionParameters(newParameters);
|
|
||||||
setSelectedAction(selectedAction);
|
|
||||||
return newParameters;
|
|
||||||
},[actionlist,selectedAction,workflow.actions,workflow,selectedApp,setNewSelectedAction])
|
|
||||||
|
|
||||||
useEffect(() => {
|
let message = "";
|
||||||
setParamValues(memoizedParam?.map((param) => {
|
if(errorVars.length > 0){
|
||||||
return {
|
if(errorVars.length === 1){
|
||||||
name: param.name,
|
message = errorVars[0] + " is not accessible in this action.";
|
||||||
value: param.value,
|
}else{
|
||||||
|
message = errorVars.join(", ") + " are not accessible in this action.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
|
||||||
},[memoizedParam])
|
if (param?.configuration) {
|
||||||
|
let regex = /(^|[^\\])\$/;
|
||||||
|
if (regex.test(paramvalue)) {
|
||||||
|
if(message.length > 0){
|
||||||
|
message += "\nUse \"\\$\" instead of \"$\".";
|
||||||
|
}else{
|
||||||
|
message = "Use \"\\$\" instead of \"$\".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {...param, value: paramvalue, error: message}
|
||||||
|
});
|
||||||
|
setSelectedActionParameters(newParameters);
|
||||||
|
setActionlist(newActionList);
|
||||||
|
}, [workflow.execution_variables,paramUpdate, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents,setNewSelectedAction]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedNameChange(appActionName)
|
selectedNameChange(appActionName)
|
||||||
@@ -653,15 +651,17 @@ const ParsedAction = (props) => {
|
|||||||
},[appActionName,delay])
|
},[appActionName,delay])
|
||||||
|
|
||||||
const handleParamChange = (event, count,data) => {
|
const handleParamChange = (event, count,data) => {
|
||||||
const newParams = [...paramValues];
|
const newParams = [...selectedActionParameters];
|
||||||
newParams.map((param) => {
|
newParams.map((param) => {
|
||||||
if (param.name === data.name) {
|
if (param.name === data.name) {
|
||||||
param.value = event.target.value;
|
param.value = event.target.value;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setParamValues(newParams);
|
setSelectedActionParameters(newParams);
|
||||||
|
setParamUpdate(event.target.value);
|
||||||
changeActionParameter(event, count, data)
|
changeActionParameter(event, count, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateHelpertext = (input_data) => {
|
const calculateHelpertext = (input_data) => {
|
||||||
var helperText = ""
|
var helperText = ""
|
||||||
var looperText = ""
|
var looperText = ""
|
||||||
@@ -1187,6 +1187,15 @@ const ParsedAction = (props) => {
|
|||||||
return helperText
|
return helperText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorHelperText = (name, value, error) => {
|
||||||
|
return (
|
||||||
|
<div style={{ whiteSpace: 'pre-line' }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const analyzeFields = () => {
|
const analyzeFields = () => {
|
||||||
|
|
||||||
if (selectedAction === undefined || selectedAction === null) {
|
if (selectedAction === undefined || selectedAction === null) {
|
||||||
@@ -1248,7 +1257,7 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Issue #40 - selectedActionParameters not reset
|
// FIXME: Issue #40 - selectedActionParameters not reset
|
||||||
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
|
if (Object.getOwnPropertyNames(selectedAction)?.length > 0 && selectedActionParameters?.length > 0) {
|
||||||
var wrapperapp = {
|
var wrapperapp = {
|
||||||
"id": "",
|
"id": "",
|
||||||
"name": "noapp",
|
"name": "noapp",
|
||||||
@@ -1312,6 +1321,7 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
setHiddenDescription(false)
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -2036,6 +2046,7 @@ const ParsedAction = (props) => {
|
|||||||
}}
|
}}
|
||||||
labelId="select-app-auth"
|
labelId="select-app-auth"
|
||||||
value={
|
value={
|
||||||
|
selectedAction.authentication_id === "authgroups" ? "authgroups" :
|
||||||
Object.getOwnPropertyNames(selectedAction.selectedAuthentication).length === 0
|
Object.getOwnPropertyNames(selectedAction.selectedAuthentication).length === 0
|
||||||
? "No selection"
|
? "No selection"
|
||||||
: selectedAction.selectedAuthentication
|
: selectedAction.selectedAuthentication
|
||||||
@@ -2052,18 +2063,48 @@ const ParsedAction = (props) => {
|
|||||||
selectedAction.authentication_id = "";
|
selectedAction.authentication_id = "";
|
||||||
|
|
||||||
for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
|
for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
|
||||||
//console.log(selectedAction.parameters[key])
|
|
||||||
if (selectedAction.parameters[key].configuration) {
|
if (selectedAction.parameters[key].configuration) {
|
||||||
selectedAction.parameters[key].value = "";
|
|
||||||
|
if (selectedAction.parameters[key].example !== undefined && selectedAction.parameters[key].example !== null && selectedAction.parameters[key].example !== "") {
|
||||||
|
if (selectedAction.parameters[key].example.toLowerCase().includes("api") || selectedAction.parameters[key].example.toLowerCase().includes("key") || selectedAction.parameters[key].example.toLowerCase().includes("pass")) {
|
||||||
|
selectedAction.parameters[key].value = ""
|
||||||
|
} else {
|
||||||
|
selectedAction.parameters[key].value = selectedAction.parameters[key].example
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedAction.parameters[key].value = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSelectedAction(selectedAction);
|
setSelectedAction(selectedAction);
|
||||||
setUpdate(Math.random());
|
setUpdate(Math.random());
|
||||||
|
|
||||||
|
} else if (e.target.value === "authgroups") {
|
||||||
|
if (authGroups !== undefined && authGroups !== null && authGroups.length === 0) {
|
||||||
|
toast("No auth groups created. Opening window to create one")
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.open("/admin?tab=app_auth", "_blank")
|
||||||
|
}, 2500)
|
||||||
|
} else {
|
||||||
|
selectedAction.selectedAuthentication = {};
|
||||||
|
selectedAction.authentication_id = "authgroups"
|
||||||
|
|
||||||
|
for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
|
||||||
|
//console.log(selectedAction.parameters[key])
|
||||||
|
if (selectedAction.parameters[key].configuration) {
|
||||||
|
selectedAction.parameters[key].value = "authgroup controlled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedAction(selectedAction)
|
||||||
|
setUpdate(Math.random())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedAction.selectedAuthentication = e.target.value;
|
selectedAction.selectedAuthentication = e.target.value;
|
||||||
selectedAction.authentication_id = e.target.value.id;
|
selectedAction.authentication_id = e.target.value.id;
|
||||||
setSelectedAction(selectedAction);
|
setSelectedAction(selectedAction)
|
||||||
setUpdate(Math.random());
|
setUpdate(Math.random())
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
@@ -2119,6 +2160,19 @@ const ParsedAction = (props) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
<Divider style={{marginTop: 10, marginBottom: 10, }}/>
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
value="authgroups"
|
||||||
|
>
|
||||||
|
<em>Auth Groups</em>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
@@ -2149,6 +2203,7 @@ const ParsedAction = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
{showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? (
|
{showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? (
|
||||||
<div style={{ marginTop: "20px" }}>
|
<div style={{ marginTop: "20px" }}>
|
||||||
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Environment</Typography>
|
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Environment</Typography>
|
||||||
@@ -2157,10 +2212,7 @@ const ParsedAction = (props) => {
|
|||||||
disableScrollLock: true,
|
disableScrollLock: true,
|
||||||
}}
|
}}
|
||||||
value={
|
value={
|
||||||
selectedActionEnvironment === undefined || selectedActionEnvironment === null ||
|
selectedActionEnvironment === undefined || selectedActionEnvironment === null || selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null ? isCloud ? "Cloud" : "Shuffle" : selectedActionEnvironment.Name
|
||||||
selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null
|
|
||||||
? isCloud ? "Cloud" : "Shuffle"
|
|
||||||
: selectedActionEnvironment.Name
|
|
||||||
}
|
}
|
||||||
SelectDisplayProps={{
|
SelectDisplayProps={{
|
||||||
style: {
|
style: {
|
||||||
@@ -2177,7 +2229,7 @@ const ParsedAction = (props) => {
|
|||||||
workflow.actions[actionkey].environment = env.Name
|
workflow.actions[actionkey].environment = env.Name
|
||||||
}
|
}
|
||||||
setWorkflow(workflow)
|
setWorkflow(workflow)
|
||||||
toast("Set environment for ALL actions to " + env.Name)
|
toast.success("Set environment for ALL actions to " + env.Name)
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
@@ -2188,9 +2240,11 @@ const ParsedAction = (props) => {
|
|||||||
>
|
>
|
||||||
{environments.map((data, index) => {
|
{environments.map((data, index) => {
|
||||||
if (data.archived === true) {
|
if (data.archived === true) {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isRunning = data.running_ip !== ""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={data.Name}
|
key={data.Name}
|
||||||
@@ -2200,6 +2254,27 @@ const ParsedAction = (props) => {
|
|||||||
}}
|
}}
|
||||||
value={data.Name}
|
value={data.Name}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{data.Name === "cloud" || data.Name === "Cloud" ? null : !isRunning ?
|
||||||
|
<a href={`/admin?tab=environments&env=${data.Name}`} target="_blank" style={{textDecoration: "none",}}>
|
||||||
|
<Tooltip title={"Click to configure the environment"} placement="top">
|
||||||
|
<Chip
|
||||||
|
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer", backgroundColor: red, }}
|
||||||
|
label={"Stopped"}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
window.open(`/admin?tab=environments&env=${data.Name}`, "_blank", "noopener,noreferrer")
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
: null}
|
||||||
|
|
||||||
{data.default === true ?
|
{data.default === true ?
|
||||||
<Chip
|
<Chip
|
||||||
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
|
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
|
||||||
@@ -2208,12 +2283,22 @@ const ParsedAction = (props) => {
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
/>
|
/>
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
|
|
||||||
{data.Name}
|
{data.Name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
|
||||||
|
{/*selectedActionEnvironment.running_ip === "" && selectedActionEnvironment.Name !== "Cloud" && selectedActionEnvironment.Name !== "cloud" ?
|
||||||
|
<a href={`/admin?tab=environment&env=${selectedActionEnvironment.Name}`} target="_blank" style={{textDecoration: "none", color: "#f85a3e",}}>
|
||||||
|
<Typography style={{}}>
|
||||||
|
Configure the environment
|
||||||
|
</Typography>
|
||||||
|
</a>
|
||||||
|
: null*/}
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{workflow.execution_variables !== undefined &&
|
{workflow.execution_variables !== undefined &&
|
||||||
workflow.execution_variables !== null &&
|
workflow.execution_variables !== null &&
|
||||||
@@ -2473,8 +2558,63 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const actionDescription = (
|
||||||
|
<Box
|
||||||
|
p={1.5}
|
||||||
|
borderRadius={3}
|
||||||
|
boxShadow={2}
|
||||||
|
backgroundColor={theme.palette.textFieldStyle}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||||
|
<Typography variant="body1" style={{ flexGrow: 1 }}>
|
||||||
|
{params.inputProps.value}
|
||||||
|
</Typography>
|
||||||
|
<IconButton size="small"
|
||||||
|
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
|
||||||
|
onClick={() => {
|
||||||
|
setHiddenDescription(true)
|
||||||
|
const inputElement = document.getElementById(uiBox);
|
||||||
|
if (inputElement) {
|
||||||
|
inputElement.focus();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Divider sx={{ backgroundColor: theme.palette.surfaceColor, marginTop: "5px", marginBottom : "10px", height: "3px" }}/>
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
|
<Typography variant="body2" mb={0.5}>
|
||||||
|
<strong>Description: </strong> {selectedAction?.description}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextField
|
<Tooltip title={actionDescription}
|
||||||
|
placement="right"
|
||||||
|
open={!hiddenDescription}
|
||||||
|
PopperProps={{
|
||||||
|
sx: {
|
||||||
|
'& .MuiTooltip-tooltip': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
'& .MuiTooltip-arrow': {
|
||||||
|
color: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
|
|
||||||
data-lpignore="true"
|
data-lpignore="true"
|
||||||
@@ -2492,8 +2632,8 @@ const ParsedAction = (props) => {
|
|||||||
label={isIntegration ? "Choose a category" : "Find Actions"}
|
label={isIntegration ? "Choose a category" : "Find Actions"}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
name={`disable_autocomplete_${Math.random()}`}
|
name={`disable_autocomplete_${Math.random()}`}
|
||||||
|
/>
|
||||||
/>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -2690,7 +2830,7 @@ const ParsedAction = (props) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
disabled={selectedAction.description === undefined || selectedAction.description === null || selectedAction.description.length === 0}
|
disabled={selectedAction.description === undefined || selectedAction.description === null || selectedAction.description.length === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setHiddenDescription(!hiddenDescription)
|
setHiddenParameters(!hiddenParameters)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<b>Parameters</b>
|
<b>Parameters</b>
|
||||||
@@ -2808,7 +2948,7 @@ const ParsedAction = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
{selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenDescription === false ? (
|
{selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenParameters === false ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid rgba(255,255,255,0.6)",
|
border: "1px solid rgba(255,255,255,0.6)",
|
||||||
@@ -2830,8 +2970,7 @@ const ParsedAction = (props) => {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{suggestionInfo()}
|
{suggestionInfo()}
|
||||||
|
{selectedActionParameters?.map((data, count) => {
|
||||||
{selectedActionParameters.map((data, count) => {
|
|
||||||
if (data.variant === "") {
|
if (data.variant === "") {
|
||||||
data.variant = "STATIC_VALUE";
|
data.variant = "STATIC_VALUE";
|
||||||
}
|
}
|
||||||
@@ -2840,10 +2979,16 @@ const ParsedAction = (props) => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.value === "authgroup controlled") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// selectedAction.selectedAuthentication = e.target.value
|
// selectedAction.selectedAuthentication = e.target.value
|
||||||
// selectedAction.authentication_id = e.target.value.id
|
// selectedAction.authentication_id = e.target.value.id
|
||||||
if (
|
if (
|
||||||
!selectedAction.auth_not_required &&
|
(selectedAction.auth_not_required !== undefined && !selectedAction.auth_not_required) &&
|
||||||
|
selectedActionParameters[count].value !== undefined &&
|
||||||
|
selectedAction.parameters[count].value !== undefined &&
|
||||||
selectedAction.selectedAuthentication !== undefined &&
|
selectedAction.selectedAuthentication !== undefined &&
|
||||||
selectedAction.selectedAuthentication.fields !== undefined &&
|
selectedAction.selectedAuthentication.fields !== undefined &&
|
||||||
selectedAction.selectedAuthentication.fields[data.name] !==
|
selectedAction.selectedAuthentication.fields[data.name] !==
|
||||||
@@ -3202,8 +3347,79 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
multiline = data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline
|
multiline = data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline
|
||||||
|
|
||||||
|
const description = data.description === undefined ? "" : data?.description;
|
||||||
|
|
||||||
|
const tooltipDescription = (
|
||||||
|
<Box
|
||||||
|
p={1.5}
|
||||||
|
borderRadius={3}
|
||||||
|
boxShadow={2}
|
||||||
|
backgroundColor={theme.palette.textFieldStyle}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||||
|
<Typography variant="body1" style={{ flexGrow: 1 }}>
|
||||||
|
{tmpitem.charAt(0).toUpperCase() + tmpitem.slice(1)}
|
||||||
|
</Typography>
|
||||||
|
<IconButton size="small"
|
||||||
|
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
|
||||||
|
onClick={() => {
|
||||||
|
setUiBox("closed")
|
||||||
|
const inputElement = document.getElementById(uiBox);
|
||||||
|
if (inputElement) {
|
||||||
|
inputElement.focus();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Divider sx={{ backgroundColor: theme.palette.surfaceColor, marginTop: "5px", marginBottom : "10px", height: "3px" }}/>
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
|
<Typography variant="body2" mb={0.5}>
|
||||||
|
<strong>Required:</strong> {data.required === true || data.configuration === true ? "True" : "False"}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" mb={0.5}>
|
||||||
|
<strong>Description:</strong> {description}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<strong>Ex. :</strong> {data?.example.length > 0 ? data.example : "No example available"}
|
||||||
|
</Typography>
|
||||||
|
{
|
||||||
|
data?.configuration === true ?
|
||||||
|
(
|
||||||
|
<Typography variant="body2" mt={0.5}>
|
||||||
|
<strong>Auth: </strong>Use "\$" instead of "$"
|
||||||
|
</Typography>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
var datafield = (
|
var datafield = (
|
||||||
|
<Tooltip
|
||||||
|
title={tooltipDescription}
|
||||||
|
placement="right"
|
||||||
|
open={clickedFieldId === uiBox}
|
||||||
|
PopperProps={{
|
||||||
|
sx: {
|
||||||
|
'& .MuiTooltip-tooltip': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
'& .MuiTooltip-arrow': {
|
||||||
|
color: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={{
|
style={{
|
||||||
@@ -3292,10 +3508,12 @@ const ParsedAction = (props) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
// defaultValue={data.value}
|
// defaultValue={data.value}
|
||||||
value={
|
value={
|
||||||
paramValues.find((param) => param.name === data.name) !== undefined
|
data?.value
|
||||||
? paramValues.find((param) => param.name === data.name).value
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
|
error={
|
||||||
|
data?.error?.length > 0 ? true : false
|
||||||
|
}
|
||||||
|
helperText={data?.error?.length > 0 ? errorHelperText(data?.name,data?.value,data?.error) : returnHelperText(data.name, data.value)}
|
||||||
//options={{
|
//options={{
|
||||||
// theme: 'gruvbox-dark',
|
// theme: 'gruvbox-dark',
|
||||||
// keyMap: 'sublime',
|
// keyMap: 'sublime',
|
||||||
@@ -3318,14 +3536,19 @@ const ParsedAction = (props) => {
|
|||||||
// changeActionParameter(event, count, data);
|
// changeActionParameter(event, count, data);
|
||||||
handleParamChange(event, count, data)
|
handleParamChange(event, count, data)
|
||||||
}}
|
}}
|
||||||
helperText={returnHelperText(data.name, data.value)}
|
onFocus={(event) => {
|
||||||
|
setUiBox(event.target.id)
|
||||||
|
|
||||||
|
}}
|
||||||
onBlur={(event) => {
|
onBlur={(event) => {
|
||||||
baseHelperText = calculateHelpertext(event.target.value)
|
baseHelperText = calculateHelpertext(event.target.value)
|
||||||
if (setLastSaved !== undefined) {
|
if (setLastSaved !== undefined) {
|
||||||
setLastSaved(false)
|
setLastSaved(false)
|
||||||
}
|
}
|
||||||
|
setUiBox("closed")
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Finds headers from a string to be used for autocompletion
|
// Finds headers from a string to be used for autocompletion
|
||||||
@@ -4057,24 +4280,6 @@ const ParsedAction = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const description =
|
|
||||||
data.description === undefined ? "" : data.description;
|
|
||||||
const tooltipDescription = (
|
|
||||||
<span>
|
|
||||||
<Typography variant="body2">
|
|
||||||
- Required:{" "}
|
|
||||||
{data.required === true || data.configuration === true
|
|
||||||
? "True"
|
|
||||||
: "False"}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2">
|
|
||||||
- Example: {data.example}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2">
|
|
||||||
- Description: {description}
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
//var itemColor = "#f85a3e"
|
//var itemColor = "#f85a3e"
|
||||||
//if (!data.required) {
|
//if (!data.required) {
|
||||||
@@ -4154,9 +4359,7 @@ const ParsedAction = (props) => {
|
|||||||
marginBottom: "auto",
|
marginBottom: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title={tooltipDescription} placement="top">
|
|
||||||
<b>{tmpitem} </b>
|
<b>{tmpitem} </b>
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null :
|
{/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null :
|
||||||
@@ -4239,11 +4442,7 @@ const ParsedAction = (props) => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowAutocomplete(false);
|
setShowAutocomplete(false);
|
||||||
|
|
||||||
if (
|
if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length - 1] === ".") {
|
||||||
!selectedActionParameters[count].value[
|
|
||||||
selectedActionParameters[count].value.length - 1
|
|
||||||
] === "."
|
|
||||||
) {
|
|
||||||
setShowDropdown(false);
|
setShowDropdown(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ const SearchField = props => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: "auto", marginLeft: !isLoggedIn ? 0: "auto", marginRight: !isLoggedIn ? 0 : "auto", width: !isLoggedIn ? "auto" : 300, }}>
|
<div style={{ marginTop: "auto", marginLeft: !isLoggedIn ? 0: "auto", marginRight: !isLoggedIn ? 0 : "auto", width: !isLoggedIn ? "auto" : 410, }}>
|
||||||
{modalView}
|
{modalView}
|
||||||
<TextField
|
<TextField
|
||||||
style={{ backgroundColor: "#212121", height: 48, borderRadius: rounded === true ? 25 : theme.palette.borderRadius, minWidth: fieldWidth, maxWidth: fieldWidth, }}
|
style={{ backgroundColor: "#212121", height: 48, borderRadius: rounded === true ? 25 : theme.palette.borderRadius, minWidth: fieldWidth, maxWidth: fieldWidth, }}
|
||||||
|
|||||||
@@ -3755,7 +3755,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
<Tab label=<span>Limits & Cloud Sync</span> />
|
<Tab label=<span>Limits & Cloud Sync</span> />
|
||||||
<Tab label=<span>Priorities</span> />
|
<Tab label=<span>Priorities</span> />
|
||||||
<Tab label=<span>Billing & Stats</span> />
|
<Tab label=<span>Billing & Stats</span> />
|
||||||
<Tab disabled={!isCloud} label=<span>Branding (Beta)</span> />
|
<Tab disabled={!isCloud} label=<span>Partner</span> />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<Divider
|
<Divider
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Folder as FolderIcon,
|
Folder as FolderIcon,
|
||||||
VerifiedUser as VerifiedUserIcon,
|
VerifiedUser as VerifiedUserIcon,
|
||||||
|
CheckCircle as CheckCircleIcon,
|
||||||
Insights as InsightsIcon,
|
Insights as InsightsIcon,
|
||||||
LibraryBooks as LibraryBooksIcon,
|
LibraryBooks as LibraryBooksIcon,
|
||||||
OpenInNew as OpenInNewIcon,
|
OpenInNew as OpenInNewIcon,
|
||||||
@@ -321,7 +322,7 @@ export function SetJsonDotnotation(jsonInput, inputKey) {
|
|||||||
|
|
||||||
export const green = "#86c142";
|
export const green = "#86c142";
|
||||||
export const yellow = "#FECC00";
|
export const yellow = "#FECC00";
|
||||||
export const red = "red";
|
export const red = "#ff3632";
|
||||||
|
|
||||||
export function removeParam(key, sourceURL) {
|
export function removeParam(key, sourceURL) {
|
||||||
if (sourceURL === undefined) {
|
if (sourceURL === undefined) {
|
||||||
@@ -537,6 +538,8 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
const [listCache, setListCache] = React.useState([]);
|
const [listCache, setListCache] = React.useState([]);
|
||||||
const [selectedOption, setSelectedOption] = React.useState("");
|
const [selectedOption, setSelectedOption] = React.useState("");
|
||||||
const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false);
|
const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false);
|
||||||
|
const [rules, setRules] = React.useState([]);
|
||||||
|
const [sigmaFilesNames, setSigmaFileNames] = React.useState("")
|
||||||
|
|
||||||
const [distributedFromParent, setDistributedFromParent] = React.useState("")
|
const [distributedFromParent, setDistributedFromParent] = React.useState("")
|
||||||
const [suborgWorkflows, setSuborgWorkflows] = React.useState([])
|
const [suborgWorkflows, setSuborgWorkflows] = React.useState([])
|
||||||
@@ -1575,7 +1578,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKafkaSubmit = (trigger) => {
|
const handleCommandSubmit = (trigger) => {
|
||||||
if (trigger.trigger_type !== "PIPELINE") {
|
if (trigger.trigger_type !== "PIPELINE") {
|
||||||
toast("Unable to save the configuration");
|
toast("Unable to save the configuration");
|
||||||
return;
|
return;
|
||||||
@@ -1583,18 +1586,48 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
|
|
||||||
trigger.parameters = []
|
trigger.parameters = []
|
||||||
|
|
||||||
const topic = document.getElementById('topic')?.value;
|
const command = document.getElementById('sigma')?.value
|
||||||
const bootstrapServers = document.getElementById('bootstrap_servers')?.value;
|
|
||||||
const groupId = document.getElementById('group_id')?.value;
|
if(command) {
|
||||||
//const autoOffsetReset = document.getElementById('auto_offset_reset')?.value;
|
trigger.parameters.push({
|
||||||
|
name: "command",
|
||||||
|
value: command
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast("Please enter the comamnd");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (autoOffsetReset) {
|
||||||
|
// trigger.parameters.push({
|
||||||
|
// name: "auto_offset_reset",
|
||||||
|
// value: autoOffsetReset
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
setTenzirConfigModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (trigger) => {
|
||||||
|
if (trigger.trigger_type !== "PIPELINE") {
|
||||||
|
toast("Unable to save the configuration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedOption === "Kafka Queue") {
|
||||||
|
trigger.parameters = []
|
||||||
|
|
||||||
|
const topic = document.getElementById('topic')?.value
|
||||||
|
const bootstrapServers = document.getElementById('bootstrap_servers')?.value
|
||||||
|
const groupId = document.getElementById('group_id')?.value
|
||||||
|
const autoOffsetReset = document.getElementById('auto_offset_reset')?.value;
|
||||||
|
|
||||||
if(topic) {
|
if(topic) {
|
||||||
trigger.parameters.push({
|
trigger.parameters.push({
|
||||||
name: "topic",
|
name: "topic",
|
||||||
value: topic
|
value: topic
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
toast("please enter the topic name");
|
toast("Please enter the topic name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1615,16 +1648,33 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (autoOffsetReset) {
|
if (autoOffsetReset) {
|
||||||
// trigger.parameters.push({
|
trigger.parameters.push({
|
||||||
// name: "auto_offset_reset",
|
name: "auto_offset_reset",
|
||||||
// value: autoOffsetReset
|
value: autoOffsetReset
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
setTenzirConfigModalOpen(false);
|
setTenzirConfigModalOpen(false);
|
||||||
|
} else if (selectedOption === "Syslog listener") {
|
||||||
|
trigger.parameters = []
|
||||||
|
|
||||||
|
const endpoint = document.getElementById('endpoint')?.value
|
||||||
|
|
||||||
|
if(endpoint) {
|
||||||
|
trigger.parameters.push({
|
||||||
|
name: "endpoint",
|
||||||
|
value: endpoint
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast("Please enter your endpoint");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleColoring = (actionId, status, label) => {
|
const handleColoring = (actionId, status, label) => {
|
||||||
if (cy === undefined) {
|
if (cy === undefined) {
|
||||||
return
|
return
|
||||||
@@ -3825,7 +3875,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
|
|
||||||
|
|
||||||
setSelectedEdge({})
|
setSelectedEdge({})
|
||||||
setSelectedActionEnvironment({})
|
//setSelectedActionEnvironment({})
|
||||||
setTriggerAuthentication({})
|
setTriggerAuthentication({})
|
||||||
setTriggerFolders([])
|
setTriggerFolders([])
|
||||||
setLocalFirstrequest(true)
|
setLocalFirstrequest(true)
|
||||||
@@ -6267,6 +6317,8 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
if (curnode.data.id === edge.data("source")) {
|
if (curnode.data.id === edge.data("source")) {
|
||||||
console.log("Found matching trigger source: ", curnode)
|
console.log("Found matching trigger source: ", curnode)
|
||||||
if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") {
|
if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") {
|
||||||
|
|
||||||
|
|
||||||
// If it's started, READD the edge
|
// If it's started, READD the edge
|
||||||
if (curnode.data.status === "running") {
|
if (curnode.data.status === "running") {
|
||||||
//console.log("Edge is running - readd it: ", edge.data())
|
//console.log("Edge is running - readd it: ", edge.data())
|
||||||
@@ -6281,7 +6333,8 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
data: newdata,
|
data: newdata,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.error("You must STOP the trigger before deleting its branches")
|
//toast.error("You must STOP the trigger before deleting its branches")
|
||||||
|
console.log("You must STOP the trigger before deleting its branches")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed re-adding edge: ", e)
|
console.log("Failed re-adding edge: ", e)
|
||||||
}
|
}
|
||||||
@@ -8378,11 +8431,11 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
toast("Pipeline deleted!")
|
toast("Pipeline deleted!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (trigger.parameters){
|
||||||
trigger.parameters.push({
|
trigger.parameters.push({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
value: data.command,
|
value: data.command,
|
||||||
});
|
});}
|
||||||
|
|
||||||
if (data.type === "stop") trigger.status = "stopped";
|
if (data.type === "stop") trigger.status = "stopped";
|
||||||
else trigger.status = "running";
|
else trigger.status = "running";
|
||||||
@@ -8390,7 +8443,6 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
|
|
||||||
setSelectedTrigger(trigger);
|
setSelectedTrigger(trigger);
|
||||||
setWorkflow(workflow);
|
setWorkflow(workflow);
|
||||||
console.log("Should set the status to running and save");
|
|
||||||
saveWorkflow(workflow);
|
saveWorkflow(workflow);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -8465,6 +8517,32 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSigmaInfo = () => {
|
||||||
|
const url = globalUrl + "/api/v1/files/detection/sigma_rules";
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === false) {
|
||||||
|
toast("Failed to get sigma rules");
|
||||||
|
} else {
|
||||||
|
setRules(responseJson.sigma_info);
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error in getting sigma files: ", error);
|
||||||
|
toast("An error occurred while fetching sigma rules");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 50
|
const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 50
|
||||||
const appViewStyle = {
|
const appViewStyle = {
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
@@ -9239,6 +9317,11 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
const startIndex = app.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0)
|
const startIndex = app.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0)
|
||||||
const actionIndex = startIndex < 0 ? 0 : startIndex
|
const actionIndex = startIndex < 0 ? 0 : startIndex
|
||||||
|
|
||||||
|
if (app.actions[actionIndex] === undefined || app.actions[actionIndex] === null) {
|
||||||
|
console.log("No actions found for app: ", app)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Make the first action the most relevant one for them based on previous use
|
// Make the first action the most relevant one for them based on previous use
|
||||||
if (
|
if (
|
||||||
app.actions[actionIndex].parameters !== undefined &&
|
app.actions[actionIndex].parameters !== undefined &&
|
||||||
@@ -11742,7 +11825,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
<div style={appApiViewStyle}>
|
<div style={appApiViewStyle}>
|
||||||
<div style={{ }}>
|
<div style={{ }}>
|
||||||
<h3 style={{ marginBottom: 5, }}>
|
<h3 style={{ marginBottom: 5, }}>
|
||||||
Branch: Conditions - {selectedEdgeIndex}
|
Conditions
|
||||||
</h3>
|
</h3>
|
||||||
<a
|
<a
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -14844,6 +14927,15 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
const defaultEnvironment = environments.find(
|
||||||
|
(env) => env.default && env.Name.toLowerCase() !== "cloud"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedTrigger.trigger_type === "PIPELINE" && selectedTrigger.environment === "onprem" && defaultEnvironment !== undefined) {
|
||||||
|
selectedTrigger.environment = defaultEnvironment.Name
|
||||||
|
setSelectedTrigger(selectedTrigger)
|
||||||
|
}
|
||||||
|
|
||||||
const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "PIPELINE" ? null :
|
const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "PIPELINE" ? null :
|
||||||
<div style={appApiViewStyle}>
|
<div style={appApiViewStyle}>
|
||||||
<h3 style={{ marginBottom: "5px" }}>
|
<h3 style={{ marginBottom: "5px" }}>
|
||||||
@@ -14942,14 +15034,32 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
<div
|
<div
|
||||||
key="syslogListener"
|
key="syslogListener"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// setSelectedOption("Syslog listener")
|
if(selectedTrigger.status === "running"){
|
||||||
// setTenzirConfigModalOpen(true);
|
//toast("please stop the trigger to edit the configuration");
|
||||||
}}
|
return;
|
||||||
|
} else {
|
||||||
|
setSelectedOption("Syslog listener");
|
||||||
|
const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
|
||||||
|
const command = `from tcp://192.168.1.100:5162 read syslog | import`
|
||||||
|
const pipelineConfig = {
|
||||||
|
command: command,
|
||||||
|
name: selectedTrigger.label,
|
||||||
|
type: "create",
|
||||||
|
environment: selectedTrigger.environment,
|
||||||
|
workflow_id: workflow.id,
|
||||||
|
trigger_id: selectedTrigger.id,
|
||||||
|
start_node: "",
|
||||||
|
url:url,
|
||||||
|
};
|
||||||
|
submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
|
||||||
|
|
||||||
|
|
||||||
|
}}}
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid rgba(255,255,255,0.3)",
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette.borderRadius,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
cursor: "not-allowed",
|
cursor: "pointer",
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -14959,27 +15069,46 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
checked={selectedOption === "Syslog listener"}
|
checked={selectedOption === "Syslog listener"}
|
||||||
onChange={() => setSelectedOption("Syslog listener")}
|
onChange={() => {
|
||||||
|
if (selectedTrigger.status !== "running"){
|
||||||
|
setSelectedOption("Syslog listener")}}
|
||||||
|
}
|
||||||
value={"Syslog listener"}
|
value={"Syslog listener"}
|
||||||
name="option"
|
name="option"
|
||||||
disabled={true}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Start Syslog listener"
|
label= {selectedOption === "Syslog listener" && selectedTrigger.status === "running" ? "listening at 192.168.1.100:5162" : "Start Syslog listener"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
key="sigmaRulesearch"
|
key="sigmaRulesearch"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// setSelectedOption("Sigma Rulesearch")
|
if(selectedTrigger.status === "running"){
|
||||||
// setTenzirConfigModalOpen(true);
|
// toast("please stop the trigger to edit the configuration");
|
||||||
}}
|
return;
|
||||||
|
} else {
|
||||||
|
setSelectedOption("SigmaRule");
|
||||||
|
const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
|
||||||
|
const command = `export | sigma /var/lib/tenzir/sigma_rules | to ${url}`
|
||||||
|
const pipelineConfig = {
|
||||||
|
command: command,
|
||||||
|
name: selectedTrigger.label,
|
||||||
|
type: "create",
|
||||||
|
environment: selectedTrigger.environment,
|
||||||
|
workflow_id: workflow.id,
|
||||||
|
trigger_id: selectedTrigger.id,
|
||||||
|
start_node: "",
|
||||||
|
url:url,
|
||||||
|
};
|
||||||
|
submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
|
||||||
|
|
||||||
|
}}}
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid rgba(255,255,255,0.3)",
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette.borderRadius,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
cursor: "not-allowed",
|
cursor: "pointer",
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -14988,11 +15117,12 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
checked={selectedOption === "Sigma Rulesearch"}
|
checked={selectedOption === "SigmaRule"}
|
||||||
onChange={() => setSelectedOption("Sigma Rulesearch")}
|
onChange={() => {
|
||||||
|
if (selectedTrigger.status !== "running"){
|
||||||
|
setSelectedOption("SigmaRule")}}}
|
||||||
value={"Sigma Rulesearch"}
|
value={"Sigma Rulesearch"}
|
||||||
name="option"
|
name="option"
|
||||||
disabled={true}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Run Sigma Rulesearch"
|
label="Run Sigma Rulesearch"
|
||||||
@@ -15023,7 +15153,9 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
checked={selectedOption === "Kafka Queue"}
|
checked={selectedOption === "Kafka Queue"}
|
||||||
onChange={() => setSelectedOption("Kafka Queue")}
|
onChange={() => {
|
||||||
|
if (selectedTrigger.status !== "running"){
|
||||||
|
setSelectedOption("Kafka Queue")}}}
|
||||||
value={"Kafka Queue"}
|
value={"Kafka Queue"}
|
||||||
name="option"
|
name="option"
|
||||||
/>
|
/>
|
||||||
@@ -15039,10 +15171,12 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
disabled={selectedTrigger.status === "running"}
|
disabled={selectedTrigger.status === "running"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
||||||
|
if (selectedOption === "Kafka Queue"){
|
||||||
|
const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
|
||||||
const topic = (selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || ''
|
const topic = (selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || ''
|
||||||
const bootstrapServers = (selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || ''
|
const bootstrapServers = (selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || ''
|
||||||
const groupId = (selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || ''
|
const groupId = (selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || ''
|
||||||
// const autoOffsetReset = (selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''
|
const autoOffsetReset = (selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''
|
||||||
let command = "from kafka"
|
let command = "from kafka"
|
||||||
|
|
||||||
if(topic) {
|
if(topic) {
|
||||||
@@ -15063,15 +15197,15 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
} else {
|
} else {
|
||||||
command = `${command},group.id=${selectedTrigger.id}`
|
command = `${command},group.id=${selectedTrigger.id}`
|
||||||
}
|
}
|
||||||
// if(autoOffsetReset) {
|
if(autoOffsetReset) {
|
||||||
// command = `${command},auto.offset.reset=${autoOffsetReset}`
|
command = `${command},auto.offset.reset=${autoOffsetReset}`
|
||||||
// } else {
|
} else {
|
||||||
// command = `${command},auto.offset.reset=earliest`
|
command = `${command},auto.offset.reset=earliest`
|
||||||
|
|
||||||
// }
|
}
|
||||||
command = `${command},auto.offset.reset=earliest`
|
command = `${command},auto.offset.reset=earliest`
|
||||||
command = `${command},client.id=${selectedTrigger.id},enable.auto.commit=true,auto.commit.interval.ms=1`
|
command = `${command},client.id=${selectedTrigger.id},enable.auto.commit=true,auto.commit.interval.ms=1`
|
||||||
command = `${command} read json | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
|
command = `${command} read json | to ${url}`
|
||||||
|
|
||||||
const pipelineConfig = {
|
const pipelineConfig = {
|
||||||
command: command,
|
command: command,
|
||||||
@@ -15081,9 +15215,11 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
workflow_id: workflow.id,
|
workflow_id: workflow.id,
|
||||||
trigger_id: selectedTrigger.id,
|
trigger_id: selectedTrigger.id,
|
||||||
start_node: "",
|
start_node: "",
|
||||||
|
url: url,
|
||||||
};
|
};
|
||||||
submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
|
submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
|
||||||
}}
|
}
|
||||||
|
}}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
Start
|
Start
|
||||||
@@ -18599,6 +18735,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const envStatus = !(executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0) ? "loading" : "success"
|
||||||
|
|
||||||
var executionDelay = -75
|
var executionDelay = -75
|
||||||
const executionModal = (
|
const executionModal = (
|
||||||
@@ -19031,26 +19168,6 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{executionData.status === "EXECUTING" ? (
|
|
||||||
<Tooltip
|
|
||||||
color="primary"
|
|
||||||
title="Abort workflow"
|
|
||||||
placement="top"
|
|
||||||
style={{ zIndex: 50000 }}
|
|
||||||
>
|
|
||||||
<span style={{}}>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
style={{ float: "right", marginTop: 20, marginLeft: 10 }}
|
|
||||||
onClick={() => {
|
|
||||||
abortExecution();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PauseIcon style={{}} />
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -19124,18 +19241,41 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{isCloud ?
|
{executionData.status === "EXECUTING" ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
color="primary"
|
color="primary"
|
||||||
title="Explore logs for the workflow"
|
title="Abort workflow"
|
||||||
placement="top"
|
placement="top"
|
||||||
style={{ zIndex: 50000, }}
|
style={{ zIndex: 50000 }}
|
||||||
>
|
>
|
||||||
<span style={{}}>
|
<span style={{}}>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
style={{ float: "right", marginTop: 20, marginLeft: 10 }}
|
style={{ float: "right", marginTop: 20, marginLeft: 10 }}
|
||||||
disabled={userdata.region_url !== "https://shuffler.io"}
|
onClick={() => {
|
||||||
|
abortExecution();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PauseIcon style={{}} />
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{isCloud ?
|
||||||
|
<Tooltip
|
||||||
|
color="primary"
|
||||||
|
title="Explore logs for the workflow (max 5 days ago)"
|
||||||
|
placement="top"
|
||||||
|
style={{ zIndex: 50000, }}
|
||||||
|
>
|
||||||
|
<span style={{}}>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
style={{ float: "right", marginTop: 20, marginLeft: 10 }}
|
||||||
|
|
||||||
|
// Max 5 days in the past
|
||||||
|
disabled={userdata.region_url !== "https://shuffler.io" || executionData.started_at < (Math.floor(Date.now() / 1000) - 432000)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toast("Opening logs in a new tab")
|
toast("Opening logs in a new tab")
|
||||||
|
|
||||||
@@ -19144,7 +19284,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
}, 250)
|
}, 250)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<InsightsIcon color="secondary" />
|
<InsightsIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -19155,7 +19295,18 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
|
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
|
||||||
<div style={{ display: "flex", marginLeft: 10, }}>
|
<div style={{ display: "flex", marginLeft: 10, }}>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
<b>Env </b>
|
|
||||||
|
{/*envStatus === "success" ?
|
||||||
|
<Tooltip title="Environment is healthy" placement="top">
|
||||||
|
<CheckCircleIcon style={{ color: "green" }} />
|
||||||
|
</Tooltip>
|
||||||
|
: envStatus === "failure" ?
|
||||||
|
<Tooltip title="Environment is unhealthy" placement="top">
|
||||||
|
<ErrorIcon style={{ color: "red" }} />
|
||||||
|
</Tooltip>
|
||||||
|
: null*/}
|
||||||
|
|
||||||
|
<b style={{ }}>Env </b>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body1" color="textSecondary" style={{color: "#f85a3e", cursor: "pointer", }} onClick={() => {
|
<Typography variant="body1" color="textSecondary" style={{color: "#f85a3e", cursor: "pointer", }} onClick={() => {
|
||||||
@@ -20013,7 +20164,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
return "The app's Docker Image is not available in the environment yet. Re-run the app to force a re-download of the app. If the problem persists, contact support"
|
return "The app's Docker Image is not available in the environment yet. Re-run the app to force a re-download of the app. If the problem persists, contact support"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.status !== 200 && result.url !== undefined && result.url !== null && (result.url.includes("192.168") || result.url.includes("172.16") || result.url.includes("10.0"))) {
|
if (result.status !== 200 && result.url !== undefined && result.url !== null && typeof result.url === "string" && (result.url.includes("192.168") || result.url.includes("172.16") || result.url.includes("10.0"))) {
|
||||||
return "Consider whether your Orborus environment can connect to a local IP or not."
|
return "Consider whether your Orborus environment can connect to a local IP or not."
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21641,146 +21792,142 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const tenzirConfigModal = tenzirConfigModalOpen ? (
|
const TenzirConfigModal = () => {
|
||||||
<Dialog
|
if (!tenzirConfigModalOpen) return null;
|
||||||
PaperComponent={PaperComponent}
|
|
||||||
hideBackdrop={true}
|
return (
|
||||||
disableEnforceFocus={true}
|
<Dialog
|
||||||
disableBackdropClick={true}
|
PaperComponent={PaperComponent}
|
||||||
style={{ pointerEvents: "none" }}
|
hideBackdrop={true}
|
||||||
open={tenzirConfigModalOpen}
|
disableEnforceFocus={true}
|
||||||
PaperProps={{
|
disableBackdropClick={true}
|
||||||
style: {
|
style={{ pointerEvents: "none" }}
|
||||||
pointerEvents: "auto",
|
open={tenzirConfigModalOpen}
|
||||||
color: "white",
|
PaperProps={{
|
||||||
minWidth: 600,
|
style: {
|
||||||
minHeight: 450,
|
pointerEvents: "auto",
|
||||||
maxHeight: 450,
|
color: "white",
|
||||||
padding: 15,
|
minWidth: 600,
|
||||||
overflow: "hidden",
|
minHeight: 550,
|
||||||
zIndex: 10012,
|
maxHeight: 550,
|
||||||
border: theme.palette.defaultBorder,
|
padding: 15,
|
||||||
},
|
overflow: "hidden",
|
||||||
}}
|
zIndex: 10012,
|
||||||
>
|
border: theme.palette.defaultBorder,
|
||||||
<div
|
},
|
||||||
style={{
|
}}
|
||||||
flex: 2,
|
>
|
||||||
padding: 0,
|
<DialogTitle id="tenzir-config-modal" style={{ cursor: "move" }}>
|
||||||
minHeight: isMobile ? "90%" : 700,
|
<div style={{ color: "white" }}>Configuration options for Kafka</div>
|
||||||
maxHeight: isMobile ? "90%" : 700,
|
</DialogTitle>
|
||||||
overflowY: "auto",
|
<DialogContent>
|
||||||
overflowX: isMobile ? "auto" : "hidden",
|
{selectedOption === "Kafka Queue" ? (
|
||||||
}}
|
<div>
|
||||||
>
|
<b>Topic</b>
|
||||||
<DialogTitle id="tenzir-config-modal" style={{ cursor: "move" }}>
|
<TextField
|
||||||
<div style={{ color: "white" }}>Configuration options for {selectedOption}</div>
|
id="topic"
|
||||||
</DialogTitle>
|
style={{
|
||||||
<DialogContent>
|
backgroundColor: theme.palette.inputColor,
|
||||||
{selectedOption === "Kafka Queue" && (
|
borderRadius: theme.palette.borderRadius,
|
||||||
<>
|
|
||||||
<b>Topic</b>
|
|
||||||
<TextField
|
|
||||||
id="topic"
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
borderRadius: theme.palette.borderRadius,
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
style: {},
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
color="primary"
|
|
||||||
placeholder={"topic name"}
|
|
||||||
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || ''}
|
|
||||||
/>
|
|
||||||
<b>bootstrap.servers</b>
|
|
||||||
<TextField
|
|
||||||
id="bootstrap_servers"
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
borderRadius: theme.palette.borderRadius,
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
style: {},
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
color="primary"
|
|
||||||
placeholder={"broker1.example.com:9092,192.168.1.100:9092"}
|
|
||||||
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || ''}
|
|
||||||
/>
|
|
||||||
<b>group.id</b>
|
|
||||||
<TextField
|
|
||||||
id="group_id"
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
borderRadius: theme.palette.borderRadius,
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
style: {},
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
color="primary"
|
|
||||||
placeholder={"tenzir"}
|
|
||||||
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || ''}
|
|
||||||
/>
|
|
||||||
{/* <b>auto.offest.reset</b>
|
|
||||||
<TextField
|
|
||||||
id="auto_offset_reset"
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
borderRadius: theme.palette.borderRadius,
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
style: {},
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
color="primary"
|
|
||||||
placeholder={"earliest"}
|
|
||||||
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''}
|
|
||||||
/> */}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button
|
|
||||||
style={{ borderRadius: "0px" }}
|
|
||||||
onClick={() => {
|
|
||||||
setTenzirConfigModalOpen(false);
|
|
||||||
}}
|
}}
|
||||||
color="primary"
|
InputProps={{
|
||||||
>
|
style: {},
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
style={{ borderRadius: "0px" }}
|
|
||||||
onClick={() => {
|
|
||||||
handleKafkaSubmit(selectedTrigger);
|
|
||||||
}}
|
}}
|
||||||
|
fullWidth
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
placeholder={"topic name"}
|
||||||
Submit
|
defaultValue={
|
||||||
</Button>
|
selectedTrigger?.parameters?.find(
|
||||||
</DialogActions>
|
(param) => param.name === "topic",
|
||||||
</div>
|
)?.value || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<b>bootstrap.servers</b>
|
||||||
|
<TextField
|
||||||
|
id="bootstrap_servers"
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {},
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
color="primary"
|
||||||
|
placeholder={"broker1.example.com:9092,192.168.1.100:9092"}
|
||||||
|
defaultValue={
|
||||||
|
selectedTrigger?.parameters?.find(
|
||||||
|
(param) => param.name === "bootstrap_servers",
|
||||||
|
)?.value || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<b>group.id</b>
|
||||||
|
<TextField
|
||||||
|
id="group_id"
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {},
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
color="primary"
|
||||||
|
placeholder={"tenzir"}
|
||||||
|
defaultValue={
|
||||||
|
selectedTrigger?.parameters?.find(
|
||||||
|
(param) => param.name === "group_id",
|
||||||
|
)?.value || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<b>auto.offest.reset</b>
|
||||||
|
<TextField
|
||||||
|
id="auto_offset_reset"
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {},
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
color="primary"
|
||||||
|
placeholder={"earliest"}
|
||||||
|
defaultValue={
|
||||||
|
selectedTrigger?.parameters?.find(
|
||||||
|
(param) => param.name === "auto_offset_reset",
|
||||||
|
)?.value || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}{" "}
|
||||||
|
|
||||||
<IconButton
|
</DialogContent>
|
||||||
style={{
|
|
||||||
zIndex: 5000,
|
<DialogActions>
|
||||||
position: "absolute",
|
<Button
|
||||||
top: 14,
|
style={{ borderRadius: "0px" }}
|
||||||
right: 18,
|
|
||||||
color: "grey",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTenzirConfigModalOpen(false);
|
setTenzirConfigModalOpen(false);
|
||||||
}}
|
}}
|
||||||
|
color="primary"
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
Cancel
|
||||||
</IconButton>
|
</Button>
|
||||||
</Dialog>
|
<Button
|
||||||
) : null;
|
style={{ borderRadius: "0px" }}
|
||||||
|
onClick={() => {
|
||||||
|
handleSubmit(selectedTrigger);
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SuggestionBoxUi = () => {
|
const SuggestionBoxUi = () => {
|
||||||
@@ -22173,7 +22320,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
</div>
|
</div>
|
||||||
<div style={{textAlign: "center", color: "white", flex: 1, paddingTop: 20, }}>
|
<div style={{textAlign: "center", color: "white", flex: 1, paddingTop: 20, }}>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
{selectedVersion.name}
|
{selectedVersion?.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
{/* Cross icon to close it */}
|
{/* Cross icon to close it */}
|
||||||
@@ -22410,7 +22557,7 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
{codePopoutModal}
|
{codePopoutModal}
|
||||||
{workflowRevisions}
|
{workflowRevisions}
|
||||||
{authenticationModal}
|
{authenticationModal}
|
||||||
{tenzirConfigModal}
|
{<TenzirConfigModal/>}
|
||||||
{/*editWorkflowModal*/}
|
{/*editWorkflowModal*/}
|
||||||
{authgroupModal}
|
{authgroupModal}
|
||||||
{executionArgumentModal}
|
{executionArgumentModal}
|
||||||
|
|||||||
@@ -2586,6 +2586,8 @@ const Apps = (props) => {
|
|||||||
if (parsedtext.indexOf("openapi") === -1 && parsedtext.indexOf("swagger") === -1) {
|
if (parsedtext.indexOf("openapi") === -1 && parsedtext.indexOf("swagger") === -1) {
|
||||||
setValidation(false);
|
setValidation(false);
|
||||||
setOpenApiError("Error in generation: "+parsedtext);
|
setOpenApiError("Error in generation: "+parsedtext);
|
||||||
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -2684,10 +2686,11 @@ const Apps = (props) => {
|
|||||||
body: openApidata,
|
body: openApidata,
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
|
||||||
setValidation(false);
|
setValidation(false);
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson.success) {
|
if (responseJson.success) {
|
||||||
setAppValidation(responseJson.id);
|
setAppValidation(responseJson.id);
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Box,
|
||||||
|
TextField,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import RuleCard from "./RuleCard";
|
||||||
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
|
||||||
|
const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) => {
|
||||||
|
|
||||||
|
if (!isTenzirActive) {
|
||||||
|
toast("connect to siem first for global enable/disable to work");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const action = folderDisabled ? "enable_folder" : "disable_folder";
|
||||||
|
const url = `${globalUrl}/api/v1/files/detection/${action}`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === true) {
|
||||||
|
if (action === "enable_folder") setFolderDisabled(false);
|
||||||
|
else setFolderDisabled(true);
|
||||||
|
} else {
|
||||||
|
//toast(`failed to disable rule`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in ${action} the rule: `, error);
|
||||||
|
toast(`An error occurred while ${action} the rule`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const Detection = ({
|
||||||
|
globalUrl,
|
||||||
|
ruleInfo,
|
||||||
|
folderDisabled,
|
||||||
|
setFolderDisabled,
|
||||||
|
isTenzirActive,
|
||||||
|
}) => {
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleConnectClick = () => {
|
||||||
|
if (!isTenzirActive) {
|
||||||
|
setLoading(true);
|
||||||
|
const url = `${globalUrl}/api/v1/detection/siem/connect`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
window.location.reload();
|
||||||
|
}, 15000);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
toast("Failed to connect to SIEM");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(`Error in connecting to SIEM: `, error);
|
||||||
|
toast("An error occurred while connecting to SIEM");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Already connected to SIEM");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredRules = ruleInfo?.filter((rule) =>
|
||||||
|
rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
rule.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container sx={{ mt: 4 }}>
|
||||||
|
<Box sx={{ border: "1px solid #ccc", borderRadius: 2, p: 3 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
|
Sigma Detection Rules
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleConnectClick}
|
||||||
|
disabled={loading} // Disable the button while loading
|
||||||
|
style={{ backgroundColor: isTenzirActive ? "green" : "red"}}
|
||||||
|
>
|
||||||
|
{loading ? <CircularProgress size={24} /> : isTenzirActive ? "Connected to siem" : "Connect to siem"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
label="Search rules"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
{/* <Button
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => uploadRef.current.click()}
|
||||||
|
>
|
||||||
|
<PublishIcon /> Upload sigma file
|
||||||
|
</Button>
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
ref={uploadRef}
|
||||||
|
onChange={(event) => {
|
||||||
|
uploadFiles(event.target.files);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Typography variant="body2" sx={{ mr: 1 }}>
|
||||||
|
Global disable/enable
|
||||||
|
</Typography>
|
||||||
|
<Switch
|
||||||
|
checked={!folderDisabled}
|
||||||
|
onChange={() =>
|
||||||
|
handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isTenzirActive)
|
||||||
|
}
|
||||||
|
disabled={!isTenzirActive}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "500px",
|
||||||
|
width: "100%",
|
||||||
|
overflowY: "auto",
|
||||||
|
border: "1px solid #ddd",
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filteredRules?.length > 0 &&
|
||||||
|
filteredRules.map((card) => (
|
||||||
|
<RuleCard
|
||||||
|
key={card.file_id}
|
||||||
|
ruleName={card.title}
|
||||||
|
description={card.description}
|
||||||
|
file_id={card.file_id}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
folderDisabled={folderDisabled}
|
||||||
|
isTenzirActive={isTenzirActive}
|
||||||
|
{...card}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Detection;
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Container, CircularProgress, Typography } from "@mui/material";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import Detection from "./Detection";
|
||||||
|
|
||||||
|
const DetectionDashBoard = (props) => {
|
||||||
|
const { globalUrl } = props;
|
||||||
|
const [ruleInfo, setRuleInfo] = useState(null);
|
||||||
|
const [, setSelectedRule] = useState(null);
|
||||||
|
const [, setFileData] = useState("");
|
||||||
|
const [isTenzirActive, setIsTenzirActive] = useState(false);
|
||||||
|
const [folderDisabled, setFolderDisabled] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [importAttempts, setImportAttempts] = useState(0);
|
||||||
|
const maxImportAttempts = 2;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTimeout = setTimeout(() => {
|
||||||
|
fetchSigmaInfo();
|
||||||
|
}, 1000); // Delay by 1 second
|
||||||
|
|
||||||
|
return () => clearTimeout(fetchTimeout);
|
||||||
|
}, [globalUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ruleInfo && ruleInfo.length === 0 && importAttempts < maxImportAttempts) {
|
||||||
|
importSigmaFromUrl();
|
||||||
|
}
|
||||||
|
}, [ruleInfo]);
|
||||||
|
|
||||||
|
const openEditBar = (rule) => {
|
||||||
|
setSelectedRule(rule);
|
||||||
|
fetchFileContent(rule.file_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = (updatedContent) => {
|
||||||
|
toast("This will be saved");
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFileContent = (file_id) => {
|
||||||
|
setFileData("");
|
||||||
|
fetch(`${globalUrl}/api/v1/files/${file_id}/content`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for file :O!");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((respdata) => {
|
||||||
|
if (respdata.length === 0) {
|
||||||
|
toast("Failed getting file. Is it deleted?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setFileData(respdata);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast(error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchSigmaInfo = () => {
|
||||||
|
const url = `${globalUrl}/api/v1/files/detection/sigma_rules`;
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson["success"] === false) {
|
||||||
|
toast("Failed to get sigma rules");
|
||||||
|
} else {
|
||||||
|
setRuleInfo(responseJson.sigma_info || []);
|
||||||
|
setFolderDisabled(responseJson.folder_disabled);
|
||||||
|
setIsTenzirActive(responseJson.is_tenzir_active);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setIsLoading(false);
|
||||||
|
console.log("Error in getting sigma files: ", error);
|
||||||
|
toast("An error occurred while fetching sigma rules");
|
||||||
|
setRuleInfo([]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const importSigmaFromUrl = () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setImportAttempts((prevAttempts) => prevAttempts + 1);
|
||||||
|
|
||||||
|
const url = "https://github.com/satti-hari-krishna-reddy/shuffle_sigma";
|
||||||
|
const folder = "sigma";
|
||||||
|
|
||||||
|
const parsedData = {
|
||||||
|
url: url,
|
||||||
|
path: folder,
|
||||||
|
field_3: "main",
|
||||||
|
};
|
||||||
|
|
||||||
|
toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`);
|
||||||
|
fetch(`${globalUrl}/api/v1/files/download_remote_enhanced`, {
|
||||||
|
method: "POST",
|
||||||
|
mode: "cors",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(parsedData),
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success) {
|
||||||
|
toast("Successfully loaded files from " + url);
|
||||||
|
fetchSigmaInfo(); // Fetch again after successful import
|
||||||
|
} else {
|
||||||
|
toast(responseJson.reason ? `Failed loading: ${responseJson.reason}` : "Failed loading");
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast(error.toString());
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading && (!ruleInfo || ruleInfo.length === 0)) {
|
||||||
|
return (
|
||||||
|
<Container style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
|
||||||
|
<div>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography variant="h6" style={{ marginTop: 20 }}>Downloading rules, please wait...</Typography>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container style={{ display: "flex" }}>
|
||||||
|
<Detection
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
ruleInfo={ruleInfo}
|
||||||
|
folderDisabled={folderDisabled}
|
||||||
|
setFolderDisabled={setFolderDisabled}
|
||||||
|
isTenzirActive={isTenzirActive}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetectionDashBoard;
|
||||||
@@ -122,7 +122,7 @@ export const Paragrah = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="sdf">
|
<div>
|
||||||
{element}
|
{element}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -457,6 +457,30 @@ const Docs = (defaultprops) => {
|
|||||||
minHeight: "80vh",
|
minHeight: "80vh",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const noteLabelStyle = {
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#f86a3e",
|
||||||
|
display: "block",
|
||||||
|
marginBottom: "5px",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Blockquote = ({ children }) => {
|
||||||
|
|
||||||
|
const textContent = children.map(child =>
|
||||||
|
child.props && child.props.children ? child.props.children.join('') : child
|
||||||
|
).join('').trim();
|
||||||
|
|
||||||
|
// Maybe some more contents....
|
||||||
|
const isNote = textContent.startsWith("[!TIP]");
|
||||||
|
return (
|
||||||
|
<blockquote style={isNote ? alertNote : {}}>
|
||||||
|
{isNote && <span style={noteLabelStyle}>Tips:</span>}
|
||||||
|
{isNote ? textContent.replace("[!TIP]", "").trim() : children}
|
||||||
|
</blockquote>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const Heading = (props) => {
|
const Heading = (props) => {
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
var id = props.children[0].toLowerCase().toString()
|
var id = props.children[0].toLowerCase().toString()
|
||||||
@@ -840,6 +864,12 @@ const Docs = (defaultprops) => {
|
|||||||
fontSize: isMobile ? "1.3rem" : "1.1rem",
|
fontSize: isMobile ? "1.3rem" : "1.1rem",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const alertNote = {
|
||||||
|
padding: "10px",
|
||||||
|
borderLeft: "5px solid #f86a3e",
|
||||||
|
backgroundColor: "rgb(26,26,26)",
|
||||||
|
};
|
||||||
|
|
||||||
const CustomButton = (props) => {
|
const CustomButton = (props) => {
|
||||||
const { title, icon, link } = props
|
const { title, icon, link } = props
|
||||||
|
|
||||||
@@ -974,9 +1004,11 @@ const Docs = (defaultprops) => {
|
|||||||
h6: Heading,
|
h6: Heading,
|
||||||
a: OuterLink,
|
a: OuterLink,
|
||||||
p: Paragrah,
|
p: Paragrah,
|
||||||
|
blockquote: Blockquote,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// PostDataBrowser Section
|
// PostDataBrowser Section
|
||||||
const postDataBrowser =
|
const postDataBrowser =
|
||||||
list === undefined || list === null ? null : (
|
list === undefined || list === null ? null : (
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Typography, Button, TextField } from '@mui/material';
|
||||||
|
|
||||||
|
const EditComponent = ({ ruleName, description, content, setContent, lastEdited, editedBy, onSave }) => {
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onSave(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: 2, height: '100%', width: '100%', marginTop:'30px'}}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Typography variant="h6">{ruleName}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body2" style={{ marginTop: '2%' }}>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ mt: 1 }}>
|
||||||
|
Last edited: {lastEdited}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Edited By: {editedBy}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<TextField
|
||||||
|
multiline
|
||||||
|
rows={12}
|
||||||
|
value={content}
|
||||||
|
onChange={(e) => setContent(e.target.value)}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2 }}>
|
||||||
|
<Button variant="contained" color="primary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditComponent;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
Switch,
|
||||||
|
} from "@mui/material";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
||||||
|
|
||||||
|
const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, ...otherProps }) => {
|
||||||
|
const [openCodeEditor, setOpenCodeEditor] = React.useState(false);
|
||||||
|
const [fileData, setFileData] = React.useState("");
|
||||||
|
const [isEnabled, setIsEnabled] = React.useState(otherProps.is_enabled);
|
||||||
|
|
||||||
|
const isCloud = ["localhost:3002", "shuffler.io"].includes(window.location.host);
|
||||||
|
|
||||||
|
const handleSwitchChange = (event) => {
|
||||||
|
if (folderDisabled) {
|
||||||
|
toast("enable the directory to enable individual rules");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isTenzirActive) {
|
||||||
|
toast("connect to the siem to enable/disable the rule");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newIsEnabled = event.target.checked;
|
||||||
|
toggleRule(file_id, !newIsEnabled, globalUrl, () => {
|
||||||
|
setIsEnabled(newIsEnabled);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateText = (text) => {
|
||||||
|
fetch(`${globalUrl}/api/v1/files/${file_id}/edit`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: text,
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Can't update file");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
toast("Successfully updated file");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast("Error updating file: " + error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card variant="outlined" sx={{ mb: 2 }}>
|
||||||
|
<CardContent>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6">{ruleName}</Typography>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Switch
|
||||||
|
checked={isEnabled && !folderDisabled}
|
||||||
|
onChange={handleSwitchChange}
|
||||||
|
disabled={!isTenzirActive}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Typography variant="body2" style={{ marginTop: '2%' }}>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<ShuffleCodeEditor
|
||||||
|
isCloud={isCloud}
|
||||||
|
expansionModalOpen={openCodeEditor}
|
||||||
|
setExpansionModalOpen={setOpenCodeEditor}
|
||||||
|
setcodedata={setFileData}
|
||||||
|
codedata={fileData}
|
||||||
|
isFileEditor={true}
|
||||||
|
key={fileData} // https://reactjs.org/docs/reconciliation.html#recursing-on-children
|
||||||
|
runUpdateText={UpdateText}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => {
|
||||||
|
const action = isCurrentlyEnabled ? "disable" : "enable";
|
||||||
|
const url = `${globalUrl}/api/v1/files/detection/${fileId}/${action}_rule`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
response.json().then((responseJson) => {
|
||||||
|
if (responseJson["success"] === false) {
|
||||||
|
toast(`Failed to ${action} the rule`);
|
||||||
|
} else {
|
||||||
|
toast(`Rule ${action}d successfully`);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error in ${action}ing the rule: `, error);
|
||||||
|
toast(`An error occurred while ${action}ing the rule`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditBar = (file_id, setOpenCodeEditor, setFileData, globalUrl) => {
|
||||||
|
getFileContent(file_id, setFileData, globalUrl)
|
||||||
|
|
||||||
|
setOpenCodeEditor(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileContent = (file_id, setFileData, globalUrl) => {
|
||||||
|
setFileData("");
|
||||||
|
fetch(globalUrl + "/api/v1/files/" + file_id + "/content", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for file :O!");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((respdata) => {
|
||||||
|
if (respdata.length === 0) {
|
||||||
|
toast("Failed getting file. Is it deleted?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return respdata
|
||||||
|
})
|
||||||
|
.then((responseData) => {
|
||||||
|
|
||||||
|
setFileData(responseData);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast(error.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default RuleCard;
|
||||||
+500
-115
@@ -10,7 +10,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shuffle/shuffle-shared"
|
"github.com/shuffle/shuffle-shared"
|
||||||
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
//"os/signal"
|
//"os/signal"
|
||||||
//"syscall"
|
//"syscall"
|
||||||
@@ -101,6 +102,7 @@ var swarmNetworkName = os.Getenv("SHUFFLE_SWARM_NETWORK_NAME")
|
|||||||
var orborusLabel = os.Getenv("SHUFFLE_ORBORUS_LABEL")
|
var orborusLabel = os.Getenv("SHUFFLE_ORBORUS_LABEL")
|
||||||
var memcached = os.Getenv("SHUFFLE_MEMCACHED")
|
var memcached = os.Getenv("SHUFFLE_MEMCACHED")
|
||||||
var tenzirUrl = os.Getenv("SHUFFLE_TENZIR_URL")
|
var tenzirUrl = os.Getenv("SHUFFLE_TENZIR_URL")
|
||||||
|
var apiKey = os.Getenv("AUTH_FOR_ORBORUS")
|
||||||
|
|
||||||
var executionIds = []string{}
|
var executionIds = []string{}
|
||||||
var namespacemade = false // For K8s
|
var namespacemade = false // For K8s
|
||||||
@@ -1805,6 +1807,12 @@ func main() {
|
|||||||
log.Printf("[WARNING] Defaulting to environment name %s. Set environment variable ENVIRONMENT_NAME to change. This should be the same as in the frontend action.", environment)
|
log.Printf("[WARNING] Defaulting to environment name %s. Set environment variable ENVIRONMENT_NAME to change. This should be the same as in the frontend action.", environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tenzirUrl == "" {
|
||||||
|
tenzirUrl = "http://localhost:5160"
|
||||||
|
log.Printf("[WARNING] SHUFFLE_TENZIR_URL not set, falling back to default URL: %s",tenzirUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// FIXME - during init, BUILD and/or LOAD worker and app_sdk
|
// FIXME - during init, BUILD and/or LOAD worker and app_sdk
|
||||||
// Build/load app_sdk so it can be loaded as 127.0.0.1:5000/walkoff_app_sdk
|
// Build/load app_sdk so it can be loaded as 127.0.0.1:5000/walkoff_app_sdk
|
||||||
log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
|
log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
|
||||||
@@ -1906,6 +1914,7 @@ func main() {
|
|||||||
log.Printf("[INFO] Waiting for executions at %s with Environment %#v", fullUrl, environment)
|
log.Printf("[INFO] Waiting for executions at %s with Environment %#v", fullUrl, environment)
|
||||||
hasStarted := false
|
hasStarted := false
|
||||||
for {
|
for {
|
||||||
|
_ = sendTenzirHealthStatus()
|
||||||
if req.Method == "POST" {
|
if req.Method == "POST" {
|
||||||
// Should find data to send (memory etc.)
|
// Should find data to send (memory etc.)
|
||||||
|
|
||||||
@@ -2021,6 +2030,71 @@ func main() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||||
|
|
||||||
|
} else if incRequest.Type == "CATEGORY_UPDATE" {
|
||||||
|
|
||||||
|
err := deployTenzirNode()
|
||||||
|
if err != nil{
|
||||||
|
log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handleFileCategoryChange()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to download the file category: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||||
|
|
||||||
|
} else if incRequest.Type == "DISABLE_SIGMA_FILE" {
|
||||||
|
fileName := incRequest.ExecutionArgument
|
||||||
|
err := deployTenzirNode()
|
||||||
|
if err != nil{
|
||||||
|
log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = disableRule(fileName)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||||
|
|
||||||
|
} else if incRequest.Type == "ENABLE_SIGMA_FILE" {
|
||||||
|
fileName := incRequest.ExecutionArgument
|
||||||
|
err := deployTenzirNode()
|
||||||
|
if err != nil{
|
||||||
|
log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = enableRule(fileName)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||||
|
|
||||||
|
} else if incRequest.Type == "DISABLE_SIGMA_FOLDER" {
|
||||||
|
|
||||||
|
err := deployTenzirNode()
|
||||||
|
if err != nil{
|
||||||
|
log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = removeAllFiles()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to disable the sigma rules: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||||
|
} else if incRequest.Type == "START_TENZIR" {
|
||||||
|
|
||||||
|
err := deployTenzirNode()
|
||||||
|
if err != nil{
|
||||||
|
log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
newrequests = append(newrequests, incRequest)
|
newrequests = append(newrequests, incRequest)
|
||||||
}
|
}
|
||||||
@@ -2211,7 +2285,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2371,11 +2444,6 @@ func main() {
|
|||||||
// docker run tenzir/tenzir:latest 'from http://192.168.86.44:5002/api/v1/orgs/7e9b9007-5df2-4b47-bca5-c4d267ef2943/cache/CIDR%20ranges?type=text&authorization=cec9d01f-09b2-4419-8a0a-76c6046e3fef read lines | to http://192.168.86.44:5002/api/v1/hooks/webhook_665ace5f-f27b-496a-a365-6e07eb61078c write lines'
|
// docker run tenzir/tenzir:latest 'from http://192.168.86.44:5002/api/v1/orgs/7e9b9007-5df2-4b47-bca5-c4d267ef2943/cache/CIDR%20ranges?type=text&authorization=cec9d01f-09b2-4419-8a0a-76c6046e3fef read lines | to http://192.168.86.44:5002/api/v1/hooks/webhook_665ace5f-f27b-496a-a365-6e07eb61078c write lines'
|
||||||
func handlePipeline(incRequest shuffle.ExecutionRequest) error {
|
func handlePipeline(incRequest shuffle.ExecutionRequest) error {
|
||||||
|
|
||||||
if tenzirUrl == "" {
|
|
||||||
tenzirUrl = "http://localhost:5160"
|
|
||||||
log.Printf("[WARNING] SHUFFLE_TENZIR_URL not set, falling back to default URL: %s", tenzirUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := deployTenzirNode()
|
err := deployTenzirNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err)
|
log.Printf("[ERROR] failed to deploy the pipeline, reason: %s", err)
|
||||||
@@ -2419,7 +2487,7 @@ func handlePipeline(incRequest shuffle.ExecutionRequest) error {
|
|||||||
log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err)
|
log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = updatePipelineState(pipelineId, "stop")
|
_, err = updatePipelineState(command, pipelineId, "stop")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to stop Pipeline: %s reason:%s ", pipelineId, err)
|
log.Printf("[ERROR] Failed to stop Pipeline: %s reason:%s ", pipelineId, err)
|
||||||
return err
|
return err
|
||||||
@@ -2439,7 +2507,7 @@ func handlePipeline(incRequest shuffle.ExecutionRequest) error {
|
|||||||
log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err)
|
log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = updatePipelineState(pipelineId, "start")
|
_, err = updatePipelineState(command, pipelineId, "start")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to start Pipeline: %s reason:%s ", pipelineId, err)
|
log.Printf("[ERROR] Failed to start Pipeline: %s reason:%s ", pipelineId, err)
|
||||||
return err
|
return err
|
||||||
@@ -2472,9 +2540,19 @@ func deployTenzirNode() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
containerInfo, err := dockercli.ContainerInspect(ctx, containerName)
|
containerInfo, err := dockercli.ContainerInspect(ctx, containerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if dockerclient.IsErrNotFound(err) {
|
if dockerclient.IsErrNotFound(err) {
|
||||||
|
// Create network if it doesn't exist
|
||||||
|
networkName := "tenzir-network"
|
||||||
|
networkSubnet := "192.168.1.0/24"
|
||||||
|
networkGateway := "192.168.1.1"
|
||||||
|
|
||||||
|
err = createNetworkIfNotExists(ctx, networkName, networkSubnet, networkGateway)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to create network: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Check if image exists
|
// Check if image exists
|
||||||
_, _, err := dockercli.ImageInspectWithRaw(ctx, imageName)
|
_, _, err := dockercli.ImageInspectWithRaw(ctx, imageName)
|
||||||
@@ -2535,30 +2613,6 @@ func deployTenzirNode() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTenzirNode() error {
|
|
||||||
retries := 20
|
|
||||||
retryInterval := 3 * time.Second
|
|
||||||
url := fmt.Sprintf("%s/api/v0/ping", tenzirUrl)
|
|
||||||
forwardMethod := "POST"
|
|
||||||
|
|
||||||
client := http.Client{}
|
|
||||||
req, err := http.NewRequest(forwardMethod, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[ERROR] Failed to create HTTP request: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < retries; i++ {
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err == nil && resp.StatusCode == http.StatusOK {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(retryInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("tenzir node is not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAndStartTenzirNode(ctx context.Context, containerName, imageName string, containerStartOptions container.StartOptions) error {
|
func createAndStartTenzirNode(ctx context.Context, containerName, imageName string, containerStartOptions container.StartOptions) error {
|
||||||
healthconfig := &container.HealthConfig{
|
healthconfig := &container.HealthConfig{
|
||||||
Test: []string{"tenzir --connection-timeout=30s --connection-retry-delay=1s 'api /ping'"},
|
Test: []string{"tenzir --connection-timeout=30s --connection-retry-delay=1s 'api /ping'"},
|
||||||
@@ -2574,23 +2628,34 @@ func createAndStartTenzirNode(ctx context.Context, containerName, imageName stri
|
|||||||
Entrypoint: []string{containerName},
|
Entrypoint: []string{containerName},
|
||||||
}
|
}
|
||||||
|
|
||||||
hostConfig := &container.HostConfig{
|
hostConfig := &container.HostConfig{
|
||||||
PortBindings: nat.PortMap{
|
PortBindings: nat.PortMap{
|
||||||
"5160/tcp": []nat.PortBinding{{HostPort: "5160"}},
|
"5160/tcp": []nat.PortBinding{{HostPort: "5160"}},
|
||||||
},
|
},
|
||||||
Mounts: []mount.Mount{
|
Mounts: []mount.Mount{
|
||||||
{
|
{
|
||||||
Type: mount.TypeVolume,
|
Type: mount.TypeVolume,
|
||||||
Source: containerName,
|
Source: containerName,
|
||||||
Target: "/var/lib/tenzir/",
|
Target: "/var/lib/tenzir/",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
VolumeDriver: "local",
|
VolumeDriver: "local",
|
||||||
}
|
}
|
||||||
_, err := dockercli.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName)
|
|
||||||
if err != nil {
|
networkingConfig := &network.NetworkingConfig{
|
||||||
return err
|
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||||
}
|
"tenzir-network": {
|
||||||
|
IPAMConfig: &network.EndpointIPAMConfig{
|
||||||
|
IPv4Address: "192.168.1.100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = dockercli.ContainerStart(ctx, containerName, containerStartOptions)
|
err = dockercli.ContainerStart(ctx, containerName, containerStartOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2599,16 +2664,76 @@ func createAndStartTenzirNode(ctx context.Context, containerName, imageName stri
|
|||||||
}
|
}
|
||||||
log.Printf("[INFO] Tenzir Node container started successfully")
|
log.Printf("[INFO] Tenzir Node container started successfully")
|
||||||
|
|
||||||
log.Printf("[INFO] Waiting for Tenzir to become available ...")
|
log.Printf("[INFO] Waiting for Tenzir to become available ...")
|
||||||
err = checkTenzirNode()
|
err = checkTenzirNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("[INFO] Successfully deployed Tenzir Node !")
|
log.Printf("[INFO] Successfully deployed Tenzir Node!")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createNetworkIfNotExists(ctx context.Context, networkName, subnet, gateway string) error {
|
||||||
|
networks, err := dockercli.NetworkList(ctx, types.NetworkListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range networks {
|
||||||
|
if network.Name == networkName {
|
||||||
|
// Network exists
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipamConfig := &network.IPAM{
|
||||||
|
Config: []network.IPAMConfig{
|
||||||
|
{
|
||||||
|
Subnet: subnet,
|
||||||
|
Gateway: gateway,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
networkCreate := types.NetworkCreate{
|
||||||
|
CheckDuplicate: true,
|
||||||
|
Driver: "bridge",
|
||||||
|
IPAM: ipamConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = dockercli.NetworkCreate(ctx, networkName, networkCreate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkTenzirNode() error {
|
||||||
|
retries := 5
|
||||||
|
retryInterval := 3 * time.Second
|
||||||
|
url := fmt.Sprintf("%s/api/v0/ping",tenzirUrl)
|
||||||
|
forwardMethod := "POST"
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(forwardMethod, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to create HTTP request: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err == nil && resp.StatusCode == http.StatusOK {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(retryInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("tenzir node is not available")
|
||||||
|
}
|
||||||
|
|
||||||
func createPipeline(command, identifier string) (string, error) {
|
func createPipeline(command, identifier string) (string, error) {
|
||||||
|
|
||||||
toBeDeleted := false
|
toBeDeleted := false
|
||||||
@@ -2627,32 +2752,36 @@ func createPipeline(command, identifier string) (string, error) {
|
|||||||
log.Printf("[INFO] an existing pipeline found with ID: %s. it will be deleted", pipelineId)
|
log.Printf("[INFO] an existing pipeline found with ID: %s. it will be deleted", pipelineId)
|
||||||
toBeDeleted = true
|
toBeDeleted = true
|
||||||
}
|
}
|
||||||
if strings.Contains(command, "shuffler.io") {
|
// if strings.Contains(command, "shuffler.io") {
|
||||||
|
|
||||||
} else {
|
// } else {
|
||||||
var scheme string
|
// var scheme string
|
||||||
if strings.Contains(command, "http://") {
|
// if strings.Contains(command, "http://") {
|
||||||
scheme = "http://"
|
// scheme = "http://"
|
||||||
} else if strings.Contains(command, "https://") {
|
// } else if strings.Contains(command, "https://") {
|
||||||
scheme = "https://"
|
// scheme = "https://"
|
||||||
}
|
// }
|
||||||
|
|
||||||
startIndex := strings.Index(command, scheme)
|
// startIndex := strings.Index(command, scheme)
|
||||||
if startIndex != -1 {
|
// if startIndex != -1 {
|
||||||
endIndex := startIndex + len(scheme)
|
// endIndex := startIndex + len(scheme)
|
||||||
endIndex += strings.Index(command[endIndex:], "/")
|
// endIndex += strings.Index(command[endIndex:], "/")
|
||||||
|
|
||||||
|
// command = command[:startIndex] + baseUrl + command[endIndex:]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
//command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | sigma /var/lib/tenzir/rule.yaml"
|
||||||
|
//command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | import"
|
||||||
|
|
||||||
command = command[:startIndex] + baseUrl + command[endIndex:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestBody := map[string]interface{}{
|
requestBody := map[string]interface{}{
|
||||||
"definition": command,
|
"definition": command,
|
||||||
"name": identifier,
|
"name": identifier,
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"autostart": map[string]bool{
|
"autostart": map[string]bool{
|
||||||
"created": true,
|
"created": true,
|
||||||
"completed": true,
|
"completed": false,
|
||||||
"failed": true,
|
"failed": false,
|
||||||
},
|
},
|
||||||
"autodelete": map[string]bool{
|
"autodelete": map[string]bool{
|
||||||
"completed": false,
|
"completed": false,
|
||||||
@@ -2718,13 +2847,14 @@ func createPipeline(command, identifier string) (string, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePipelineState(pipelineId, action string) (string, error) {
|
func updatePipelineState(command, pipelineId, action string) (string, error) {
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/api/v0/pipeline/update", tenzirUrl)
|
url := fmt.Sprintf("%s/api/v0/pipeline/update", tenzirUrl)
|
||||||
forwardMethod := "POST"
|
forwardMethod := "POST"
|
||||||
|
|
||||||
requestBody := map[string]interface{}{
|
requestBody := map[string]interface{}{
|
||||||
"id": pipelineId,
|
"id": pipelineId,
|
||||||
|
"definition": command,
|
||||||
"action": action,
|
"action": action,
|
||||||
"autostart": map[string]bool{
|
"autostart": map[string]bool{
|
||||||
"created": true,
|
"created": true,
|
||||||
@@ -2870,53 +3000,308 @@ func searchPipeline(identifier string) (string, error) {
|
|||||||
return "", errors.New("no existing pipeline found with name")
|
return "", errors.New("no existing pipeline found with name")
|
||||||
}
|
}
|
||||||
|
|
||||||
// func savePipelineData(pipelineId, identifier, status string) error {
|
func handleFileCategoryChange() error {
|
||||||
|
apiEndpoint := baseUrl + "/api/v1/files/namespaces/sigma"
|
||||||
|
req, err := http.NewRequest("GET", apiEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// url := fmt.Sprintf("%s/api/v1/triggers/pipeline/save", baseUrl)
|
req.Header.Add("Authorization", "Bearer "+apiKey)
|
||||||
// identifierWithoutPrefix := strings.TrimPrefix(identifier, "shuffle-")
|
|
||||||
|
|
||||||
// forwardMethod := "PUT"
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// payload := map[string]interface{}{
|
if resp.StatusCode != http.StatusOK {
|
||||||
// "pipeline_id": pipelineId,
|
return fmt.Errorf("received non-200 response: %s", resp.Status)
|
||||||
// "trigger_id": identifierWithoutPrefix,
|
}
|
||||||
// "status": status,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// payloadBytes, err := json.Marshal(payload)
|
out, err := os.Create("files.zip")
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// log.Printf("[ERROR] Failed to marshal payload: %s", err)
|
return err
|
||||||
// return err
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// forwardData := bytes.NewBuffer(payloadBytes)
|
defer out.Close()
|
||||||
|
defer os.Remove("files.zip")
|
||||||
|
|
||||||
// req, err := http.NewRequest(
|
_, err = io.Copy(out, resp.Body)
|
||||||
// forwardMethod,
|
if err != nil {
|
||||||
// url,
|
return err
|
||||||
// forwardData,
|
}
|
||||||
// )
|
|
||||||
// if err != nil {
|
|
||||||
// log.Printf("[ERROR] Failed to create HTTP request: %s", err)
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// client := &http.Client{Timeout: 10 * time.Second}
|
log.Println("ZIP file downloaded successfully.")
|
||||||
// resp, err := client.Do(req)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Printf("[ERROR] Failed to send HTTP request: %s", err)
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// defer resp.Body.Close()
|
|
||||||
|
|
||||||
// if resp.StatusCode != 200 {
|
err = extractZIP("files.zip", "sigma_rules")
|
||||||
// log.Printf("[ERROR] Received non-successful HTTP status code: %d", resp.StatusCode)
|
if err != nil {
|
||||||
// return fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode)
|
return err
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return nil
|
destPath := "/var/lib/tenzir/sigma_rules"
|
||||||
// }
|
|
||||||
|
err = copyToTenzir("sigma_rules", destPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Files copied to container successfully.")
|
||||||
|
|
||||||
|
checkDisabledDirCmd := exec.Command("docker", "exec", "tenzir-node", "sh", "-c", "test -d /var/lib/tenzir/disabled_rules")
|
||||||
|
if err := checkDisabledDirCmd.Run(); err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||||
|
// Directory does not exist, nothing to do
|
||||||
|
log.Println("[DEBUG] /var/lib/tenzir/disabled_rules does not exist.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error checking disabled rules directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List files in /var/lib/tenzir/disabled_rules
|
||||||
|
listFilesCmd := exec.Command("docker", "exec", "tenzir-node", "sh", "-c", "ls /var/lib/tenzir/disabled_rules")
|
||||||
|
output, err := listFilesCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error listing files in disabled rules directory: %v, output: %s", err, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
files := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||||
|
for _, file := range files {
|
||||||
|
disabledFilePath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", file)
|
||||||
|
checkFileCmd := exec.Command("docker", "exec", "tenzir-node", "sh", "-c", fmt.Sprintf("test -f %s", disabledFilePath))
|
||||||
|
if err := checkFileCmd.Run(); err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||||
|
log.Printf("[ERROR] File does not exist: %s, moving on.\n", disabledFilePath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error checking file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFileCmd := exec.Command("docker", "exec", "-u", "root", "tenzir-node", "sh", "-c", fmt.Sprintf("rm -f %s", disabledFilePath))
|
||||||
|
if err := deleteFileCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error deleting file: %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Deleted file: %s\n", disabledFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractZIP(zipFile, destDir string) error {
|
||||||
|
r, err := zip.OpenReader(zipFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range r.File {
|
||||||
|
err := extractFile(f, destDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractFile(f *zip.File, destDir string) error {
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
path := filepath.Join(destDir, f.Name)
|
||||||
|
|
||||||
|
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, rc)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyToTenzir(srcPath, destPath string) error {
|
||||||
|
containerName := "tenzir-node"
|
||||||
|
|
||||||
|
checkCmd := exec.Command("docker", "exec", containerName, "test", "-d", destPath)
|
||||||
|
if err := checkCmd.Run(); err == nil {
|
||||||
|
rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", destPath)
|
||||||
|
if err := rmCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error removing existing directory in container: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cpCmd := exec.Command("docker", "cp", srcPath, fmt.Sprintf("%s:%s", containerName, destPath))
|
||||||
|
var out bytes.Buffer
|
||||||
|
cpCmd.Stdout = &out
|
||||||
|
cpCmd.Stderr = &out
|
||||||
|
|
||||||
|
err := cpCmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error copying files: %v, output: %s", err, out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeAllFiles() error {
|
||||||
|
containerName := "tenzir-node"
|
||||||
|
sigmaPath := "/var/lib/tenzir/sigma_rules/*"
|
||||||
|
|
||||||
|
checkCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("ls %s", sigmaPath))
|
||||||
|
checkOutput, checkErr := checkCmd.CombinedOutput()
|
||||||
|
if checkErr != nil {
|
||||||
|
if strings.Contains(string(checkOutput), "No such file or directory") {
|
||||||
|
return nil // nothing to delete
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error checking files: %v, output: %s", checkErr, checkOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("rm -rf %s", sigmaPath))
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error removing files: %v, output: %s", err, output)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFile(fileName string) error {
|
||||||
|
containerName := "tenzir-node"
|
||||||
|
srcPath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", fileName)
|
||||||
|
|
||||||
|
checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath))
|
||||||
|
if err := checkSrcCmd.Run(); err != nil {
|
||||||
|
// If the file does not exist, simply return nil
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||||
|
log.Printf("[ERROR] No such file: %s, nothing to delete\n", srcPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error checking source file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return removePath(containerName, srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePath(containerName, path string) error {
|
||||||
|
rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("rm -rf %s", path))
|
||||||
|
output, err := rmCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error removing path: %v, output: %s", err, output)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendTenzirHealthStatus() error {
|
||||||
|
var status string
|
||||||
|
url := fmt.Sprintf("%s/api/v1/detection/siem/node_health", baseUrl)
|
||||||
|
err := checkTenzirNode()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
status = "active"
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardMethod := "POST"
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to marshal payload: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
forwardData := bytes.NewBuffer(payloadBytes)
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
forwardMethod,
|
||||||
|
url,
|
||||||
|
forwardData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to create HTTP request: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to send HTTP request: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
log.Printf("[ERROR] Received non-successful HTTP status code: %d", resp.StatusCode)
|
||||||
|
return fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableRule(fileName string) error {
|
||||||
|
containerName := "tenzir-node"
|
||||||
|
srcPath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", fileName)
|
||||||
|
destDir := "/var/lib/tenzir/disabled_rules"
|
||||||
|
destPath := fmt.Sprintf("%s/%s", destDir, fileName)
|
||||||
|
|
||||||
|
checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath))
|
||||||
|
if err := checkSrcCmd.Run(); err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||||
|
fmt.Printf("File does not exist: %s\n", srcPath)
|
||||||
|
return nil // Nothing to disable
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error checking source file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDestDirCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mkdir -p %s", destDir))
|
||||||
|
if err := checkDestDirCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error ensuring destination directory exists: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
moveCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mv %s %s", srcPath, destPath))
|
||||||
|
if err := moveCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error moving file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("File %s moved to %s successfully.\n", fileName, destDir)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableRule(fileName string) error {
|
||||||
|
containerName := "tenzir-node"
|
||||||
|
srcPath := fmt.Sprintf("/var/lib/tenzir/disabled_rules/%s", fileName)
|
||||||
|
destDir := "/var/lib/tenzir/sigma_rules"
|
||||||
|
destPath := fmt.Sprintf("%s/%s", destDir, fileName)
|
||||||
|
|
||||||
|
checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath))
|
||||||
|
if err := checkSrcCmd.Run(); err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
||||||
|
fmt.Printf("File does not exist: %s\n", srcPath)
|
||||||
|
return nil // Nothing to enable
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error checking source file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDestDirCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mkdir -p %s", destDir))
|
||||||
|
if err := checkDestDirCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error ensuring destination directory exists: %v", err)
|
||||||
|
}
|
||||||
|
moveCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mv %s %s", srcPath, destPath))
|
||||||
|
if err := moveCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error moving file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("File %s moved to %s successfully.\n", fileName, destDir)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Is this ok to do with Docker? idk :)
|
// Is this ok to do with Docker? idk :)
|
||||||
func getRunningWorkers(ctx context.Context, workerTimeout int) int {
|
func getRunningWorkers(ctx context.Context, workerTimeout int) int {
|
||||||
|
|||||||
Reference in New Issue
Block a user