Resolved the conflicts

This commit is contained in:
monilprajapati
2024-08-01 00:31:01 +05:30
23 changed files with 2046 additions and 525 deletions
+4 -2
View File
@@ -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=
+22 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;"]
+3 -2
View File
@@ -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;
+3 -4
View File
@@ -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 "$@"
+6
View File
@@ -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"
+5 -6
View File
@@ -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>
+54 -18
View File
@@ -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", }}>
&nbsp; &nbsp;
</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", }}>
+1 -1
View File
@@ -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`
+18 -6
View File
@@ -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>
+330 -131
View File
@@ -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);
} }
+1 -1
View File
@@ -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, }}
+1 -1
View File
@@ -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
+351 -204
View File
@@ -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 &nbsp;&nbsp;&nbsp;&nbsp;</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 &nbsp;&nbsp;&nbsp;&nbsp;</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}
+5 -2
View File
@@ -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);
+198
View File
@@ -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;
+162
View File
@@ -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;
+33 -1
View File
@@ -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 : (
+43
View File
@@ -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;
+168
View File
@@ -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
View File
@@ -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 {