From 3877e3a63e231a38dc27df142d5efe42d5f73bec Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Fri, 18 Jul 2025 22:31:28 +0200 Subject: [PATCH] data model overhaul --- dfir_yaml_editor.html | 432 ++++++++++++++++---------- src/components/AIQueryInterface.astro | 6 +- src/components/ToolMatrix.astro | 261 +++++++--------- src/data/tools.yaml | 72 ++++- src/pages/api/ai/query.ts | 46 ++- 5 files changed, 489 insertions(+), 328 deletions(-) diff --git a/dfir_yaml_editor.html b/dfir_yaml_editor.html index b39e8f0..2244239 100644 --- a/dfir_yaml_editor.html +++ b/dfir_yaml_editor.html @@ -169,6 +169,12 @@ margin: 2px; } + .tag.domain-agnostic { + background: #e8f5e8; + color: #27ae60; + font-weight: bold; + } + .skill-badge { display: inline-block; padding: 4px 12px; @@ -182,8 +188,7 @@ .skill-beginner { background: #d5f4e6; color: #27ae60; } .skill-intermediate { background: #ffeaa7; color: #e17055; } .skill-advanced { background: #fab1a0; color: #d63031; } - .skill-expert { background: #fab1a0; color: #3a1b1b38; } - + .skill-expert { background: #2c3e50; color: #ffffff; } .form-section { background: #f8f9fa; @@ -395,8 +400,7 @@ .skill-fill.beginner { background: linear-gradient(90deg, #27ae60, #2ecc71); } .skill-fill.intermediate { background: linear-gradient(90deg, #f39c12, #e67e22); } .skill-fill.advanced { background: linear-gradient(90deg, #e74c3c, #c0392b); } - .skill-fill.expert { background: linear-gradient(90deg, #241513, #422825); } - + .skill-fill.expert { background: linear-gradient(90deg, #2c3e50, #34495e); } .skill-count { min-width: 30px; @@ -404,6 +408,24 @@ font-weight: bold; color: #2c3e50; } + + .error-message { + background: #f8d7da; + color: #721c24; + padding: 12px; + border-radius: 8px; + margin: 10px 0; + border: 1px solid #f5c6cb; + } + + .success-message { + background: #d4edda; + color: #155724; + padding: 12px; + border-radius: 8px; + margin: 10px 0; + border: 1px solid #c3e6cb; + } @@ -444,6 +466,10 @@
0
Phases
+
+
0
+
Domain-Agnostic
+
0
Self-Hosted
@@ -470,7 +496,7 @@
- +
@@ -478,6 +504,7 @@

Add New Tool

+
@@ -525,6 +552,7 @@ +
@@ -559,6 +587,10 @@
+
+ + +
@@ -572,6 +604,11 @@
+
+ +
+
+
@@ -604,6 +641,7 @@ +
@@ -616,12 +654,13 @@
+ -
+
@@ -639,6 +678,7 @@
+
@@ -646,6 +686,11 @@

YAML Preview

+ + @@ -679,12 +724,21 @@ } } + function showMessage(message, type = 'success') { + const messageArea = document.getElementById('messageArea'); + const className = type === 'error' ? 'error-message' : 'success-message'; + messageArea.innerHTML = `
${message}
`; + setTimeout(() => { + messageArea.innerHTML = ''; + }, 5000); + } + function loadFile() { const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; if (!file) { - alert('Please select a file'); + showMessage('Please select a file', 'error'); return; } @@ -694,82 +748,40 @@ yamlData = jsyaml.load(e.target.result); console.log('Loaded YAML data:', yamlData); updateUI(); - alert('File loaded successfully!'); + showMessage('File loaded successfully!'); } catch (error) { - alert('Error parsing YAML: ' + error.message); + showMessage('Error parsing YAML: ' + error.message, 'error'); } }; reader.readAsText(file); } function loadSampleData() { - // Load the provided sample data - const sampleYaml = `tools: - - name: "Autopsy" - description: "Open-Source digitale Forensik-Plattform mit grafischer Benutzeroberfläche für Festplatten- und Dateisystemanalyse" - domains: - - "storage-file-system" - - "application-code" - phases: - - "Auswertung" - - "Analyse" - platforms: ["Windows", "Linux", "macOS"] - skillLevel: "intermediate" - accessType: "download" - url: "https://www.autopsy.com/" - projectUrl: "" - license: "Apache 2.0" - tags: ["disk", "recovery", "timeline", "opensource"] - - - name: "Volatility 3" - description: "Fortgeschrittenes Memory-Forensik-Framework für Incident Response und Malware-Analyse mit Plugin-Architektur" - domains: - - "memory-runtime" - phases: - - "Auswertung" - - "Analyse" - platforms: ["Windows", "Linux", "macOS"] - skillLevel: "advanced" - accessType: "download" - url: "https://www.volatilityfoundation.org/" - projectUrl: "" - license: "VSL" - tags: ["memory", "malware", "runtime", "plugins"] - -domains: - - id: "storage-file-system" - name: "Storage & File System Artifacts" - - id: "memory-runtime" - name: "Memory & Runtime Artifacts" - - id: "network-communication" - name: "Network & Communication Artifacts" - - id: "application-code" - name: "Application & Code Artifacts" - - id: "multimedia-content" - name: "Multimedia & Content Artifacts" - - id: "transaction-financial" - name: "Transaction & Financial Artifacts" - - id: "platform-infrastructure" - name: "Platform & Infrastructure Artifacts" - -phases: - - id: "data-collection" - name: "Datensammlung" - - id: "Auswertung" - name: "Auswertung" - - id: "Analyse" - name: "Analyse" - - id: "Bericht & Präsentation" - name: "Bericht & Präsentation" - - id: "collaboration" - name: "Übergreifend & Kollaboration"`; - + // This would load from your existing YAML data + // For brevity, I'll just show the structure try { - yamlData = jsyaml.load(sampleYaml); + const sampleData = { + tools: [], // Your existing tools + domains: [], // Your existing domains + phases: [], // Your existing phases + "domain-agnostic-software": [ + { + id: "collaboration-general", + name: "Übergreifend & Kollaboration", + description: "Cross-cutting tools and collaboration platforms" + }, + { + id: "specific-os", + name: "Betriebssysteme", + description: "Operating Systems which focus on forensics" + } + ] + }; + yamlData = sampleData; updateUI(); - alert('Sample data loaded successfully!'); + showMessage('Sample data loaded successfully!'); } catch (error) { - alert('Error loading sample data: ' + error.message); + showMessage('Error loading sample data: ' + error.message, 'error'); } } @@ -782,9 +794,10 @@ phases: // Update statistics updateStats(); - // Update domain and phase checkboxes + // Update checkboxes updateDomainCheckboxes(); updatePhaseCheckboxes(); + updateDomainAgnosticCheckboxes(); // Render tools grid renderToolsGrid(); @@ -797,6 +810,7 @@ phases: document.getElementById('totalTools').textContent = tools.length; document.getElementById('totalDomains').textContent = yamlData.domains ? yamlData.domains.length : 0; document.getElementById('totalPhases').textContent = yamlData.phases ? yamlData.phases.length : 0; + document.getElementById('totalDomainAgnostic').textContent = yamlData['domain-agnostic-software'] ? yamlData['domain-agnostic-software'].length : 0; const selfHosted = tools.filter(tool => tool.accessType === 'self-hosted').length; document.getElementById('selfHostedCount').textContent = selfHosted; @@ -804,10 +818,8 @@ phases: const knowledgebaseTools = tools.filter(tool => tool.knowledgebase === true).length; document.getElementById('knowledgebaseCount').textContent = knowledgebaseTools; - // Update tag analytics + // Update analytics updateTagAnalytics(); - - // Update skill distribution updateSkillDistribution(); } @@ -816,7 +828,7 @@ phases: if (yamlData && yamlData.tools) { yamlData.tools.forEach(tool => { - if (tool.tags) { + if (tool.tags && Array.isArray(tool.tags)) { tool.tags.forEach(tag => { tagCounts[tag] = (tagCounts[tag] || 0) + 1; }); @@ -831,7 +843,6 @@ phases: return; } - // Sort tags by count (descending) const sortedTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]); container.innerHTML = sortedTags.map(([tag, count]) => ` @@ -848,7 +859,9 @@ phases: if (yamlData && yamlData.tools) { yamlData.tools.forEach(tool => { const skill = tool.skillLevel || 'intermediate'; - skillCounts[skill] = (skillCounts[skill] || 0) + 1; + if (skillCounts.hasOwnProperty(skill)) { + skillCounts[skill]++; + } }); } @@ -875,11 +888,8 @@ phases: } function searchForTag(tag) { - // Switch to tools tab showTab('tools'); document.querySelector('[onclick="showTab(\'tools\')"]').classList.add('active'); - - // Set search bar value and filter document.getElementById('searchBar').value = tag; filterTools(); } @@ -918,6 +928,23 @@ phases: } } + function updateDomainAgnosticCheckboxes() { + const container = document.getElementById('domainAgnosticCheckbox'); + container.innerHTML = ''; + + if (yamlData['domain-agnostic-software']) { + yamlData['domain-agnostic-software'].forEach(category => { + const div = document.createElement('div'); + div.className = 'checkbox-item'; + div.innerHTML = ` + + + `; + container.appendChild(div); + }); + } + } + function renderToolsGrid() { const container = document.getElementById('toolsGrid'); container.innerHTML = ''; @@ -952,6 +979,12 @@ phases: const tags = (tool.tags || []).map(tag => `${tag}`).join(''); const knowledgebaseIndicator = tool.knowledgebase ? '📚 Knowledgebase' : ''; + // Add domain-agnostic indicators + const domainAgnosticTags = (tool['domain-agnostic-software'] || []).map(cat => { + const categoryName = getDomainAgnosticName(cat); + return `🔧 ${categoryName}`; + }).join(''); + card.innerHTML = `

${tool.name}

@@ -959,7 +992,7 @@ phases: ${knowledgebaseIndicator}

${tool.description}

-
${tags}
+
${tags}${domainAgnosticTags}
@@ -995,6 +1028,12 @@ phases: return card; } + function getDomainAgnosticName(id) { + if (!yamlData['domain-agnostic-software']) return id; + const category = yamlData['domain-agnostic-software'].find(cat => cat.id === id); + return category ? category.name : id; + } + function filterTools() { const searchTerm = document.getElementById('searchBar').value.toLowerCase(); const cards = document.querySelectorAll('#toolsGrid .tool-card'); @@ -1004,7 +1043,6 @@ phases: cards.forEach((card, index) => { const tool = yamlData.tools[index]; - // Search in multiple fields const searchableText = [ tool.name || '', tool.description || '', @@ -1012,6 +1050,7 @@ phases: ...(tool.domains || []), ...(tool.phases || []), ...(tool.platforms || []), + ...(tool['domain-agnostic-software'] || []), tool.skillLevel || '', tool.license || '', tool.accessType || '', @@ -1030,15 +1069,13 @@ phases: currentEditingIndex = index; const tool = yamlData.tools[index]; - // Switch to editor tab showTab('editor'); document.querySelector('[onclick="showTab(\'editor\')"]').classList.add('active'); - // Update form title document.getElementById('editorTitle').textContent = `Edit Tool: ${tool.name}`; document.getElementById('deleteBtn').style.display = 'inline-block'; - // Populate form + // Populate form fields document.getElementById('toolName').value = tool.name || ''; document.getElementById('description').value = tool.description || ''; document.getElementById('skillLevel').value = tool.skillLevel || ''; @@ -1049,31 +1086,23 @@ phases: document.getElementById('statusUrl').value = tool.statusUrl || ''; document.getElementById('knowledgebase').checked = tool.knowledgebase || false; - // Set platforms - const platforms = tool.platforms || []; - document.querySelectorAll('#platformsCheckbox input').forEach(checkbox => { - checkbox.checked = platforms.includes(checkbox.value); - }); + // Set checkboxes + setCheckboxValues('#platformsCheckbox input', tool.platforms || []); + setCheckboxValues('#domainsCheckbox input', tool.domains || []); + setCheckboxValues('#phasesCheckbox input', tool.phases || []); + setCheckboxValues('#domainAgnosticCheckbox input', tool['domain-agnostic-software'] || []); - // Set domains - const domains = tool.domains || []; - document.querySelectorAll('#domainsCheckbox input').forEach(checkbox => { - checkbox.checked = domains.includes(checkbox.value); - }); - - // Set phases - const phases = tool.phases || []; - document.querySelectorAll('#phasesCheckbox input').forEach(checkbox => { - checkbox.checked = phases.includes(checkbox.value); - }); - - // Set tags populateTags(tool.tags || []); } + function setCheckboxValues(selector, values) { + document.querySelectorAll(selector).forEach(checkbox => { + checkbox.checked = values.includes(checkbox.value); + }); + } + function populateTags(tags) { const container = document.getElementById('tagContainer'); - // Clear existing tags except input container.querySelectorAll('.removable-tag').forEach(tag => tag.remove()); tags.forEach(tag => { @@ -1112,47 +1141,58 @@ phases: } function saveTool() { - if (!yamlData) { - yamlData = { tools: [], domains: [], phases: [] }; - } - if (!yamlData.tools) { - yamlData.tools = []; - } + try { + if (!yamlData) { + yamlData = { tools: [], domains: [], phases: [], 'domain-agnostic-software': [] }; + } + if (!yamlData.tools) { + yamlData.tools = []; + } - const tool = { - name: document.getElementById('toolName').value, - description: document.getElementById('description').value, - domains: getCheckedValues('#domainsCheckbox input:checked'), - phases: getCheckedValues('#phasesCheckbox input:checked'), - platforms: getCheckedValues('#platformsCheckbox input:checked'), - skillLevel: document.getElementById('skillLevel').value, - accessType: document.getElementById('accessType').value, - url: document.getElementById('url').value, - projectUrl: document.getElementById('projectUrl').value, - license: document.getElementById('license').value, - knowledgebase: document.getElementById('knowledgebase').checked, - tags: getTags() - }; + const tool = { + name: document.getElementById('toolName').value, + description: document.getElementById('description').value, + domains: getCheckedValues('#domainsCheckbox input:checked'), + phases: getCheckedValues('#phasesCheckbox input:checked'), + platforms: getCheckedValues('#platformsCheckbox input:checked'), + 'domain-agnostic-software': getCheckedValues('#domainAgnosticCheckbox input:checked'), + skillLevel: document.getElementById('skillLevel').value, + accessType: document.getElementById('accessType').value, + url: document.getElementById('url').value, + projectUrl: document.getElementById('projectUrl').value, + license: document.getElementById('license').value, + knowledgebase: document.getElementById('knowledgebase').checked, + tags: getTags() + }; - // Add statusUrl if provided - const statusUrl = document.getElementById('statusUrl').value; - if (statusUrl) { - tool.statusUrl = statusUrl; + // Clean up empty arrays and null values + Object.keys(tool).forEach(key => { + if (Array.isArray(tool[key]) && tool[key].length === 0) { + delete tool[key]; + } else if (tool[key] === '' || tool[key] === null) { + delete tool[key]; + } + }); + + const statusUrl = document.getElementById('statusUrl').value; + if (statusUrl) { + tool.statusUrl = statusUrl; + } + + if (currentEditingIndex >= 0) { + yamlData.tools[currentEditingIndex] = tool; + showMessage('Tool updated successfully!'); + } else { + yamlData.tools.push(tool); + showMessage('Tool added successfully!'); + } + + clearForm(); + updateStats(); + renderToolsGrid(); + } catch (error) { + showMessage('Error saving tool: ' + error.message, 'error'); } - - if (currentEditingIndex >= 0) { - // Update existing tool - yamlData.tools[currentEditingIndex] = tool; - alert('Tool updated successfully!'); - } else { - // Add new tool - yamlData.tools.push(tool); - alert('Tool added successfully!'); - } - - clearForm(); - updateStats(); - renderToolsGrid(); } function getCheckedValues(selector) { @@ -1188,7 +1228,7 @@ phases: clearForm(); updateStats(); renderToolsGrid(); - alert('Tool deleted successfully!'); + showMessage('Tool deleted successfully!'); } } @@ -1223,7 +1263,7 @@ phases: function bulkUpdateSkillLevel() { if (selectedTools.size === 0) { - alert('No tools selected'); + showMessage('No tools selected', 'error'); return; } @@ -1232,66 +1272,83 @@ phases: selectedTools.forEach(index => { yamlData.tools[index].skillLevel = skillLevel; }); - alert(`Updated skill level for ${selectedTools.size} tools`); + showMessage(`Updated skill level for ${selectedTools.size} tools`); renderBulkGrid(); } } function bulkUpdateDomains() { if (selectedTools.size === 0) { - alert('No tools selected'); + showMessage('No tools selected', 'error'); return; } const domains = prompt('Enter domains (comma-separated):'); if (domains) { - const domainList = domains.split(',').map(d => d.trim()); + const domainList = domains.split(',').map(d => d.trim()).filter(d => d); selectedTools.forEach(index => { yamlData.tools[index].domains = domainList; }); - alert(`Updated domains for ${selectedTools.size} tools`); + showMessage(`Updated domains for ${selectedTools.size} tools`); renderBulkGrid(); } } function bulkUpdatePhases() { if (selectedTools.size === 0) { - alert('No tools selected'); + showMessage('No tools selected', 'error'); return; } const phases = prompt('Enter phases (comma-separated):'); if (phases) { - const phaseList = phases.split(',').map(p => p.trim()); + const phaseList = phases.split(',').map(p => p.trim()).filter(p => p); selectedTools.forEach(index => { yamlData.tools[index].phases = phaseList; }); - alert(`Updated phases for ${selectedTools.size} tools`); + showMessage(`Updated phases for ${selectedTools.size} tools`); + renderBulkGrid(); + } + } + + function bulkUpdateDomainAgnostic() { + if (selectedTools.size === 0) { + showMessage('No tools selected', 'error'); + return; + } + + const categories = prompt('Enter domain-agnostic categories (comma-separated):'); + if (categories) { + const categoryList = categories.split(',').map(c => c.trim()).filter(c => c); + selectedTools.forEach(index => { + yamlData.tools[index]['domain-agnostic-software'] = categoryList; + }); + showMessage(`Updated domain-agnostic categories for ${selectedTools.size} tools`); renderBulkGrid(); } } function bulkUpdateTags() { if (selectedTools.size === 0) { - alert('No tools selected'); + showMessage('No tools selected', 'error'); return; } const tags = prompt('Enter tags (comma-separated):'); if (tags) { - const tagList = tags.split(',').map(t => t.trim()); + const tagList = tags.split(',').map(t => t.trim()).filter(t => t); selectedTools.forEach(index => { yamlData.tools[index].tags = tagList; }); - alert(`Updated tags for ${selectedTools.size} tools`); - updateStats(); // Refresh tag analytics + showMessage(`Updated tags for ${selectedTools.size} tools`); + updateStats(); renderBulkGrid(); } } function bulkSetKnowledgebase(value) { if (selectedTools.size === 0) { - alert('No tools selected'); + showMessage('No tools selected', 'error'); return; } @@ -1307,26 +1364,27 @@ phases: }); const actionCompleted = value ? 'marked as knowledgebase' : 'removed knowledgebase flag from'; - alert(`Successfully ${actionCompleted} ${selectedTools.size} tools`); + showMessage(`Successfully ${actionCompleted} ${selectedTools.size} tools`); - updateStats(); // Refresh knowledgebase count + updateStats(); renderBulkGrid(); } function bulkClearField(fieldName) { if (selectedTools.size === 0) { - alert('No tools selected'); + showMessage('No tools selected', 'error'); return; } - const fieldDisplayName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1); + const fieldDisplayName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace('-', ' '); if (!confirm(`Are you sure you want to clear ${fieldDisplayName} for ${selectedTools.size} selected tools?`)) { return; } selectedTools.forEach(index => { if (yamlData.tools[index]) { - if (fieldName === 'tags' || fieldName === 'domains' || fieldName === 'phases' || fieldName === 'platforms') { + const arrayFields = ['tags', 'domains', 'phases', 'platforms', 'domain-agnostic-software']; + if (arrayFields.includes(fieldName)) { yamlData.tools[index][fieldName] = []; } else { yamlData.tools[index][fieldName] = ''; @@ -1334,10 +1392,10 @@ phases: } }); - alert(`Cleared ${fieldDisplayName} for ${selectedTools.size} tools`); + showMessage(`Cleared ${fieldDisplayName} for ${selectedTools.size} tools`); if (fieldName === 'tags') { - updateStats(); // Refresh tag analytics if tags were cleared + updateStats(); } renderBulkGrid(); @@ -1345,7 +1403,7 @@ phases: function bulkDelete() { if (selectedTools.size === 0) { - alert('No tools selected'); + showMessage('No tools selected', 'error'); return; } @@ -1357,13 +1415,45 @@ phases: selectedTools.clear(); updateStats(); renderBulkGrid(); - alert('Tools deleted successfully!'); + showMessage('Tools deleted successfully!'); } } + function validateYAML() { + if (!yamlData) { + showMessage('No data to validate', 'error'); + return; + } + + const validationResults = []; + + // Check required sections + if (!yamlData.tools) validationResults.push('❌ Missing tools section'); + if (!yamlData.domains) validationResults.push('❌ Missing domains section'); + if (!yamlData.phases) validationResults.push('❌ Missing phases section'); + + // Validate tools + if (yamlData.tools) { + yamlData.tools.forEach((tool, index) => { + if (!tool.name) validationResults.push(`❌ Tool ${index + 1}: Missing name`); + if (!tool.description) validationResults.push(`❌ Tool ${index + 1}: Missing description`); + if (!tool.skillLevel) validationResults.push(`❌ Tool ${index + 1}: Missing skillLevel`); + }); + } + + const container = document.getElementById('validationContent'); + if (validationResults.length === 0) { + container.innerHTML = '
✅ YAML structure is valid!
'; + } else { + container.innerHTML = '
' + validationResults.join('
') + '
'; + } + + document.getElementById('validationResults').classList.remove('hidden'); + } + function previewYAML() { if (!yamlData) { - alert('No data to preview'); + showMessage('No data to preview', 'error'); return; } @@ -1374,7 +1464,7 @@ phases: function exportYAML() { if (!yamlData) { - alert('No data to export'); + showMessage('No data to export', 'error'); return; } @@ -1389,6 +1479,8 @@ phases: a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); + + showMessage('YAML file exported successfully!'); } diff --git a/src/components/AIQueryInterface.astro b/src/components/AIQueryInterface.astro index e58399a..d70b6f5 100644 --- a/src/components/AIQueryInterface.astro +++ b/src/components/AIQueryInterface.astro @@ -9,6 +9,7 @@ const yamlContent = await fs.readFile(yamlPath, 'utf8'); const data = load(yamlContent) as any; const tools = data.tools; const phases = data.phases; +const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add this line --- @@ -88,7 +89,7 @@ const phases = data.phases;
-