diff --git a/ui/src/components/FileComponents/OcrOverviewCard.js b/ui/src/components/FileComponents/OcrOverviewCard.js index 2dd74d9..9336319 100644 --- a/ui/src/components/FileComponents/OcrOverviewCard.js +++ b/ui/src/components/FileComponents/OcrOverviewCard.js @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { Checkbox, Input, Row, Col, Modal } from "antd"; +import { Checkbox, Input, Row, Col, Modal, Button, Tooltip } from "antd"; import "../../styles/OcrOverviewCard.css"; const OcrOverviewCard = ({ data }) => { @@ -7,9 +7,16 @@ const OcrOverviewCard = ({ data }) => { const [wrapText, setWrapText] = useState(false); const [trimText, setTrimText] = useState(true); const [filter, setFilter] = useState(""); + const [isBlurred, setIsBlurred] = useState(data.scan.qr ? true : false); // State to manage blur for QR codes - const showModal = () => setIsModalVisible(true); + const showModal = () => { + // Only show modal if the image is not blurred + if (!isBlurred) { + setIsModalVisible(true); + } + }; const handleCancel = () => setIsModalVisible(false); + const toggleBlur = () => setIsBlurred(!isBlurred); let texts = Array.isArray(data.scan.ocr?.text) ? data.scan.ocr.text @@ -27,6 +34,16 @@ const OcrOverviewCard = ({ data }) => { return
; }; + // Conditional styling for blurred image (QR codes) + const imageStyle = isBlurred + ? { + filter: "blur(4px)", + cursor: "pointer", + } + : { + cursor: "pointer", + }; + // Function to create line numbers and corresponding text const renderTextLines = (texts) => { let lineNumber = 1; // Initialize line number @@ -83,19 +100,27 @@ const OcrOverviewCard = ({ data }) => { - {base64Thumbnail ? ( - <> + {base64Thumbnail ? ( +
Email Preview + {isBlurred && ( +
+ + + +
+ )} { style={{ width: "100%" }} /> - +
) : ( )} diff --git a/ui/src/components/FileComponents/QrOverviewCard.js b/ui/src/components/FileComponents/QrOverviewCard.js new file mode 100644 index 0000000..46171b7 --- /dev/null +++ b/ui/src/components/FileComponents/QrOverviewCard.js @@ -0,0 +1,56 @@ +import React, { useState } from "react"; +import { Input, Table, Typography } from "antd"; +import "../../styles/IocOverviewCard.css"; + +const { Text } = Typography; + +const QrOverviewCard = ({ data }) => { + const [filter, setFilter] = useState(""); + + const columns = [ + { + title: "Data", + dataIndex: "data", + key: "data", + render: (text) => ( + + {text} + + ), + }, + ]; + + const processQrData = () => { + // Assuming data.scan.qr.data is an array of strings (URLs) + return data.scan.qr.data + .filter((url) => { + const searchTerm = filter.toLowerCase(); + return !filter || url.toLowerCase().includes(searchTerm); + }) + .map((data, index) => ({ + key: index, + data: data, + })); + }; + + const filteredQrData = processQrData(); + + return ( +
+ setFilter(e.target.value)} + style={{ width: "100%", marginBottom: 16 }} + /> + + + ); +}; + +export default QrOverviewCard; diff --git a/ui/src/components/FileFlow/NodeTypes/EventNode.js b/ui/src/components/FileFlow/NodeTypes/EventNode.js index bb7f678..c7fd711 100644 --- a/ui/src/components/FileFlow/NodeTypes/EventNode.js +++ b/ui/src/components/FileFlow/NodeTypes/EventNode.js @@ -1,6 +1,6 @@ import { memo } from "react"; import styled, { createGlobalStyle } from "styled-components"; -import { CameraOutlined } from "@ant-design/icons"; +import { CameraOutlined, QrcodeOutlined } from "@ant-design/icons"; import { Tag, Tooltip } from "antd"; import { Handle, Position } from "reactflow"; import { getIconConfig } from "../../../utils/iconMappingTable"; @@ -55,6 +55,21 @@ function getVirusTotalStatus(virusTotalResponse) { } // Styled Components +const QrCodePreviewWrapper = styled.div` + position: absolute; + bottom: 10px; + right: ${({ hasImage }) => (hasImage ? "40px" : "10px")}; + width: 24px; + height: 24px; + border-radius: 20%; + background-color: #ff7a45; + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0px 4px 6px -1px rgba(0, 0, 0, 0.1); + cursor: pointer; +`; + const PulsatingAnimation = createGlobalStyle` @keyframes pulsate { 0% { @@ -97,15 +112,15 @@ const ImageTooltip = styled(Tooltip)` `; const PreviewImage = styled.img` - max-width: 100%; - max-height: 100%; - width: auto; - height: auto; + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; `; const TagWrapper = styled.div` - display: inline-block; - margin-right: 10px; + display: inline-block; + margin-right: 10px; position: absolute; top: -15px; z-index: 10; @@ -235,6 +250,7 @@ const EventNode = memo(({ data, selected }) => { const mappingEntry = getIconConfig("strelka", data.nodeMain.toLowerCase()); const IconComponent = mappingEntry?.icon; const color = mappingEntry?.color || data.color; + const hasImage = Boolean(data.nodeImage); const virusTotalResponse = data.nodeVirustotal; data.nodeAlert = typeof virusTotalResponse === "number" && virusTotalResponse > 5; @@ -302,6 +318,13 @@ const EventNode = memo(({ data, selected }) => { {getVirusTotalStatus(data.nodeVirustotal)} + {data.nodeQrData && ( + + + + + + )} {data.nodeImage && ( { )} + {selectedNodeData && selectedNodeData.scan?.qr?.data && ( + + +
+
+ {" "} + QR Code Data +
+ {selectedNodeData.scan.qr.data.length > 0 + ? "QR Data Count: " + selectedNodeData.scan.qr.data.length + : "No QR Data"} +
+
+
+ + } + key="1" + > + +
+
+ )} {selectedNodeData && selectedNodeData.scan.email && ( {
Indicators of Compromise (IOCs)
- {selectedNodeData.iocs[0].ioc} and{" "} - {selectedNodeData.iocs.length - 1} more + {selectedNodeData.iocs[0].ioc} {" "} + {selectedNodeData.iocs.length > 1 && ` and ${selectedNodeData.iocs.length - 1} more`}
diff --git a/ui/src/utils/indexDataUtils.js b/ui/src/utils/indexDataUtils.js index 64241b3..5533795 100644 --- a/ui/src/utils/indexDataUtils.js +++ b/ui/src/utils/indexDataUtils.js @@ -1,101 +1,112 @@ // Constants for index colors const COLORS = { - STRELKA: "#9c27b0", - DEFAULT: "#ff0072" - }; - - /** - * Returns the color theme based on the given index. - * - * @param index - The index for which the color is to be fetched. - * - * @returns A color string. - */ - export const indexColorThemes = (index) => { - switch (index) { - case "strelka": - return COLORS.STRELKA; - default: - return COLORS.DEFAULT; - } + STRELKA: "#9c27b0", + DEFAULT: "#ff0072", +}; + +/** + * Returns the color theme based on the given index. + * + * @param index - The index for which the color is to be fetched. + * + * @returns A color string. + */ +export const indexColorThemes = (index) => { + switch (index) { + case "strelka": + return COLORS.STRELKA; + default: + return COLORS.DEFAULT; + } +}; + +/** + * Transforms raw data into a structured nodeData object based on the index. + * + * @param index - The type/index of the data. + * @param data - The raw data to be transformed. + * + * @returns A structured nodeData object. + */ +export const indexDataType = (index, data) => { + let nodeData = { + nodeDatatype: "strelka", + nodeVirustotal: "Not Found", + nodeInsights: 0, + nodeIocs: 0, + nodeImage: "", + nodeDisposition: "", + nodeMain: "", + nodeSub: "", + nodeLabel: "", + nodeYaraList: "", + nodeMetric: "", + nodeParentId: "", + nodeRelationshipId: "", }; - - /** - * Transforms raw data into a structured nodeData object based on the index. - * - * @param index - The type/index of the data. - * @param data - The raw data to be transformed. - * - * @returns A structured nodeData object. - */ - export const indexDataType = (index, data) => { - let nodeData = { - nodeDatatype: "strelka", - nodeVirustotal: "Not Found", - nodeInsights: 0, - nodeIocs: 0, - nodeImage: "", - nodeDisposition: "", - nodeMain: "", - nodeSub: "", - nodeLabel: "", - nodeYaraList: "", - nodeMetric: "", - nodeParentId: "", - nodeRelationshipId: "", - }; - switch (index) { - case "strelka": + switch (index) { + case "strelka": - // Extracting the base64_thumbnail from _any_ scanner, if present - let base64Thumbnail = ''; - if (data["scan"]) { - for (let key in data["scan"]) { - if (data["scan"][key]["base64_thumbnail"]) { - base64Thumbnail = data["scan"][key]["base64_thumbnail"]; - break; - } + // Check if QR data exists and add to nodeData + let qrData = ""; + if (data.scan?.qr?.data) { + qrData = data.scan.qr.data; + } + + // Extracting the base64_thumbnail from _any_ scanner, if present + let base64Thumbnail = ""; + if (data["scan"]) { + for (let key in data["scan"]) { + if (data["scan"][key]["base64_thumbnail"]) { + base64Thumbnail = data["scan"][key]["base64_thumbnail"]; + break; } } + } - Object.assign(nodeData, { - nodeDepth: data["file"]["depth"], - nodeMain: data["file"]["flavors"]["yara"]?.[0] || data["file"]["flavors"]["mime"]?.[0], - nodeSub: `${data["file"]["size"]} Bytes`, - nodeLabel: data["file"]["name"] || "No Filename", - nodeYaraList: data["scan"]?.["yara"]?.["matches"] || "", - nodeMetric: data["scan"]?.["yara"]?.["matches"]?.length || 0, - nodeMetricLabel: "Yara Matches", - nodeParentId: data["file"]["tree"]["parent"], - nodeRelationshipId: data["file"]["tree"]["node"], - nodeVirustotal: data["enrichment"]?.["virustotal"] !== undefined ? data["enrichment"]["virustotal"] : "Not Found", - nodeInsights: data?.insights?.length, - nodeIocs: data?.iocs?.length, - nodeImage: base64Thumbnail, - } - ); - break; - - default: - break; - } + Object.assign(nodeData, { + nodeDepth: data["file"]["depth"], + nodeMain: + data["file"]["flavors"]["yara"]?.[0] || + data["file"]["flavors"]["mime"]?.[0], + nodeSub: `${data["file"]["size"]} Bytes`, + nodeLabel: data["file"]["name"] || "No Filename", + nodeYaraList: data["scan"]?.["yara"]?.["matches"] || "", + nodeMetric: data["scan"]?.["yara"]?.["matches"]?.length || 0, + nodeMetricLabel: "Yara Matches", + nodeParentId: data["file"]["tree"]["parent"], + nodeRelationshipId: data["file"]["tree"]["node"], + nodeVirustotal: + data["enrichment"]?.["virustotal"] !== undefined + ? data["enrichment"]["virustotal"] + : "Not Found", + nodeInsights: data?.insights?.length, + nodeIocs: data?.iocs?.length, + nodeImage: base64Thumbnail, + nodeQrData: qrData + }); + break; - return nodeData; - }; - - /** - * Transforms raw data into a structured nodeData object based on the index. - * - * @param index - The type/index of the data. - * @param data - The raw data to be transformed. - * - * @returns A structured nodeData object. - */ - export const indexNodeType = (index) => { - switch (index) { - case "strelka": - return "event"; - default: - return "event"; - } - }; \ No newline at end of file + default: + break; + } + + return nodeData; +}; + +/** + * Transforms raw data into a structured nodeData object based on the index. + * + * @param index - The type/index of the data. + * @param data - The raw data to be transformed. + * + * @returns A structured nodeData object. + */ +export const indexNodeType = (index) => { + switch (index) { + case "strelka": + return "event"; + default: + return "event"; + } +}; diff --git a/ui/src/utils/layoutUtils.js b/ui/src/utils/layoutUtils.js index 0520994..de1e451 100644 --- a/ui/src/utils/layoutUtils.js +++ b/ui/src/utils/layoutUtils.js @@ -104,6 +104,7 @@ export function transformElasticSearchDataToElements(results) { nodeInsights: nodeData.nodeInsights, nodeIocs: nodeData.nodeIocs, nodeImage: nodeData.nodeImage, + nodeQrData: nodeData.nodeQrData, nodeMain: nodeData.nodeMain, nodeSub: nodeData.nodeSub, nodeLabel: nodeData.nodeLabel,