import React, { forwardRef, memo, useContext, useEffect } from 'react'; import {getTheme} from "../theme.jsx"; import { toast } from "react-toastify" ; import { Divider, List, ListItem, ListItemText, Button, ButtonGroup, Tooltip, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Chip, CircularProgress, Select, MenuItem, } from '@mui/material'; import { FileCopy as FileCopyIcon, OpenInNew as OpenInNewIcon, Refresh as RefreshIcon, Delete as DeleteIcon, Check as CheckIcon, } from "@mui/icons-material" import { green, yellow, red } from '../views/AngularWorkflow.jsx' import { Box, Skeleton, Typography } from '@mui/material'; import { Context } from '../context/ContextApi.jsx'; import RunDetectionTest from '../components/RunDetectionTest.jsx'; const SchedulesTab = memo((props) => { const {globalUrl, users, } = props; const [webHooks, setWebHooks] = React.useState([]); const [allSchedules, setAllSchedules] = React.useState([]); const [pipelines, setPipelines] = React.useState([]); const [showLoader, setShowLoader] = React.useState(true); const [workflows, setWorkflows] = React.useState([]); const [pipelineModalOpen, setPipelineModalOpen] = React.useState(false); const [newPipelineValue, setNewPipelineValue] = React.useState(`export live=true | sigma "/tmp/sigma_rules" | to "SHUFFLE_WEBHOOK"`); const [ticketWebhook, setTicketWebhook] = React.useState(""); const [detectionWorkflowId, setDetectionWorkflowId] = React.useState(""); const [environments, setEnvironments] = React.useState([]); const [selectedEnvironment, setSelectedEnvironment] = React.useState(""); const { themeMode, brandColor } = useContext(Context); const theme = getTheme(themeMode, brandColor); const handleGetWorkflows = () => { const url = `${globalUrl}/api/v1/workflows`; fetch(url, { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }).then((response) => { if (response.status !== 200) { console.log("Status not 200 for getting all workflows"); } return response.json(); }) .then((responseJson) => { if (responseJson.success !== false) { setWorkflows(responseJson || []); for (var i = 0; i < responseJson?.length; i++) { if (responseJson[i].background_processing === true && responseJson[i].name.toLowerCase().includes("ingest tickets") && responseJson[i].triggers !== undefined) { for (var triggerkey in responseJson[i].triggers) { if (responseJson[i].triggers[triggerkey].trigger_type === "WEBHOOK") { setDetectionWorkflowId(responseJson[i].id) setTicketWebhook(`${globalUrl}/api/v1/hooks/webhook_${responseJson[i].triggers[triggerkey].id}`) setNewPipelineValue(`export live=true | sigma /tmp/sigma_rules | to ${globalUrl}/api/v1/hooks/webhook_${responseJson[i].triggers[triggerkey].id}`) break; } } } } } }) .catch((error) => { toast(error.toString()); }) } useEffect(() => { handleGetWorkflows() if (allSchedules.length === 0 && webHooks.length === 0 && pipelines.length === 0) { handleGetAllTriggers() handleGetEnvironments() } }, []) const textColor = "#9E9E9E !important"; const changePipelineState = (pipeline, state) => { if (state.trim() === "") { toast("state is not defined"); return; } const data = { name: pipeline.name, id: pipeline.id, type: state, command: pipeline.definition, environment: pipeline.environment, }; if (state === "start") { toast("starting the pipeline") } else { toast.info("Stopping a pipeline. This may take a few minutes to propagate.") } 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", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); toast("Failed to update the pipeline state"); } return response.json(); }) .then((responseJson) => { if (!responseJson.success) { toast.error("Failed to update the pipeline: " + responseJson.reason); } else { setTimeout(() => { handleGetAllTriggers() }, 5000) setTimeout(() => { handleGetAllTriggers() }, 10000) setTimeout(() => { handleGetAllTriggers() }, 20000) setTimeout(() => { handleGetAllTriggers() }, 120000) /* if (state === "start") { toast("Successfully created pipeline"); } else { toast("Sucessfully stopped the pipeline"); } */ } }) .catch((error) => { //toast(error.toString()); console.log("Get schedule error: ", error.toString()); }) } const submitPipelineWrapper = (pipelineValue, environment) => { submitPipeline(pipelineValue, environment) } const NewPipelineView = ( { setPipelineModalOpen(false) }} PaperProps={{ sx: { borderRadius: theme?.palette?.DialogStyle?.borderRadius, border: theme?.palette?.DialogStyle?.border, minWidth: "800px", minHeight: "320px", fontFamily: theme?.typography?.fontFamily, backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, zIndex: 1000, '& .MuiDialogContent-root': { backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, }, '& .MuiDialogTitle-root': { backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, }, '& .MuiDialogActions-root': { backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, }, }, }} > Run a Tenzir pipeline Alpha feature. Deploys to the first available Orborus location. Explore Tenzir Pipelines. The example below exports everything in the Tenzir database, runs Sigma rules on it, and forwards the results to a Shuffle webhook.
{ setNewPipelineValue(`load_tcp "0.0.0.0:1514" { read_syslog } | import`) }} label={"Syslog Listener (TCP)"} variant="outlined" color="secondary" style={{ marginRight: 10, }} /> { setNewPipelineValue(`load_udp "0.0.0.0:1514", insert_newlines=true | read_syslog | import`) }} label={"Syslog Listener (UDP)"} variant="outlined" color="secondary" style={{ marginRight: 10, }} /> { setNewPipelineValue(`export live=true | sigma "/tmp/sigma_rules" | to "${ticketWebhook !== "" ? ticketWebhook : "SHUFFLE_WEBHOOK"}"`) }} label={"Sigma Rules"} variant="outlined" color="secondary" style={{ marginRight: 10, }} /> { setNewPipelineValue(`export live=true | to_opensearch "localhost:9200", action="create", index="shuffle_logs", user="admin", passwd="PASSWORD"`) }} label={"Opensearch Ingest"} variant="outlined" color="secondary" style={{ marginRight: 10, }} /> setNewPipelineValue(event.target.value) } />
{environments.length === 0 ? null :
Runtime Location
}
) const submitPipeline = (pipeline, environment) => { var pipelineConfig = { command: pipeline, name: pipeline, type: "create", environment: "", workflow_id: "", trigger_id: "", start_node: "", } if (selectedEnvironment !== undefined && selectedEnvironment !== "") { pipelineConfig.environment = selectedEnvironment.Name } if (environment !== undefined && environment !== "") { pipelineConfig.environment = environment } const url = `${globalUrl}/api/v1/triggers/pipeline`; fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(pipelineConfig), 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 && pipelineConfig.type !== "delete") { toast.error("Failed to set pipeline: " + responseJson.reason); } else { if (pipelineConfig.type === "create") { toast.success("Pipeline will be created. Page will autorefresh in a bit: " + responseJson.reason) setPipelineModalOpen(false) } else if (pipelineConfig.type === "stop") { toast.success("Pipeline will be stopped: " + responseJson.reason) setPipelineModalOpen(false) } else { toast.info("Unknown pipeline type: " + pipelineConfig.type) } } setTimeout(() => { handleGetAllTriggers() }, 5000) setTimeout(() => { handleGetAllTriggers() }, 10000) setTimeout(() => { handleGetAllTriggers() }, 15000) setTimeout(() => { handleGetAllTriggers() }, 20000) }) .catch((error) => { console.log("Get pipeline error: ", error.toString()); }); } const deleteSchedule = (data) => { // FIXME - add some check here ROFL console.log("INPUT: ", data); // Just use this one? const url = `${globalUrl}/api/v1/workflows/${data?.workflow_id}/schedule/${data.id}`; fetch(url, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { toast("Failed stopping schedule"); } else { setTimeout(() => { handleGetAllTriggers(); }, 1500); //toast("Successfully stopped schedule!") } }) ) .catch((error) => { console.log("Error in userdata: ", error); }); }; const deleteWebhook = (trigger) => { if (trigger === undefined) { return; } fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", { method: "DELETE", headers: { "Content-Type": "application/json", Accept: "application/json", }, 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("Successfully stopped webhook"); } else { if (responseJson.reason !== undefined) { toast("Failed stopping webhook: " + responseJson.reason); } } setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { toast( "Delete webhook error. Contact support or check logs if this persists.", ); }); }; const handleGetEnvironments = () => { fetch(`${globalUrl}/api/v1/environments`, { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for getting all triggers"); } return response.json(); }) .then((responseJson) => { setEnvironments(responseJson || []); if (responseJson?.length > 0) { var selectedEnv = "" for (var i = 0; i < responseJson?.length; i++) { const env = responseJson[i] if (env.archived) { continue } selectedEnv = env.Name if (env?.data_lake?.enabled === true) { break } } setSelectedEnvironment(selectedEnv.Name) } }) .catch((error) => { // toast(error.toString()); }); } const handleGetAllTriggers = () => { fetch(globalUrl + "/api/v1/triggers", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for getting all triggers"); } return response.json(); }) .then((responseJson) => { setWebHooks(responseJson.webhooks || []); setAllSchedules(responseJson.schedules || []); setPipelines(responseJson.pipelines || []); setShowLoader(false); }) .catch((error) => { // toast(error.toString()); }); }; const startSchedule = (trigger) => { if (trigger.name.length <= 0) { toast("Error: name can't be empty"); return; } toast("Creating schedule"); const data = { name: trigger.name, frequency: trigger.frequency, execution_argument: trigger.argument, environment: trigger.environment, id: trigger.id, start: trigger.start_node, }; fetch(`${globalUrl}/api/v1/workflows/${trigger.workflow_id}/schedule`, { 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 schedule: " + responseJson.reason); } else { toast("Successfully created schedule"); } setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { //toast(error.toString()); console.log("Get schedule error: ", error.toString()); }); } const startWebHook = (trigger) => { const hookname = trigger.info.name; if (hookname.length === 0) { toast("Missing name"); return; } if (trigger.id.length !== 36) { toast("Missing id"); return; } toast("Starting webhook"); const data = { name: hookname, type: "webhook", id: trigger.id, workflow: trigger.workflows[0], start: trigger.start, environment: trigger.environment, auth: trigger.auth, custom_response: trigger.custom_response, version: trigger.version, version_timeout: 15, }; fetch(globalUrl + "/api/v1/hooks/new", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(data), credentials: "include", }) .then((response) => response.json()) .then((responseJson) => { if (responseJson.success) { // Set the status toast("Successfully started webhook"); } else { toast("Failed starting webhook: " + responseJson.reason); } setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { //console.log(error.toString()); console.log("New webhook error: ", error.toString()); }); }; return (
{NewPipelineView}
Triggers Triggers are Automatic Workflow starters. Status: Schedules ({allSchedules.length}), Webhooks ({webHooks.length}), Pipelines ({pipelines.length})
Pipelines Controls Pipelines on your Orborus Runners, e.g. for Log Ingestion, MQ subscriptions or Sigma Detections.{" "} Learn more
{["Status", "Command", "Environment", "Total Runs", "Actions"].map((header, index) => ( ))} {showLoader ? ( [...Array(6)].map((_, rowIndex) => { return ( {Array(5) .fill() .map((_, colIndex) => { return ( ) })} ) } ) ) : ( pipelines?.length === 0 ? (
No pipelines found.
):( pipelines.map((pipeline, index) => { var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF"; if (index % 2 === 0) { bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA"; } return ( { const copyContent = `curl -XPOST http://localhost:5160/api/v0/pipeline/delete -H "Content-Type: application/json" -d '{"id":"${pipeline.id}"}' -v` const copyText = navigator?.clipboard?.writeText(copyContent) if (copyText) { toast.success("Pipeline copied to clipboard") } else { toast.error("Failed to copy pipeline") } }}> { changePipelineState(pipeline, "stop"); }}> )} /> ); }) ) )}
Schedules Schedules used in Workflows. Makes locating and control easier.{" "} Learn more
{["Name", "Interval", "Environment", "Workflow", "Argument", "Action"].map((header, index) => ( ))} {showLoader ? ( [...Array(6)].map((_, rowIndex) => ( {Array(6) .fill() .map((_, colIndex) => ( ))} )) ):( allSchedules?.length === 0 ? (
No schedules found
):( allSchedules.map((schedule, index) => { var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF"; if (index % 2 === 0) { bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA"; } return ( 0 ? ( schedule.frequency ) : ( {schedule.seconds} seconds ) } /> } /> )} /> ); }) ) )}
Webhooks Webhooks used in Shuffle workflows.  Learn more
{["Name", "Environment", "Workflow", "URL", "Action"].map((header, index) => ( ))} {showLoader ? ( [...Array(6)].map((_, rowIndex) => ( {Array(5) .fill() .map((_, colIndex) => ( ))} )) ):( webHooks?.length === 0 ? (
No webhooks found
):( webHooks.map((webhook, index) => { var bgColor = themeMode === "dark" ? "#212121" : "#FFFFFF"; if (index % 2 === 0) { bgColor = themeMode === "dark" ? "#1A1A1A" : "#EAEAEA"; } return ( } /> { const copyText = navigator?.clipboard?.writeText(webhook.info.url); if(copyText){ toast.success("URL copied to clipboard"); }else{ toast.error("Failed to copy URL"); } }} > copy ) } /> )} /> ); }) ) )}
); }); export default SchedulesTab;