Files
shuffle-cracked/frontend/src/components/CollectIngestModal.jsx
T
2025-10-20 11:37:26 +02:00

454 lines
12 KiB
React

import React, { useState, useEffect, useContext, memo } from "react";
import { Context } from "../context/ContextApi.jsx";
import { getTheme } from "../theme.jsx";
import { GetIconInfo } from "../views/Workflows2.jsx";
import { toast } from 'react-toastify';
import {
Dialog,
DialogTitle,
DialogContent,
Typography,
IconButton,
Paper,
LinearProgress,
Grid,
Button,
Tooltip,
Autocomplete,
TextField,
Box,
} from '@mui/material';
import {
Rocket as RocketIcon,
FilterAlt as FilterAltIcon,
Add as AddIcon,
Check as CheckIcon,
} from '@mui/icons-material';
import {
green,
red,
} from '../views/AngularWorkflow.jsx'
import algoliasearch from 'algoliasearch/lite';
const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e")
const CollectIngestModal = (props) => {
const { globalUrl, open, setOpen, workflows, getWorkflows, apps, } = props;
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
if (open === undefined || open === null) {
console.error("CollectIngestModal: 'open' prop is required.");
return null
}
if (setOpen === undefined || setOpen === null) {
console.error("CollectIngestModal: 'setOpen' prop is required.");
return null
}
const startIngestion = (bundleName, appnames, category, index) => {
var body = {
"label": bundleName,
"app_name": appnames,
"category": "",
}
if (category !== undefined && category !== null) {
body.category = category
}
const url = `${globalUrl}/api/v2/workflows/generate`
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
credentials: "include",
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
if (getWorkflows !== undefined) {
getWorkflows()
}
console.log("Ingestion started successfully:", data);
toast.success(`Ingestion for ${bundleName} started successfully!`);
})
.catch((error) => {
console.error("Error starting ingestion:", error);
toast.error(`Failed to start ingestion for ${bundleName}. Please try again or contact support@shuffler.io if this persists.`);
});
}
const IngestItem = (props) => {
const { type, appCategory, index, webhook } = props
const [hovering, setHovering] = useState(false);
const [selectedApps, setSelectedApps] = useState([]);
const [showAppsearch, setShowAppsearch] = useState(false);
const [algoliaOptions, setAlgoliaOptions] = useState([]);
const [generating, setGenerating] = useState(false);
const appname = type
const ingestedAmount = 20
const iconDetails = GetIconInfo({
"app_name": appname,
"name": appname,
})
var foundMatchingWorkflow = null
if (!showAppsearch && workflows !== undefined && workflows !== null && workflows.length > 0) {
const parsedName = type.toLowerCase().replaceAll(" ", "_");
const foundWorkflow = workflows.find((workflow) => {
return workflow?.name?.toLowerCase().replaceAll(" ", "_") === parsedName
})
if (foundWorkflow !== undefined && foundWorkflow !== null) {
foundMatchingWorkflow = foundWorkflow
// Find relevant apps and maps them
if (apps.length > 0 && selectedApps.length === 0 && foundWorkflow?.actions !== undefined && foundWorkflow?.actions !== null && foundWorkflow?.actions.length > 0) {
var newSelectedApps = []
for (var actionkey in foundWorkflow.actions) {
const action = foundWorkflow.actions[actionkey]
if (action?.app_name !== "Singul" && action?.app_id !== "integration") {
continue
}
for (var paramkey in action.parameters) {
const param = action.parameters[paramkey]
if (!((param.name === "app_name" || param.name === "appName") && param.value !== undefined && param.value !== null && param.value.length > 0)) {
continue
}
// Find the app in available apps
const parsedname = param.value.replaceAll(" ", "_").toLowerCase()
for (var appkey in apps) {
const appname = apps[appkey].name.replaceAll(" ", "_").toLowerCase()
if (appname === parsedname) {
newSelectedApps.push(apps[appkey])
break
}
}
break
}
}
if (newSelectedApps.length > 0) {
setSelectedApps(newSelectedApps)
}
}
}
}
var matchingapps = []
if (appCategory !== undefined && appCategory !== null && appCategory.length > 0 && apps !== undefined && apps !== null && apps.length > 0) {
for (var appkey in apps) {
const app = apps[appkey]
for (var categorykey in app.categories) {
const category = app.categories[categorykey]
if (category.toLowerCase() === appCategory.toLowerCase()) {
matchingapps.push(app)
}
}
}
}
const runIngestion = () => {
setGenerating(true)
setTimeout(() => {
setGenerating(false)
}, 5000)
toast.info("Starting ingest for relevant apps")
var newapps = ""
for (var key in selectedApps) {
const app = selectedApps[key]
if (newapps.length > 0) {
newapps += ","
}
newapps += app.name
}
startIngestion(appname, newapps, appCategory, index)
if (webhook === true) {
startIngestion(appname+"_webhook", newapps, appCategory, index)
}
}
return (
//<Grid item xs={hovering ? 12 : 5.9}
<Grid item xs={12}
style={{
minHeight: hovering ? 200 : 200,
cursor: "pointer",
position: "relative",
transition: "all 0.3s ease-in-out",
borderRadius: theme.palette.borderRadius,
border: hovering ? `2px solid ${theme.palette.primary.main}` : foundMatchingWorkflow !== null ? `2px solid ${theme.palette.success.main}` : `2px solid ${theme.palette.secondary.main}`,
textAlign: "center",
marginBottom: 10,
overflow: "hidden",
}}
onMouseEnter={() => {
//if (foundMatchingWorkflow !== null) {
//} else {
setHovering(true)
//}
}}
onMouseLeave={() => setHovering(false)}
>
<div style={{display: "flex", }}>
<div style={{flex: 1, margin: "auto", marginTop: 50, }}>
<div style={{width: 50+selectedApps?.length*50, margin: "auto", itemAlign: "center", textAlign: "center", display: "flex", }}>
{generating ? null : selectedApps.map((app, index) => {
// Show image of each one
return (
<div key={index} style={{display: "flex", alignItems: "center", marginLeft: 10, }}>
<Tooltip title={app.name} placement="top">
<img
style={{height: 40, width: 40, borderRadius: 50}}
src={app?.large_image || app?.icon || app?.image || "/static/images/default_app_icon.png"}
/>
</Tooltip>
</div>
)
})}
{!generating && appCategory !== undefined && appCategory !== null && appCategory.length > 0 ?
<Tooltip title={showAppsearch ? "Done selecting apps" : "Select Apps"} placement="top">
<IconButton
style={{marginLeft: 10, marginRight: 50, }}
variant="outlined"
color="secondary"
onClick={() => {
if (showAppsearch === true) {
runIngestion()
}
setShowAppsearch(!showAppsearch)
}}
>
{showAppsearch ?
<CheckIcon style={{color: green, }} />
:
<AddIcon style={{color: theme.palette.primary.main, }} />
}
</IconButton>
</Tooltip>
: null}
</div>
{showAppsearch ?
<Autocomplete
style={{flex: 1, maxWidth: 200, minWidth: 200, margin: "auto", marginTop: 10, }}
multiple
filterSelectedOptions
options={matchingapps}
value={selectedApps}
onChange={(event, value) => {
console.log("New value: ", value)
setSelectedApps(value)
}}
getOptionLabel={(option) => {
const parsedname = option.name.replaceAll("_", " ")
return parsedname
//return (
// <div>
// <img src={option?.large_image} alt={option.name} style={{ width: 24, height: 24, marginRight: 10, borderRadius: 5, }} />
// <Typography variant="body1" style={{ display: "inline-block", verticalAlign: "middle", marginTop: -12, }}>
// {parsedname}
// </Typography>
// </div>
//)
}}
renderOption={(props, option, state, ownerState) => {
const { key, ...optionProps } = props;
return (
<Box
key={key}
sx={{
borderRadius: '8px',
margin: '5px',
padding: '8px',
}}
component="li"
{...optionProps}
>
<img src={option?.large_image} alt={option.name} style={{ width: 24, height: 24, marginRight: 10, borderRadius: 5, }} />
{ownerState.getOptionLabel(option)}
</Box>
);
}}
renderInput={(params) => {
return (
<TextField
{...params}
variant="outlined"
label="Select apps"
/>
)
}}
/>
:
<Button
style={{width: 250, margin: 25, }}
variant={foundMatchingWorkflow !== null ? "outlined" : "contained"}
onClick={() => {
runIngestion()
}}
disabled={generating}
>
{foundMatchingWorkflow !== null ?
"Re-Create Ingestion"
:
"Start Ingestion"
}
</Button>
}
</div>
<div style={{flex: 1, marginTop: 50, }}>
{iconDetails?.originalIcon && (
iconDetails?.originalIcon
)}
<Typography variant="h4" style={{marginTop: 10, }}>
{appname}
</Typography>
</div>
</div>
{foundMatchingWorkflow !== null ?
<a href={`/workflows/${foundMatchingWorkflow.id}`} target="_blank" rel="noopener noreferrer">
<Tooltip title="View Workflow" placement="right">
<IconButton style={{position: "absolute", top: 10, right: 10, marginLeft: 15, }}>
<RocketIcon style={{ }} />
</IconButton>
</Tooltip>
</a>
: null}
{hovering ?
<div>
</div>
: null}
{foundMatchingWorkflow !== null ?
<div>
<Typography variant="body1" style={{
position: "absolute",
bottom: 10,
left: 10,
color: theme.palette.text.secondary,
}}>
{ingestedAmount} / X
</Typography>
{/*
<LinearProgress
style={{
width: "100%",
position: "absolute",
bottom: 0,
}}
variant="determinate"
fullWidth value={{ingestedAmount}}
/>
*/}
</div>
: null}
</Grid>
)
}
return (
<Dialog
PaperProps={{
sx: {
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
border: theme?.palette?.DialogStyle?.border,
minWidth: 850,
minHeight: 700,
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,
},
},
}}
open={open}
onClose={() => {
setOpen(false)
}}
>
<DialogTitle>
</DialogTitle>
<DialogContent style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: theme.palette.DialogStyle.backgroundColor, }}>
<Typography variant="h6" style={{ color: theme.palette.text.primary, marginBottom: 20 }}>
<FilterAltIcon style={{ verticalAlign: "middle", marginRight: 10 }} />
Collection and Ingestion
</Typography>
<Grid container>
<IngestItem type="Ingest Tickets" appCategory={"cases"} webhook={true} index={1} />
<IngestItem type="Enable Threat feeds" index={2} />
<IngestItem type="Ingest Assets" appCategory={"assets"} index={2} />
<IngestItem type="Ingest Users " appCategory={"users"} index={2} />
<IngestItem type="Enable Search" index={2} />
<IngestItem type="Enable Mitre Att&ck techniques" index={2} />
<IngestItem type="Enable Detection Rules" index={2} />
<IngestItem type="Ingest Logs" index={2} />
</Grid>
</DialogContent>
</Dialog>
)
}
export default CollectIngestModal