import React, { useState } from 'react'; import { ChevronRight, ChevronDown, Pin, X, Hash, User, Check, Send, Filter, Trash2 } from 'lucide-react'; export default function FactumNotesMockup() { const [selectedCase, setSelectedCase] = useState('CASE-2025-001'); const [selectedEvidence, setSelectedEvidence] = useState('HDD-001'); const [activeCase, setActiveCase] = useState('CASE-2025-001'); const [activeEvidence, setActiveEvidence] = useState('HDD-001'); const [expandedCases, setExpandedCases] = useState({ 'CASE-2025-001': true }); const [noteInput, setNoteInput] = useState(''); const [showGoals, setShowGoals] = useState(true); const [showIocs, setShowIocs] = useState(true); const [llmActive, setLlmActive] = useState(false); const [llmInput, setLlmInput] = useState(''); const [llmMessages, setLlmMessages] = useState([ { role: 'assistant', content: 'Ready to assist with analysis. Ask me about timeline correlation, IoC enrichment, or next investigation steps.' } ]); const [filterTag, setFilterTag] = useState(null); // Mock data const cases = { 'CASE-2025-001': { name: 'Financial Fraud Investigation', evidences: ['HDD-001', 'USB-STICK-001', 'MEMORY-DUMP-001'] }, 'CASE-2024-089': { name: 'Corporate Espionage', evidences: ['LAPTOP-001', 'PHONE-BACKUP-001'] } }; const [notes, setNotes] = useState([ { id: 1, caseId: 'CASE-2025-001', evidenceId: 'HDD-001', timestamp: '2025-12-22 14:32:15 UTC', content: 'Initial analysis of disk image reveals potential data exfiltration. Found suspicious PowerShell script at C:\\Users\\john.doe\\AppData\\Local\\Temp\\export.ps1. Script contains Base64-encoded payload attempting to connect to 192.168.45.123:4444 and download file from https://malicious-domain.com/payload.exe. MD5 hash of script: 5d41402abc4b2a76b9719d911017c592', tags: ['initial-analysis', 'powershell', 'network-activity'], signatures: ['Alice Johnson', 'Bob Smith'], iocs: ['192.168.45.123:4444', 'https://malicious-domain.com/payload.exe', '5d41402abc4b2a76b9719d911017c592'], signed: true, pinned: false }, { id: 2, caseId: 'CASE-2025-001', evidenceId: 'HDD-001', timestamp: '2025-12-22 15:45:02 UTC', content: 'Registry analysis shows persistence mechanism established via Run key. Malware creates scheduled task named "SystemUpdate" executing C:\\Windows\\Temp\\svchost.exe every 30 minutes. SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', tags: ['persistence', 'registry', 'scheduled-task'], signatures: ['Alice Johnson'], iocs: ['C:\\Windows\\Temp\\svchost.exe', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'], signed: true, pinned: false }, { id: 3, caseId: 'CASE-2025-001', evidenceId: 'HDD-001', timestamp: '2025-12-22 16:20:38 UTC', content: 'Browser history extraction complete. User visited cryptocurrency exchange wallet at 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa multiple times between Dec 15-20. Timeline correlates with unauthorized transfers.', tags: ['timeline', 'browser-history', 'cryptocurrency'], signatures: ['Alice Johnson'], iocs: ['1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'], signed: true, pinned: true }, { id: 4, caseId: 'CASE-2025-001', evidenceId: 'USB-STICK-001', timestamp: '2025-12-22 17:10:22 UTC', content: 'USB device analysis reveals autorun.inf configured to execute malicious payload. Device contains encrypted archive with password hint referencing internal project codename.', tags: ['usb-analysis', 'autorun', 'encryption'], signatures: ['Bob Smith'], iocs: [], signed: true, pinned: false }, { id: 5, caseId: 'CASE-2024-089', evidenceId: 'LAPTOP-001', timestamp: '2025-12-21 10:15:00 UTC', content: 'Corporate laptop shows evidence of unauthorized remote access tool installation. RustDesk client configured with external server 203.0.113.50:21116', tags: ['remote-access', 'corporate', 'initial-analysis'], signatures: ['Alice Johnson'], iocs: ['203.0.113.50:21116'], signed: true, pinned: false }, { id: 6, caseId: 'CASE-2025-001', evidenceId: null, // Case-level note timestamp: '2025-12-22 13:00:00 UTC', content: 'Case opened: Financial fraud investigation involving potential cryptocurrency theft. Initial evidence suggests internal employee involvement based on access patterns.', tags: ['case-overview', 'fraud', 'cryptocurrency'], signatures: ['Alice Johnson'], iocs: [], signed: true, pinned: false }, { id: 7, caseId: 'CASE-2025-001', evidenceId: null, // Case-level note timestamp: '2025-12-22 18:00:00 UTC', content: 'Daily summary: Evidence from multiple devices shows coordinated exfiltration attempt. Recommend expanding investigation to include network logs and employee workstation forensics.', tags: ['summary', 'coordination'], signatures: ['Alice Johnson'], iocs: [], signed: true, pinned: false } ]); const [showNestedNotes, setShowNestedNotes] = useState(true); const [pinnedNotes, setPinnedNotes] = useState([ { id: 'pin-1', content: 'CRITICAL: Check all PowerShell execution logs between Dec 15-20', timestamp: '2025-12-22 13:00:00 UTC' } ]); const investigativeGoals = [ 'Identify initial infection vector and timeline', 'Map complete network of compromised systems', 'Determine data exfiltration extent and destination', 'Establish attribution indicators' ]; const toggleCase = (caseId) => { setExpandedCases(prev => ({ ...prev, [caseId]: !prev[caseId] })); }; const extractIocs = (text) => { const iocs = []; // IPv4 with port const ipv4Pattern = /\b(?:\d{1,3}\.){3}\d{1,3}(?::\d+)?\b/g; const ipMatches = text.match(ipv4Pattern); if (ipMatches) iocs.push(...ipMatches); // URLs const urlPattern = /https?:\/\/[^\s]+/g; const urlMatches = text.match(urlPattern); if (urlMatches) iocs.push(...urlMatches); // MD5 const md5Pattern = /\b[a-f0-9]{32}\b/gi; const md5Matches = text.match(md5Pattern); if (md5Matches) iocs.push(...md5Matches); // SHA256 const sha256Pattern = /\b[a-f0-9]{64}\b/gi; const sha256Matches = text.match(sha256Pattern); if (sha256Matches) iocs.push(...sha256Matches); // Bitcoin addresses (simplified) const btcPattern = /\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b/g; const btcMatches = text.match(btcPattern); if (btcMatches) iocs.push(...btcMatches); // File paths const pathPattern = /[A-Z]:\\(?:[^\s\\]+\\)*[^\s\\]+\.[a-z]{2,4}/gi; const pathMatches = text.match(pathPattern); if (pathMatches) iocs.push(...pathMatches); return [...new Set(iocs)]; }; const extractTags = (text) => { const tagPattern = /#([a-z0-9-]+)/gi; const matches = text.match(tagPattern); return matches ? [...new Set(matches.map(t => t.substring(1).toLowerCase()))] : []; }; const addNote = () => { if (!noteInput.trim()) return; const now = new Date(); const timestamp = now.toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; const iocs = extractIocs(noteInput); const tags = extractTags(noteInput); const newNote = { id: Math.max(...notes.map(n => n.id), 0) + 1, caseId: selectedCase, evidenceId: selectedEvidence, timestamp, content: noteInput, tags, signatures: ['Alice Johnson'], iocs, signed: true, pinned: false }; setNotes([...notes, newNote]); setNoteInput(''); }; const togglePin = (noteId) => { setNotes(notes.map(note => note.id === noteId ? { ...note, pinned: !note.pinned } : note )); }; const deleteNote = (noteId) => { setNotes(notes.filter(n => n.id !== noteId)); }; const sendLlmMessage = () => { if (!llmInput.trim()) return; const userMsg = { role: 'user', content: llmInput }; setLlmMessages([...llmMessages, userMsg]); // Simulate AI response setTimeout(() => { let response = ''; const input = llmInput.toLowerCase(); if (input.includes('timeline') || input.includes('chronology')) { response = 'Based on the timestamps, the attack sequence appears to be: 1) Initial compromise via PowerShell script (14:32), 2) Persistence establishment through registry modification (15:45), 3) Data exfiltration to cryptocurrency wallet (16:20). Consider examining system logs between these timestamps for additional pivot points.'; } else if (input.includes('ioc') || input.includes('indicator')) { response = 'I\'ve identified 6 unique IoCs across your notes. The IP address 192.168.45.123:4444 appears to be a C2 server. I recommend: 1) Checking if this IP appears in other evidence items, 2) Searching for the malicious domain in DNS logs, 3) Validating the file hashes against VirusTotal.'; } else if (input.includes('next') || input.includes('recommend')) { response = 'Recommended next steps: 1) Analyze the PowerShell execution history for the timeframe Dec 15-20, 2) Extract and decrypt the USB archive if possible, 3) Cross-reference the cryptocurrency wallet address with blockchain explorers for transaction analysis, 4) Check network logs for communication with 192.168.45.123.'; } else { response = 'I can help analyze your notes, correlate timelines, enrich IoCs, or suggest investigation steps. What specific aspect would you like assistance with?'; } const aiMsg = { role: 'assistant', content: response }; setLlmMessages(prev => [...prev, aiMsg]); }, 500); setLlmInput(''); }; const handleKeyDown = (e, callback) => { if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); callback(); } }; // Filter notes based on selection and tag filter const filteredNotes = notes.filter(note => { const matchesCase = note.caseId === selectedCase; const matchesTag = !filterTag || note.tags.includes(filterTag); if (!matchesCase || !matchesTag) return false; // If viewing a specific evidence, only show notes from that evidence if (selectedEvidence) { return note.evidenceId === selectedEvidence; } // If viewing case-level (no evidence selected) // Show case-level notes always if (!note.evidenceId) { return true; } // Show evidence notes only if nested toggle is on return showNestedNotes; }).sort((a, b) => { // Sort chronologically by timestamp return new Date(a.timestamp) - new Date(b.timestamp); }); // Get all unique tags from filtered notes const allTags = [...new Set(filteredNotes.flatMap(note => note.tags))]; // Get extracted IoCs from filtered notes and organize by type const iocsByType = filteredNotes.reduce((acc, note) => { note.iocs.forEach(ioc => { let type = 'Unknown'; if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.test(ioc)) type = 'IPv4'; else if (/^https?:\/\//.test(ioc)) type = 'URL'; else if (/^[a-f0-9]{32}$/i.test(ioc)) type = 'MD5'; else if (/^[a-f0-9]{64}$/i.test(ioc)) type = 'SHA256'; else if (/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(ioc)) type = 'Bitcoin'; else if (/^[A-Z]:\\/.test(ioc)) type = 'File Path'; if (!acc[type]) { acc[type] = []; } if (!acc[type].find(item => item === ioc)) { acc[type].push(ioc); } }); return acc; }, {}); // Get pinned notes from the currently selected case (not filtered by evidence) const displayPinnedNotes = notes.filter(n => n.pinned && n.caseId === selectedCase); // State for IoC tree expansion const [expandedIocTypes, setExpandedIocTypes] = useState({}); const toggleIocType = (type) => { setExpandedIocTypes(prev => ({ ...prev, [type]: !prev[type] })); }; const highlightIocs = (text, iocs) => { if (!iocs || iocs.length === 0) return text; let result = text; const parts = []; let lastIndex = 0; // Sort IOCs by their position in text const positions = []; iocs.forEach(ioc => { const index = result.indexOf(ioc); if (index !== -1) { positions.push({ ioc, index }); } }); positions.sort((a, b) => a.index - b.index); positions.forEach(({ ioc, index }) => { if (index >= lastIndex) { parts.push(result.substring(lastIndex, index)); parts.push({ioc}); lastIndex = index + ioc.length; } }); parts.push(result.substring(lastIndex)); return parts; }; return (
= Active for CLI notes
{note.content}
{note.tags && note.tags.length > 0 && (No IoCs extracted yet
) : ( Object.entries(iocsByType).map(([type, iocs]) => (