diff --git a/.env b/.env index 61885b3b..64e325bc 100755 --- a/.env +++ b/.env @@ -101,7 +101,7 @@ SHUFFLE_OPENSEARCH_INDEX_PREFIX= SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true #Tenzir related -SHUFFLE_TENZIR_URL=http://localhost:5160 +SHUFFLE_TENZIR_URL= DEBUG_MODE=false diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 0547b7b6..54f0874c 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1975,6 +1975,144 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } +func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { + + if request.Method != "POST" { + request.Method = "POST" + } + + if request.Body == nil { + stringReader := strings.NewReader("") + request.Body = ioutil.NopCloser(stringReader) + } + + path := strings.Split(request.URL.String(), "/") + if len(path) < 4 { + resp.WriteHeader(403) + resp.Write([]byte(`{"success": false}`)) + return + } + + ctx := context.Background() + location := strings.Split(request.URL.String(), "/") + + var pipelineId string + + if location[1] == "api" { + if len(location) <= 4 { + log.Printf("[INFO] Couldn't handle location. Too short in pipeline: %d", len(location)) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + pipelineId = location[4] + } + + userAgent := request.Header.Get("User-Agent") + 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) + resp.WriteHeader(400) + resp.Write([]byte(`{"success": false, "reason": "Google/Microsoft preview bots not allowed. Please change the useragent."}`)) + return + } + + if len(pipelineId) != 45 { + log.Printf("[INFO] Couldn't handle pipeline. Too short in pipeline: %d", len(pipelineId)) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "pipeline ID not valid"}`)) + return + } + + pipelineId = pipelineId[9:] + + pipeline, err := shuffle.GetPipeline(ctx, pipelineId) + if err != nil { + log.Printf("[WARNING] Failed getting pipeline %s (callback): %s", pipelineId, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if pipeline.Status != "running" { + log.Printf("[WARNING] Not running %s because pipeline status is %s", pipeline.TriggerId, pipeline.Status) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The pipeline isn't running"}`))) + return + } + + if pipeline.WorkflowId == "" { + log.Printf("[DEBUG] Not running because pipeline isn't connected to any workflows") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("[DEBUG] Body data error: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + parsedBody := shuffle.GetExecutionbody(body) + newBody := shuffle.ExecutionStruct{ + Start: pipeline.StartNode, + ExecutionSource: "pipeline", + ExecutionArgument: parsedBody, + } + + workflow, err := shuffle.GetWorkflow(ctx, pipeline.WorkflowId) + if err == nil { + for _, branch := range workflow.Branches { + if branch.SourceID == pipeline.TriggerId { + log.Printf("[DEBUG] Found ID %s for pipeline", pipeline.TriggerId) + if branch.DestinationID != pipeline.StartNode { + newBody.Start = branch.DestinationID + break + } + } + } + } + + b, err := json.Marshal(newBody) + if err != nil { + log.Printf("[ERROR] Failed newBody marshaling for pipeline: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("[INFO] Running pipeline for workflow %s with startnode %s", pipeline.WorkflowId, pipeline.StartNode) + + newWorkflow := shuffle.Workflow{ + ID: "", + } + + if len(pipeline.StartNode) == 0 { + log.Printf("[WARNING] No start node for pipeline %s - running with workflow default.", pipeline.TriggerId) + + } + + newRequest := &http.Request{ + URL: &url.URL{}, + Method: "POST", + Body: ioutil.NopCloser(bytes.NewReader(b)), + } + + workflowExecution, executionResp, err := handleExecution(pipeline.WorkflowId, newWorkflow, newRequest, pipeline.OrgId) + + if err == nil { + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId))) + return + } + + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp))) +} + func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error { data, err := json.Marshal(action) if err != nil { @@ -4911,7 +5049,8 @@ func initHandlers() { r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS") + //r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS") r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS") //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 10d76509..de8c0bae 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -68,7 +68,8 @@ import { ListItemAvatar, Badge, AvatarGroup, - Autocomplete, + Autocomplete, + Radio, } from "@mui/material"; import { @@ -220,7 +221,6 @@ export const triggers = [ id: "", }, ]; - // Adds specific text to items // https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react @@ -521,6 +521,10 @@ const AngularWorkflow = (defaultprops) => { const [highlightedApp, setHighlightedApp] = React.useState("") const [listCache, setListCache] = React.useState([]); + + const [selectedOption, setSelectedOption] = React.useState(""); + const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false); + const [suggestionBox, setSuggestionBox] = React.useState({ "position": { "top": 500, @@ -1342,6 +1346,56 @@ const AngularWorkflow = (defaultprops) => { }); }; + const handleKafkaSubmit = (trigger) => { + if (trigger.trigger_type !== "PIPELINE") { + toast("Unable to save the configuration"); + return; + } + + 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) { + trigger.parameters.push({ + name: "topic", + value: topic + }); + } else { + toast("please enter the topic name"); + return; + } + + if (bootstrapServers) { + trigger.parameters.push({ + name: "bootstrap_servers", + value: bootstrapServers + }); + } else { + toast("please enter bootstrap server details"); + return; + } + + if (groupId) { + trigger.parameters.push({ + name: "group_id", + value: groupId + }); + } + + if (autoOffsetReset) { + trigger.parameters.push({ + name: "auto_offset_reset", + value: autoOffsetReset + }); + } + + setTenzirConfigModalOpen(false); + }; + const handleColoring = (actionId, status, label) => { if (cy === undefined) { return @@ -6963,6 +7017,20 @@ const AngularWorkflow = (defaultprops) => { } else if (selectedNode.data().trigger_type === "EMAIL") { setSelectedTrigger(selectedNode.data()); stopMailSub(selectedTrigger, triggerindex); + } else if (selectedNode.data().trigger_type === "PIPELINE") { + setSelectedTrigger(selectedNode.data()); + + const pipelineConfig = { + command: "", + name: selectedNode.data().label, + type: "delete", + environment: selectedNode.data().environment, + workflow_id: workflow.id, + trigger_id: selectedNode.data().id, + start_node: "", + }; + + submitPipeline(selectedNode.data(), triggerindex, pipelineConfig); } } @@ -7348,47 +7416,67 @@ const AngularWorkflow = (defaultprops) => { toast("Error: name can't be empty"); return; } - - var mappedStartnode = "" - const alledges = cy.edges().jsons() + + var mappedStartnode = ""; + const alledges = cy.edges().jsons(); if (alledges !== undefined && alledges !== null && alledges.length > 0) { - for (let edgekey in alledges) { - const tmp = alledges[edgekey] - console.log("TMP: ", tmp, tmp.data.source) - if (tmp.data.source === trigger.id) { - mappedStartnode = tmp.data.target - break - } - } + for (let edgekey in alledges) { + const tmp = alledges[edgekey]; + console.log("TMP: ", tmp, tmp.data.source); + if (tmp.data.source === trigger.id) { + mappedStartnode = tmp.data.target; + break; + } + } + } + const data = usecase; + data.start_node = mappedStartnode + + if (data.type === "create") { + toast("Creating pipeline"); + } else if (data.type === "stop") { + toast("stopping pipeline"); } - toast("Creating pipeline") - const data = usecase - const url = `${globalUrl}/api/v1/triggers/pipeline` + const url = `${globalUrl}/api/v1/triggers/pipeline`; fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(data), - credentials: "include", - } - ) + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); - } - + } + return response.json(); }) .then((responseJson) => { if (!responseJson.success) { toast("Failed to set pipeline: " + responseJson.reason); } else { - toast("Successfully created pipeline"); - workflow.triggers[triggerindex].status = "running"; - trigger.status = "running"; + if (data.type === "create") { + toast("Pipeline will be created!"); + } else if (data.type === "stop") { + toast("Pipeline will be stopped!"); + } else { + toast("Pipeline deleted!") + return + } + + trigger.parameters.push({ + name: data.name, + value: data.command, + }); + + if (data.type === "stop") trigger.status = "stopped"; + else trigger.status = "running"; + workflow.triggers[triggerindex] = trigger; + setSelectedTrigger(trigger); setWorkflow(workflow); console.log("Should set the status to running and save"); @@ -7396,11 +7484,10 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - //toast(error.toString()); - console.log("Get schedule error: ", error.toString()); + console.log("Get pipeline error: ", error.toString()); }); - } - + }; + const submitSchedule = (trigger, triggerindex) => { if (trigger.name.length <= 0) { toast("Error: name can't be empty"); @@ -13825,225 +13912,270 @@ const AngularWorkflow = (defaultprops) => { } const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null : !userdata.support === true ? null : -