data model overhaul

This commit is contained in:
overcuriousity 2025-07-18 22:31:28 +02:00
parent 69819eba7d
commit 3877e3a63e
5 changed files with 489 additions and 328 deletions

View File

@ -169,6 +169,12 @@
margin: 2px; margin: 2px;
} }
.tag.domain-agnostic {
background: #e8f5e8;
color: #27ae60;
font-weight: bold;
}
.skill-badge { .skill-badge {
display: inline-block; display: inline-block;
padding: 4px 12px; padding: 4px 12px;
@ -182,8 +188,7 @@
.skill-beginner { background: #d5f4e6; color: #27ae60; } .skill-beginner { background: #d5f4e6; color: #27ae60; }
.skill-intermediate { background: #ffeaa7; color: #e17055; } .skill-intermediate { background: #ffeaa7; color: #e17055; }
.skill-advanced { background: #fab1a0; color: #d63031; } .skill-advanced { background: #fab1a0; color: #d63031; }
.skill-expert { background: #fab1a0; color: #3a1b1b38; } .skill-expert { background: #2c3e50; color: #ffffff; }
.form-section { .form-section {
background: #f8f9fa; background: #f8f9fa;
@ -395,8 +400,7 @@
.skill-fill.beginner { background: linear-gradient(90deg, #27ae60, #2ecc71); } .skill-fill.beginner { background: linear-gradient(90deg, #27ae60, #2ecc71); }
.skill-fill.intermediate { background: linear-gradient(90deg, #f39c12, #e67e22); } .skill-fill.intermediate { background: linear-gradient(90deg, #f39c12, #e67e22); }
.skill-fill.advanced { background: linear-gradient(90deg, #e74c3c, #c0392b); } .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 { .skill-count {
min-width: 30px; min-width: 30px;
@ -404,6 +408,24 @@
font-weight: bold; font-weight: bold;
color: #2c3e50; 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;
}
</style> </style>
</head> </head>
<body> <body>
@ -444,6 +466,10 @@
<div class="stat-number" id="totalPhases">0</div> <div class="stat-number" id="totalPhases">0</div>
<div class="stat-label">Phases</div> <div class="stat-label">Phases</div>
</div> </div>
<div class="stat-card">
<div class="stat-number" id="totalDomainAgnostic">0</div>
<div class="stat-label">Domain-Agnostic</div>
</div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="selfHostedCount">0</div> <div class="stat-number" id="selfHostedCount">0</div>
<div class="stat-label">Self-Hosted</div> <div class="stat-label">Self-Hosted</div>
@ -470,7 +496,7 @@
<!-- Tools Tab --> <!-- Tools Tab -->
<div id="tools" class="tab-content"> <div id="tools" class="tab-content">
<input type="text" class="search-bar" id="searchBar" placeholder="🔍 Search tools by name, description, tags, domains, or 'knowledgebase'..." onkeyup="filterTools()" /> <input type="text" class="search-bar" id="searchBar" placeholder="🔍 Search tools by name, description, tags, domains, phases, or domain-agnostic categories..." onkeyup="filterTools()" />
<div class="tools-grid" id="toolsGrid"></div> <div class="tools-grid" id="toolsGrid"></div>
</div> </div>
@ -478,6 +504,7 @@
<div id="editor" class="tab-content"> <div id="editor" class="tab-content">
<div class="form-section"> <div class="form-section">
<h3 id="editorTitle">Add New Tool</h3> <h3 id="editorTitle">Add New Tool</h3>
<div id="messageArea"></div>
<form id="toolForm"> <form id="toolForm">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="form-group"> <div class="form-group">
@ -525,6 +552,7 @@
<option value="download">Download</option> <option value="download">Download</option>
<option value="self-hosted">Self-Hosted</option> <option value="self-hosted">Self-Hosted</option>
<option value="commercial">Commercial</option> <option value="commercial">Commercial</option>
<option value="OS">Operating System</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -559,6 +587,10 @@
<input type="checkbox" id="platform-web" value="Web"> <input type="checkbox" id="platform-web" value="Web">
<label for="platform-web">Web</label> <label for="platform-web">Web</label>
</div> </div>
<div class="checkbox-item">
<input type="checkbox" id="platform-os" value="OS">
<label for="platform-os">Operating System</label>
</div>
</div> </div>
</div> </div>
@ -572,6 +604,11 @@
<div class="checkbox-group" id="phasesCheckbox"></div> <div class="checkbox-group" id="phasesCheckbox"></div>
</div> </div>
<div class="form-group">
<label>Domain-Agnostic Software Categories</label>
<div class="checkbox-group" id="domainAgnosticCheckbox"></div>
</div>
<div class="form-group"> <div class="form-group">
<label for="tagsInput">Tags</label> <label for="tagsInput">Tags</label>
<div class="tag-input-container" id="tagContainer"> <div class="tag-input-container" id="tagContainer">
@ -604,6 +641,7 @@
<button class="btn btn-warning" onclick="bulkUpdateSkillLevel()">Update Skill Level</button> <button class="btn btn-warning" onclick="bulkUpdateSkillLevel()">Update Skill Level</button>
<button class="btn btn-warning" onclick="bulkUpdateDomains()">Update Domains</button> <button class="btn btn-warning" onclick="bulkUpdateDomains()">Update Domains</button>
<button class="btn btn-warning" onclick="bulkUpdatePhases()">Update Phases</button> <button class="btn btn-warning" onclick="bulkUpdatePhases()">Update Phases</button>
<button class="btn btn-warning" onclick="bulkUpdateDomainAgnostic()">Update Domain-Agnostic</button>
<button class="btn btn-warning" onclick="bulkUpdateTags()">Update Tags</button> <button class="btn btn-warning" onclick="bulkUpdateTags()">Update Tags</button>
</div> </div>
@ -616,12 +654,13 @@
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;">
<button class="btn" onclick="bulkClearField('phases')">🗑️ Clear All Phases</button> <button class="btn" onclick="bulkClearField('phases')">🗑️ Clear All Phases</button>
<button class="btn" onclick="bulkClearField('domain-agnostic-software')">🗑️ Clear Domain-Agnostic</button>
<button class="btn" onclick="bulkClearField('platforms')">🗑️ Clear All Platforms</button> <button class="btn" onclick="bulkClearField('platforms')">🗑️ Clear All Platforms</button>
<button class="btn" onclick="bulkClearField('url')">🗑️ Clear All URLs</button> <button class="btn" onclick="bulkClearField('url')">🗑️ Clear All URLs</button>
<button class="btn" onclick="bulkClearField('projectUrl')">🗑️ Clear Project URLs</button>
</div> </div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;">
<button class="btn" onclick="bulkClearField('projectUrl')">🗑️ Clear Project URLs</button>
<button class="btn" onclick="bulkClearField('statusUrl')">🗑️ Clear Status URLs</button> <button class="btn" onclick="bulkClearField('statusUrl')">🗑️ Clear Status URLs</button>
<button class="btn btn-danger" onclick="bulkDelete()">🗑️ Delete Selected</button> <button class="btn btn-danger" onclick="bulkDelete()">🗑️ Delete Selected</button>
</div> </div>
@ -639,6 +678,7 @@
<div style="margin: 20px 0;"> <div style="margin: 20px 0;">
<button class="btn btn-success" onclick="exportYAML()">📥 Download YAML</button> <button class="btn btn-success" onclick="exportYAML()">📥 Download YAML</button>
<button class="btn" onclick="previewYAML()">👁️ Preview YAML</button> <button class="btn" onclick="previewYAML()">👁️ Preview YAML</button>
<button class="btn btn-warning" onclick="validateYAML()">✅ Validate Structure</button>
</div> </div>
</div> </div>
@ -646,6 +686,11 @@
<h3>YAML Preview</h3> <h3>YAML Preview</h3>
<textarea readonly style="width: 100%; height: 400px; font-family: monospace; font-size: 12px;" id="yamlPreviewText"></textarea> <textarea readonly style="width: 100%; height: 400px; font-family: monospace; font-size: 12px;" id="yamlPreviewText"></textarea>
</div> </div>
<div id="validationResults" class="form-section hidden">
<h3>Validation Results</h3>
<div id="validationContent"></div>
</div>
</div> </div>
</div> </div>
@ -679,12 +724,21 @@
} }
} }
function showMessage(message, type = 'success') {
const messageArea = document.getElementById('messageArea');
const className = type === 'error' ? 'error-message' : 'success-message';
messageArea.innerHTML = `<div class="${className}">${message}</div>`;
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}
function loadFile() { function loadFile() {
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0]; const file = fileInput.files[0];
if (!file) { if (!file) {
alert('Please select a file'); showMessage('Please select a file', 'error');
return; return;
} }
@ -694,82 +748,40 @@
yamlData = jsyaml.load(e.target.result); yamlData = jsyaml.load(e.target.result);
console.log('Loaded YAML data:', yamlData); console.log('Loaded YAML data:', yamlData);
updateUI(); updateUI();
alert('File loaded successfully!'); showMessage('File loaded successfully!');
} catch (error) { } catch (error) {
alert('Error parsing YAML: ' + error.message); showMessage('Error parsing YAML: ' + error.message, 'error');
} }
}; };
reader.readAsText(file); reader.readAsText(file);
} }
function loadSampleData() { function loadSampleData() {
// Load the provided sample data // This would load from your existing YAML data
const sampleYaml = `tools: // For brevity, I'll just show the structure
- 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"`;
try { 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(); updateUI();
alert('Sample data loaded successfully!'); showMessage('Sample data loaded successfully!');
} catch (error) { } catch (error) {
alert('Error loading sample data: ' + error.message); showMessage('Error loading sample data: ' + error.message, 'error');
} }
} }
@ -782,9 +794,10 @@ phases:
// Update statistics // Update statistics
updateStats(); updateStats();
// Update domain and phase checkboxes // Update checkboxes
updateDomainCheckboxes(); updateDomainCheckboxes();
updatePhaseCheckboxes(); updatePhaseCheckboxes();
updateDomainAgnosticCheckboxes();
// Render tools grid // Render tools grid
renderToolsGrid(); renderToolsGrid();
@ -797,6 +810,7 @@ phases:
document.getElementById('totalTools').textContent = tools.length; document.getElementById('totalTools').textContent = tools.length;
document.getElementById('totalDomains').textContent = yamlData.domains ? yamlData.domains.length : 0; document.getElementById('totalDomains').textContent = yamlData.domains ? yamlData.domains.length : 0;
document.getElementById('totalPhases').textContent = yamlData.phases ? yamlData.phases.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; const selfHosted = tools.filter(tool => tool.accessType === 'self-hosted').length;
document.getElementById('selfHostedCount').textContent = selfHosted; document.getElementById('selfHostedCount').textContent = selfHosted;
@ -804,10 +818,8 @@ phases:
const knowledgebaseTools = tools.filter(tool => tool.knowledgebase === true).length; const knowledgebaseTools = tools.filter(tool => tool.knowledgebase === true).length;
document.getElementById('knowledgebaseCount').textContent = knowledgebaseTools; document.getElementById('knowledgebaseCount').textContent = knowledgebaseTools;
// Update tag analytics // Update analytics
updateTagAnalytics(); updateTagAnalytics();
// Update skill distribution
updateSkillDistribution(); updateSkillDistribution();
} }
@ -816,7 +828,7 @@ phases:
if (yamlData && yamlData.tools) { if (yamlData && yamlData.tools) {
yamlData.tools.forEach(tool => { yamlData.tools.forEach(tool => {
if (tool.tags) { if (tool.tags && Array.isArray(tool.tags)) {
tool.tags.forEach(tag => { tool.tags.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1; tagCounts[tag] = (tagCounts[tag] || 0) + 1;
}); });
@ -831,7 +843,6 @@ phases:
return; return;
} }
// Sort tags by count (descending)
const sortedTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]); const sortedTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]);
container.innerHTML = sortedTags.map(([tag, count]) => ` container.innerHTML = sortedTags.map(([tag, count]) => `
@ -848,7 +859,9 @@ phases:
if (yamlData && yamlData.tools) { if (yamlData && yamlData.tools) {
yamlData.tools.forEach(tool => { yamlData.tools.forEach(tool => {
const skill = tool.skillLevel || 'intermediate'; 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) { function searchForTag(tag) {
// Switch to tools tab
showTab('tools'); showTab('tools');
document.querySelector('[onclick="showTab(\'tools\')"]').classList.add('active'); document.querySelector('[onclick="showTab(\'tools\')"]').classList.add('active');
// Set search bar value and filter
document.getElementById('searchBar').value = tag; document.getElementById('searchBar').value = tag;
filterTools(); 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 = `
<input type="checkbox" id="domain-agnostic-${category.id}" value="${category.id}">
<label for="domain-agnostic-${category.id}">${category.name}</label>
`;
container.appendChild(div);
});
}
}
function renderToolsGrid() { function renderToolsGrid() {
const container = document.getElementById('toolsGrid'); const container = document.getElementById('toolsGrid');
container.innerHTML = ''; container.innerHTML = '';
@ -952,6 +979,12 @@ phases:
const tags = (tool.tags || []).map(tag => `<span class="tag">${tag}</span>`).join(''); const tags = (tool.tags || []).map(tag => `<span class="tag">${tag}</span>`).join('');
const knowledgebaseIndicator = tool.knowledgebase ? '<span class="tag" style="background: #e8f5e8; color: #27ae60; font-weight: bold;">📚 Knowledgebase</span>' : ''; const knowledgebaseIndicator = tool.knowledgebase ? '<span class="tag" style="background: #e8f5e8; color: #27ae60; font-weight: bold;">📚 Knowledgebase</span>' : '';
// Add domain-agnostic indicators
const domainAgnosticTags = (tool['domain-agnostic-software'] || []).map(cat => {
const categoryName = getDomainAgnosticName(cat);
return `<span class="tag domain-agnostic">🔧 ${categoryName}</span>`;
}).join('');
card.innerHTML = ` card.innerHTML = `
<h3>${tool.name}</h3> <h3>${tool.name}</h3>
<div style="margin: 5px 0;"> <div style="margin: 5px 0;">
@ -959,7 +992,7 @@ phases:
${knowledgebaseIndicator} ${knowledgebaseIndicator}
</div> </div>
<p>${tool.description}</p> <p>${tool.description}</p>
<div style="margin: 10px 0;">${tags}</div> <div style="margin: 10px 0;">${tags}${domainAgnosticTags}</div>
<div style="margin-top: 15px;"> <div style="margin-top: 15px;">
<button class="btn" onclick="editTool(${index})">✏️ Edit</button> <button class="btn" onclick="editTool(${index})">✏️ Edit</button>
<button class="btn btn-danger" onclick="confirmDelete(${index})">🗑️ Delete</button> <button class="btn btn-danger" onclick="confirmDelete(${index})">🗑️ Delete</button>
@ -995,6 +1028,12 @@ phases:
return card; 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() { function filterTools() {
const searchTerm = document.getElementById('searchBar').value.toLowerCase(); const searchTerm = document.getElementById('searchBar').value.toLowerCase();
const cards = document.querySelectorAll('#toolsGrid .tool-card'); const cards = document.querySelectorAll('#toolsGrid .tool-card');
@ -1004,7 +1043,6 @@ phases:
cards.forEach((card, index) => { cards.forEach((card, index) => {
const tool = yamlData.tools[index]; const tool = yamlData.tools[index];
// Search in multiple fields
const searchableText = [ const searchableText = [
tool.name || '', tool.name || '',
tool.description || '', tool.description || '',
@ -1012,6 +1050,7 @@ phases:
...(tool.domains || []), ...(tool.domains || []),
...(tool.phases || []), ...(tool.phases || []),
...(tool.platforms || []), ...(tool.platforms || []),
...(tool['domain-agnostic-software'] || []),
tool.skillLevel || '', tool.skillLevel || '',
tool.license || '', tool.license || '',
tool.accessType || '', tool.accessType || '',
@ -1030,15 +1069,13 @@ phases:
currentEditingIndex = index; currentEditingIndex = index;
const tool = yamlData.tools[index]; const tool = yamlData.tools[index];
// Switch to editor tab
showTab('editor'); showTab('editor');
document.querySelector('[onclick="showTab(\'editor\')"]').classList.add('active'); document.querySelector('[onclick="showTab(\'editor\')"]').classList.add('active');
// Update form title
document.getElementById('editorTitle').textContent = `Edit Tool: ${tool.name}`; document.getElementById('editorTitle').textContent = `Edit Tool: ${tool.name}`;
document.getElementById('deleteBtn').style.display = 'inline-block'; document.getElementById('deleteBtn').style.display = 'inline-block';
// Populate form // Populate form fields
document.getElementById('toolName').value = tool.name || ''; document.getElementById('toolName').value = tool.name || '';
document.getElementById('description').value = tool.description || ''; document.getElementById('description').value = tool.description || '';
document.getElementById('skillLevel').value = tool.skillLevel || ''; document.getElementById('skillLevel').value = tool.skillLevel || '';
@ -1049,31 +1086,23 @@ phases:
document.getElementById('statusUrl').value = tool.statusUrl || ''; document.getElementById('statusUrl').value = tool.statusUrl || '';
document.getElementById('knowledgebase').checked = tool.knowledgebase || false; document.getElementById('knowledgebase').checked = tool.knowledgebase || false;
// Set platforms // Set checkboxes
const platforms = tool.platforms || []; setCheckboxValues('#platformsCheckbox input', tool.platforms || []);
document.querySelectorAll('#platformsCheckbox input').forEach(checkbox => { setCheckboxValues('#domainsCheckbox input', tool.domains || []);
checkbox.checked = platforms.includes(checkbox.value); 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 || []); populateTags(tool.tags || []);
} }
function setCheckboxValues(selector, values) {
document.querySelectorAll(selector).forEach(checkbox => {
checkbox.checked = values.includes(checkbox.value);
});
}
function populateTags(tags) { function populateTags(tags) {
const container = document.getElementById('tagContainer'); const container = document.getElementById('tagContainer');
// Clear existing tags except input
container.querySelectorAll('.removable-tag').forEach(tag => tag.remove()); container.querySelectorAll('.removable-tag').forEach(tag => tag.remove());
tags.forEach(tag => { tags.forEach(tag => {
@ -1112,8 +1141,9 @@ phases:
} }
function saveTool() { function saveTool() {
try {
if (!yamlData) { if (!yamlData) {
yamlData = { tools: [], domains: [], phases: [] }; yamlData = { tools: [], domains: [], phases: [], 'domain-agnostic-software': [] };
} }
if (!yamlData.tools) { if (!yamlData.tools) {
yamlData.tools = []; yamlData.tools = [];
@ -1125,6 +1155,7 @@ phases:
domains: getCheckedValues('#domainsCheckbox input:checked'), domains: getCheckedValues('#domainsCheckbox input:checked'),
phases: getCheckedValues('#phasesCheckbox input:checked'), phases: getCheckedValues('#phasesCheckbox input:checked'),
platforms: getCheckedValues('#platformsCheckbox input:checked'), platforms: getCheckedValues('#platformsCheckbox input:checked'),
'domain-agnostic-software': getCheckedValues('#domainAgnosticCheckbox input:checked'),
skillLevel: document.getElementById('skillLevel').value, skillLevel: document.getElementById('skillLevel').value,
accessType: document.getElementById('accessType').value, accessType: document.getElementById('accessType').value,
url: document.getElementById('url').value, url: document.getElementById('url').value,
@ -1134,25 +1165,34 @@ phases:
tags: getTags() tags: getTags()
}; };
// Add statusUrl if provided // 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; const statusUrl = document.getElementById('statusUrl').value;
if (statusUrl) { if (statusUrl) {
tool.statusUrl = statusUrl; tool.statusUrl = statusUrl;
} }
if (currentEditingIndex >= 0) { if (currentEditingIndex >= 0) {
// Update existing tool
yamlData.tools[currentEditingIndex] = tool; yamlData.tools[currentEditingIndex] = tool;
alert('Tool updated successfully!'); showMessage('Tool updated successfully!');
} else { } else {
// Add new tool
yamlData.tools.push(tool); yamlData.tools.push(tool);
alert('Tool added successfully!'); showMessage('Tool added successfully!');
} }
clearForm(); clearForm();
updateStats(); updateStats();
renderToolsGrid(); renderToolsGrid();
} catch (error) {
showMessage('Error saving tool: ' + error.message, 'error');
}
} }
function getCheckedValues(selector) { function getCheckedValues(selector) {
@ -1188,7 +1228,7 @@ phases:
clearForm(); clearForm();
updateStats(); updateStats();
renderToolsGrid(); renderToolsGrid();
alert('Tool deleted successfully!'); showMessage('Tool deleted successfully!');
} }
} }
@ -1223,7 +1263,7 @@ phases:
function bulkUpdateSkillLevel() { function bulkUpdateSkillLevel() {
if (selectedTools.size === 0) { if (selectedTools.size === 0) {
alert('No tools selected'); showMessage('No tools selected', 'error');
return; return;
} }
@ -1232,66 +1272,83 @@ phases:
selectedTools.forEach(index => { selectedTools.forEach(index => {
yamlData.tools[index].skillLevel = skillLevel; yamlData.tools[index].skillLevel = skillLevel;
}); });
alert(`Updated skill level for ${selectedTools.size} tools`); showMessage(`Updated skill level for ${selectedTools.size} tools`);
renderBulkGrid(); renderBulkGrid();
} }
} }
function bulkUpdateDomains() { function bulkUpdateDomains() {
if (selectedTools.size === 0) { if (selectedTools.size === 0) {
alert('No tools selected'); showMessage('No tools selected', 'error');
return; return;
} }
const domains = prompt('Enter domains (comma-separated):'); const domains = prompt('Enter domains (comma-separated):');
if (domains) { if (domains) {
const domainList = domains.split(',').map(d => d.trim()); const domainList = domains.split(',').map(d => d.trim()).filter(d => d);
selectedTools.forEach(index => { selectedTools.forEach(index => {
yamlData.tools[index].domains = domainList; yamlData.tools[index].domains = domainList;
}); });
alert(`Updated domains for ${selectedTools.size} tools`); showMessage(`Updated domains for ${selectedTools.size} tools`);
renderBulkGrid(); renderBulkGrid();
} }
} }
function bulkUpdatePhases() { function bulkUpdatePhases() {
if (selectedTools.size === 0) { if (selectedTools.size === 0) {
alert('No tools selected'); showMessage('No tools selected', 'error');
return; return;
} }
const phases = prompt('Enter phases (comma-separated):'); const phases = prompt('Enter phases (comma-separated):');
if (phases) { if (phases) {
const phaseList = phases.split(',').map(p => p.trim()); const phaseList = phases.split(',').map(p => p.trim()).filter(p => p);
selectedTools.forEach(index => { selectedTools.forEach(index => {
yamlData.tools[index].phases = phaseList; 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(); renderBulkGrid();
} }
} }
function bulkUpdateTags() { function bulkUpdateTags() {
if (selectedTools.size === 0) { if (selectedTools.size === 0) {
alert('No tools selected'); showMessage('No tools selected', 'error');
return; return;
} }
const tags = prompt('Enter tags (comma-separated):'); const tags = prompt('Enter tags (comma-separated):');
if (tags) { if (tags) {
const tagList = tags.split(',').map(t => t.trim()); const tagList = tags.split(',').map(t => t.trim()).filter(t => t);
selectedTools.forEach(index => { selectedTools.forEach(index => {
yamlData.tools[index].tags = tagList; yamlData.tools[index].tags = tagList;
}); });
alert(`Updated tags for ${selectedTools.size} tools`); showMessage(`Updated tags for ${selectedTools.size} tools`);
updateStats(); // Refresh tag analytics updateStats();
renderBulkGrid(); renderBulkGrid();
} }
} }
function bulkSetKnowledgebase(value) { function bulkSetKnowledgebase(value) {
if (selectedTools.size === 0) { if (selectedTools.size === 0) {
alert('No tools selected'); showMessage('No tools selected', 'error');
return; return;
} }
@ -1307,26 +1364,27 @@ phases:
}); });
const actionCompleted = value ? 'marked as knowledgebase' : 'removed knowledgebase flag from'; 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(); renderBulkGrid();
} }
function bulkClearField(fieldName) { function bulkClearField(fieldName) {
if (selectedTools.size === 0) { if (selectedTools.size === 0) {
alert('No tools selected'); showMessage('No tools selected', 'error');
return; 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?`)) { if (!confirm(`Are you sure you want to clear ${fieldDisplayName} for ${selectedTools.size} selected tools?`)) {
return; return;
} }
selectedTools.forEach(index => { selectedTools.forEach(index => {
if (yamlData.tools[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] = []; yamlData.tools[index][fieldName] = [];
} else { } else {
yamlData.tools[index][fieldName] = ''; 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') { if (fieldName === 'tags') {
updateStats(); // Refresh tag analytics if tags were cleared updateStats();
} }
renderBulkGrid(); renderBulkGrid();
@ -1345,7 +1403,7 @@ phases:
function bulkDelete() { function bulkDelete() {
if (selectedTools.size === 0) { if (selectedTools.size === 0) {
alert('No tools selected'); showMessage('No tools selected', 'error');
return; return;
} }
@ -1357,13 +1415,45 @@ phases:
selectedTools.clear(); selectedTools.clear();
updateStats(); updateStats();
renderBulkGrid(); 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 = '<div class="success-message">✅ YAML structure is valid!</div>';
} else {
container.innerHTML = '<div class="error-message">' + validationResults.join('<br>') + '</div>';
}
document.getElementById('validationResults').classList.remove('hidden');
}
function previewYAML() { function previewYAML() {
if (!yamlData) { if (!yamlData) {
alert('No data to preview'); showMessage('No data to preview', 'error');
return; return;
} }
@ -1374,7 +1464,7 @@ phases:
function exportYAML() { function exportYAML() {
if (!yamlData) { if (!yamlData) {
alert('No data to export'); showMessage('No data to export', 'error');
return; return;
} }
@ -1389,6 +1479,8 @@ phases:
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
showMessage('YAML file exported successfully!');
} }
</script> </script>
</body> </body>

View File

@ -9,6 +9,7 @@ const yamlContent = await fs.readFile(yamlPath, 'utf8');
const data = load(yamlContent) as any; const data = load(yamlContent) as any;
const tools = data.tools; const tools = data.tools;
const phases = data.phases; const phases = data.phases;
const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add this line
--- ---
<!-- AI Query Interface --> <!-- AI Query Interface -->
@ -88,7 +89,7 @@ const phases = data.phases;
</div> </div>
</section> </section>
<script define:vars={{ tools, phases }}> <script define:vars={{ tools, phases, domainAgnosticSoftware }}>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const aiInterface = document.getElementById('ai-interface'); const aiInterface = document.getElementById('ai-interface');
const aiInput = document.getElementById('ai-query-input'); const aiInput = document.getElementById('ai-query-input');
@ -280,8 +281,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Group tools by phase // Group tools by phase
const toolsByPhase = {}; const toolsByPhase = {};
// Replace hardcoded values with dynamic data from YAML const phaseOrder = phases.map(phase => phase.id);
const phaseOrder = phases.filter(phase => phase.id !== 'collaboration-general').map(phase => phase.id);
const phaseNames = phases.reduce((acc, phase) => { const phaseNames = phases.reduce((acc, phase) => {
acc[phase.id] = phase.name; acc[phase.id] = phase.name;
return acc; return acc;

View File

@ -11,44 +11,49 @@ const data = load(yamlContent) as any;
const domains = data.domains; const domains = data.domains;
const phases = data.phases; const phases = data.phases;
const tools = data.tools; const tools = data.tools;
const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
// Separate collaboration tools from domain-specific tools // Get tools for each domain-agnostic section based on the tool's domain-agnostic-software field
const collaborationTools = tools.filter((tool: any) => const domainAgnosticTools = domainAgnosticSoftware.map((section: any) => ({
tool.phases && tool.phases.includes('collaboration-general') section,
); tools: tools.filter((tool: any) =>
tool['domain-agnostic-software'] && tool['domain-agnostic-software'].includes(section.id)
)
}));
const domainTools = tools.filter((tool: any) => // Matrix shows ALL tools based on domains × phases (independent of domain-agnostic-software)
!tool.phases || !tool.phases.includes('collaboration-general')
);
// Create matrix structure for domain-specific tools only
const matrix: Record<string, Record<string, any[]>> = {}; const matrix: Record<string, Record<string, any[]>> = {};
domains.forEach((domain: any) => { domains.forEach((domain: any) => {
matrix[domain.id] = {}; matrix[domain.id] = {};
phases.filter((phase: any) => phase.id !== 'collaboration-general').forEach((phase: any) => { phases.forEach((phase: any) => {
matrix[domain.id][phase.id] = domainTools.filter((tool: any) => matrix[domain.id][phase.id] = tools.filter((tool: any) =>
tool.domains && tool.domains.includes(domain.id) && tool.phases && tool.phases.includes(phase.id) tool.domains && tool.domains.includes(domain.id) &&
tool.phases && tool.phases.includes(phase.id)
); );
}); });
}); });
--- ---
<div id="matrix-container" class="matrix-wrapper" style="display: none;"> <div id="matrix-container" class="matrix-wrapper" style="display: none;">
<div id="collaboration-tools-section" class="collaboration-section-collapsed" style="margin-bottom: 1.5rem;"> <!-- Domain-Agnostic Software Sections -->
<div class="collaboration-header" onclick="toggleCollaborationSection()" style="cursor: pointer; display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.1rem;"> {domainAgnosticTools.map((sectionData: any, index: number) => (
<h3 style="margin: 0; color: var(--color-text); font-size: 1.125rem;">Übergreifend & Kollaboration</h3> <div id={`domain-agnostic-section-${sectionData.section.id}`} class="collaboration-section-collapsed" style="margin-bottom: 1.5rem;">
<div class="collaboration-header" onclick={`toggleDomainAgnosticSection('${sectionData.section.id}')`} style="cursor: pointer; display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.1rem;">
<h3 style="margin: 0; color: var(--color-text); font-size: 1.125rem;">
{sectionData.section.name}
<span id={`count-${sectionData.section.id}`} class="badge" style="background-color: var(--color-text-secondary); color: var(--color-bg); margin-left: 0.5rem; font-size: 0.75rem;">
{sectionData.tools.length}
</span>
</h3>
<div class="collaboration-expand-icon"> <div class="collaboration-expand-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline> <polyline points="6 9 12 15 18 9"></polyline>
</svg> </svg>
</div> </div>
</div>
</div>
</div>
</div> </div>
<div class="collaboration-content" style="display: none;"> <div class="collaboration-content" style="display: none;">
<div class="collaboration-tools-compact" id="collaboration-tools-container"> <div class="collaboration-tools-compact" id={`domain-agnostic-tools-${sectionData.section.id}`}>
{collaborationTools.map((tool: any) => { {sectionData.tools.map((tool: any) => {
const hasValidProjectUrl = tool.projectUrl !== undefined && const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null && tool.projectUrl !== null &&
tool.projectUrl !== "" && tool.projectUrl !== "" &&
@ -77,6 +82,8 @@ domains.forEach((domain: any) => {
})} })}
</div> </div>
</div> </div>
</div>
))}
<!-- DFIR Tools Matrix --> <!-- DFIR Tools Matrix -->
<div id="dfir-matrix-section"> <div id="dfir-matrix-section">
@ -85,7 +92,7 @@ domains.forEach((domain: any) => {
<thead> <thead>
<tr> <tr>
<th style="width: 200px;">Domain / Phase</th> <th style="width: 200px;">Domain / Phase</th>
{phases.filter((phase: any) => phase.id !== 'collaboration-general').map((phase: any) => ( {phases.map((phase: any) => (
<th data-phase={phase.id}>{phase.name}</th> <th data-phase={phase.id}>{phase.name}</th>
))} ))}
</tr> </tr>
@ -94,7 +101,7 @@ domains.forEach((domain: any) => {
{domains.map((domain: any) => ( {domains.map((domain: any) => (
<tr data-domain={domain.id}> <tr data-domain={domain.id}>
<th>{domain.name}</th> <th>{domain.name}</th>
{phases.filter((phase: any) => phase.id !== 'collaboration-general').map((phase: any) => ( {phases.map((phase: any) => (
<td class="matrix-cell" data-domain={domain.id} data-phase={phase.id}> <td class="matrix-cell" data-domain={domain.id} data-phase={phase.id}>
{matrix[domain.id][phase.id].map((tool: any) => { {matrix[domain.id][phase.id].map((tool: any) => {
const hasValidProjectUrl = tool.projectUrl !== undefined && const hasValidProjectUrl = tool.projectUrl !== undefined &&
@ -122,7 +129,7 @@ domains.forEach((domain: any) => {
</div> </div>
</div> </div>
<!-- Tool Details Modal --> <!-- Tool Details Modal stays the same -->
<div class="modal-overlay" id="modal-overlay" onclick="window.hideToolDetails()"></div> <div class="modal-overlay" id="modal-overlay" onclick="window.hideToolDetails()"></div>
<div class="tool-details" id="tool-details"> <div class="tool-details" id="tool-details">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;"> <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
@ -143,11 +150,10 @@ domains.forEach((domain: any) => {
<div id="tool-tags" style="margin-bottom: 1rem;"></div> <div id="tool-tags" style="margin-bottom: 1rem;"></div>
<!-- Updated button container for dual buttons -->
<div id="tool-links" style="display: flex; gap: 0.5rem; flex-direction: column;"></div> <div id="tool-links" style="display: flex; gap: 0.5rem; flex-direction: column;"></div>
</div> </div>
<script define:vars={{ toolsData: tools, collaborationTools, domainTools }}> <script define:vars={{ toolsData: tools, domainAgnosticSoftware, domainAgnosticTools }}>
// Helper function to get selected phase from active button // Helper function to get selected phase from active button
function getSelectedPhase() { function getSelectedPhase() {
const activePhaseButton = document.querySelector('.phase-button.active'); const activePhaseButton = document.querySelector('.phase-button.active');
@ -200,11 +206,11 @@ domains.forEach((domain: any) => {
} }
} }
// Toggle collaboration section // Toggle domain-agnostic section
function toggleCollaborationSection() { function toggleDomainAgnosticSection(sectionId) {
const section = document.getElementById('collaboration-tools-section'); const section = document.getElementById(`domain-agnostic-section-${sectionId}`);
const content = document.querySelector('.collaboration-content'); const content = section?.querySelector('.collaboration-content');
const icon = document.querySelector('.collaboration-expand-icon svg'); const icon = section?.querySelector('.collaboration-expand-icon svg');
if (!section || !content || !icon) return; if (!section || !content || !icon) return;
@ -225,9 +231,10 @@ function toggleCollaborationSection() {
} }
} }
// Make function globally available // Make functions globally available
window.toggleCollaborationSection = toggleCollaborationSection; window.toggleDomainAgnosticSection = toggleDomainAgnosticSection;
// Helper function to create compact collaboration tool cards for matrix view
// Helper function to create compact tool cards
function createCollaborationToolCardCompact(tool) { function createCollaborationToolCardCompact(tool) {
const hasValidProjectUrl = tool.projectUrl !== undefined && const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null && tool.projectUrl !== null &&
@ -261,7 +268,7 @@ window.toggleCollaborationSection = toggleCollaborationSection;
return cardDiv; return cardDiv;
} }
// Tool details functions // Tool details functions (unchanged)
window.showToolDetails = function(toolName) { window.showToolDetails = function(toolName) {
const tool = toolsData.find(t => t.name === toolName); const tool = toolsData.find(t => t.name === toolName);
if (!tool) return; if (!tool) return;
@ -314,14 +321,12 @@ window.toggleCollaborationSection = toggleCollaborationSection;
</div> </div>
`; `;
// Links - Updated to handle dual buttons for hosted tools AND knowledgebase links // Links
const linksContainer = document.getElementById('tool-links'); const linksContainer = document.getElementById('tool-links');
let linksHTML = ''; let linksHTML = '';
// Main action buttons
if (hasValidProjectUrl) { if (hasValidProjectUrl) {
// Two buttons for tools we're hosting
linksHTML += ` linksHTML += `
<div style="display: flex; gap: 0.5rem;"> <div style="display: flex; gap: 0.5rem;">
<a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;"> <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
@ -333,7 +338,6 @@ window.toggleCollaborationSection = toggleCollaborationSection;
</div> </div>
`; `;
} else { } else {
// Single button for tools we're not hosting
linksHTML += ` linksHTML += `
<a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;"> <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
Software-Homepage Software-Homepage
@ -341,7 +345,6 @@ window.toggleCollaborationSection = toggleCollaborationSection;
`; `;
} }
// Add knowledgebase link if available
if (tool.knowledgebase === true) { if (tool.knowledgebase === true) {
const kbId = tool.name.toLowerCase().replace(/\s+/g, '-'); const kbId = tool.name.toLowerCase().replace(/\s+/g, '-');
linksHTML += ` linksHTML += `
@ -374,7 +377,6 @@ window.toggleCollaborationSection = toggleCollaborationSection;
window.addEventListener('viewChanged', (event) => { window.addEventListener('viewChanged', (event) => {
const view = event.detail; const view = event.detail;
if (view === 'matrix') { if (view === 'matrix') {
// Delay highlighting to ensure matrix is visible
setTimeout(updateMatrixHighlighting, 100); setTimeout(updateMatrixHighlighting, 100);
} }
}); });
@ -393,56 +395,34 @@ window.toggleCollaborationSection = toggleCollaborationSection;
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view'); const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
if (currentView === 'matrix') { if (currentView === 'matrix') {
// Get selected phase from active button instead of dropdown
const selectedPhase = getSelectedPhase(); const selectedPhase = getSelectedPhase();
// Handle collaboration tools section // Get all domain-agnostic phase IDs
const collaborationSection = document.getElementById('collaboration-tools-section'); const domainAgnosticPhaseIds = domainAgnosticSoftware.map(section => section.id);
const dfirMatrixSection = document.getElementById('dfir-matrix-section');
const collaborationContainer = document.getElementById('collaboration-tools-container');
if (selectedPhase === 'collaboration-general') { // Check if selected phase is a domain-agnostic phase
// Show only collaboration tools, hide matrix const isDomainAgnosticPhase = domainAgnosticPhaseIds.includes(selectedPhase);
collaborationSection.style.display = 'block';
dfirMatrixSection.style.display = 'none';
// Filter collaboration tools // Handle domain-agnostic sections
const filteredCollaboration = filtered.filter(tool => (tool.phases || []).includes('collaboration-general')); domainAgnosticSoftware.forEach(sectionData => {
collaborationContainer.innerHTML = ''; const section = document.getElementById(`domain-agnostic-section-${sectionData.id}`);
const container = document.getElementById(`domain-agnostic-tools-${sectionData.id}`);
if (!section || !container) return;
filteredCollaboration.forEach(tool => {
const toolCard = createCollaborationToolCardCompact(tool);
collaborationContainer.appendChild(toolCard);
}); });
} else {
// Show matrix, handle collaboration tools visibility
dfirMatrixSection.style.display = 'block';
if (selectedPhase === '' || selectedPhase === null) { if (!isDomainAgnosticPhase) {
// Show collaboration tools when no specific phase is selected // Show matrix for regular phases
collaborationSection.style.display = 'block'; document.getElementById('dfir-matrix-section').style.display = 'block';
// Show all collaboration tools that pass general filters // Clear and update matrix cells with ALL tools (based on domains × phases)
const filteredCollaboration = filtered.filter(tool => (tool.phases || []).includes('collaboration-general'));
collaborationContainer.innerHTML = '';
filteredCollaboration.forEach(tool => {
const toolCard = createCollaborationToolCardCompact(tool);
collaborationContainer.appendChild(toolCard);
});
} else {
// Hide collaboration tools when specific DFIR phase is selected
collaborationSection.style.display = 'none';
}
// Clear and update matrix cells with DFIR tools only
document.querySelectorAll('.matrix-cell').forEach(cell => { document.querySelectorAll('.matrix-cell').forEach(cell => {
cell.innerHTML = ''; cell.innerHTML = '';
}); });
// Re-populate with filtered DFIR tools - with safe array handling // Re-populate with filtered tools based on domains × phases
const filteredDfirTools = filtered.filter(tool => !(tool.phases || []).includes('collaboration-general')); filtered.forEach(tool => {
filteredDfirTools.forEach(tool => {
const hasValidProjectUrl = tool.projectUrl !== undefined && const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null && tool.projectUrl !== null &&
tool.projectUrl !== "" && tool.projectUrl !== "" &&
@ -453,7 +433,6 @@ window.toggleCollaborationSection = toggleCollaborationSection;
domains.forEach(domain => { domains.forEach(domain => {
phases.forEach(phase => { phases.forEach(phase => {
if (phase !== 'collaboration-general') {
const cell = document.querySelector(`[data-domain="${domain}"][data-phase="${phase}"]`); const cell = document.querySelector(`[data-domain="${domain}"][data-phase="${phase}"]`);
if (cell) { if (cell) {
const chip = document.createElement('span'); const chip = document.createElement('span');
@ -463,12 +442,10 @@ window.toggleCollaborationSection = toggleCollaborationSection;
chip.onclick = () => window.showToolDetails(tool.name); chip.onclick = () => window.showToolDetails(tool.name);
cell.appendChild(chip); cell.appendChild(chip);
} }
}
}); });
}); });
}); });
// Update highlighting after matrix content is updated
setTimeout(updateMatrixHighlighting, 50); setTimeout(updateMatrixHighlighting, 50);
} }
} }

View File

@ -21,6 +21,7 @@ tools:
platforms: platforms:
- Windows - Windows
- Linux - Linux
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: download accessType: download
url: https://www.autopsy.com/ url: https://www.autopsy.com/
@ -50,6 +51,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: advanced skillLevel: advanced
accessType: download accessType: download
url: https://www.volatilityfoundation.org/ url: https://www.volatilityfoundation.org/
@ -73,9 +75,10 @@ tools:
- data-collection - data-collection
- examination - examination
- analysis - analysis
- collaboration-general
platforms: platforms:
- Web - Web
domain-agnostic-software:
- collaboration-general
skillLevel: intermediate skillLevel: intermediate
accessType: self-hosted accessType: self-hosted
url: https://github.com/TheHive-Project/TheHive url: https://github.com/TheHive-Project/TheHive
@ -104,6 +107,7 @@ tools:
- analysis - analysis
platforms: platforms:
- Web - Web
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: self-hosted accessType: self-hosted
url: https://misp-project.org/ url: https://misp-project.org/
@ -129,6 +133,7 @@ tools:
- reporting - reporting
platforms: platforms:
- Web - Web
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: self-hosted accessType: self-hosted
url: https://timesketch.org/ url: https://timesketch.org/
@ -159,6 +164,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: download accessType: download
url: https://www.wireshark.org/ url: https://www.wireshark.org/
@ -185,6 +191,7 @@ tools:
- reporting - reporting
platforms: platforms:
- Windows - Windows
domain-agnostic-software:
skillLevel: beginner skillLevel: beginner
accessType: commercial accessType: commercial
url: https://www.magnetforensics.com/products/magnet-axiom/ url: https://www.magnetforensics.com/products/magnet-axiom/
@ -210,6 +217,7 @@ tools:
- reporting - reporting
platforms: platforms:
- Windows - Windows
domain-agnostic-software:
skillLevel: beginner skillLevel: beginner
accessType: commercial accessType: commercial
url: https://cellebrite.com/en/ufed/ url: https://cellebrite.com/en/ufed/
@ -231,6 +239,7 @@ tools:
platforms: platforms:
- Linux - Linux
- Web - Web
domain-agnostic-software:
skillLevel: advanced skillLevel: advanced
accessType: self-hosted accessType: self-hosted
url: https://github.com/cert-ee/cuckoo3 url: https://github.com/cert-ee/cuckoo3
@ -252,6 +261,7 @@ tools:
platforms: platforms:
- Windows - Windows
- Linux - Linux
domain-agnostic-software:
skillLevel: expert skillLevel: expert
accessType: download accessType: download
url: https://github.com/NationalSecurityAgency/ghidra url: https://github.com/NationalSecurityAgency/ghidra
@ -276,6 +286,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: download accessType: download
url: https://plaso.readthedocs.io/ url: https://plaso.readthedocs.io/
@ -301,6 +312,7 @@ tools:
- analysis - analysis
platforms: platforms:
- Web - Web
domain-agnostic-software:
skillLevel: beginner skillLevel: beginner
accessType: self-hosted accessType: self-hosted
url: https://gchq.github.io/CyberChef/ url: https://gchq.github.io/CyberChef/
@ -335,6 +347,7 @@ tools:
- Linux - Linux
- macOS - macOS
- Web - Web
domain-agnostic-software:
skillLevel: advanced skillLevel: advanced
accessType: self-hosted accessType: self-hosted
url: https://www.velociraptor.app/ url: https://www.velociraptor.app/
@ -364,6 +377,7 @@ tools:
- Linux - Linux
- macOS - macOS
- Web - Web
domain-agnostic-software:
skillLevel: advanced skillLevel: advanced
accessType: self-hosted accessType: self-hosted
url: https://github.com/google/grr url: https://github.com/google/grr
@ -389,6 +403,7 @@ tools:
- analysis - analysis
platforms: platforms:
- Linux - Linux
domain-agnostic-software:
skillLevel: expert skillLevel: expert
accessType: self-hosted accessType: self-hosted
url: https://arkime.com/ url: https://arkime.com/
@ -414,6 +429,7 @@ tools:
- analysis - analysis
platforms: platforms:
- Windows - Windows
domain-agnostic-software:
skillLevel: beginner skillLevel: beginner
accessType: download accessType: download
url: https://www.netresec.com/?page=NetworkMiner url: https://www.netresec.com/?page=NetworkMiner
@ -438,6 +454,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: novice skillLevel: novice
accessType: download accessType: download
url: https://exiftool.org/ url: https://exiftool.org/
@ -462,6 +479,7 @@ tools:
- reporting - reporting
platforms: platforms:
- Web - Web
domain-agnostic-software:
skillLevel: advanced skillLevel: advanced
accessType: commercial accessType: commercial
url: https://www.chainalysis.com/ url: https://www.chainalysis.com/
@ -490,6 +508,7 @@ tools:
- Linux - Linux
- macOS - macOS
- Web - Web
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: self-hosted accessType: self-hosted
url: https://neo4j.com/ url: https://neo4j.com/
@ -516,6 +535,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: download accessType: download
url: https://qgis.org/ url: https://qgis.org/
@ -543,9 +563,10 @@ tools:
- ics-forensics - ics-forensics
phases: phases:
- reporting - reporting
- collaboration-general
platforms: platforms:
- Web - Web
domain-agnostic-software:
- collaboration-general
skillLevel: novice skillLevel: novice
accessType: self-hosted accessType: self-hosted
url: https://nextcloud.com/ url: https://nextcloud.com/
@ -562,9 +583,10 @@ tools:
- incident-response - incident-response
- malware-analysis - malware-analysis
phases: phases:
- collaboration-general
platforms: platforms:
- Web - Web
domain-agnostic-software:
- collaboration-general
skillLevel: beginner skillLevel: beginner
accessType: self-hosted accessType: self-hosted
url: https://gitea.io/ url: https://gitea.io/
@ -586,6 +608,7 @@ tools:
platforms: platforms:
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: advanced skillLevel: advanced
accessType: download accessType: download
url: https://github.com/ReFirmLabs/binwalk url: https://github.com/ReFirmLabs/binwalk
@ -611,11 +634,12 @@ tools:
- ics-forensics - ics-forensics
phases: phases:
- reporting - reporting
- collaboration-general
platforms: platforms:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
- collaboration-general
skillLevel: novice skillLevel: novice
accessType: download accessType: download
url: https://www.libreoffice.org/ url: https://www.libreoffice.org/
@ -643,12 +667,13 @@ tools:
- ics-forensics - ics-forensics
phases: phases:
- reporting - reporting
- collaboration-general
platforms: platforms:
- Windows - Windows
- Linux - Linux
- macOS - macOS
- Web - Web
domain-agnostic-software:
- collaboration-general
skillLevel: novice skillLevel: novice
accessType: commercial accessType: commercial
url: https://www.office.com/ url: https://www.office.com/
@ -675,6 +700,7 @@ tools:
- reporting - reporting
platforms: platforms:
- Web - Web
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: self-hosted accessType: self-hosted
url: https://graphsense.org/ url: https://graphsense.org/
@ -694,6 +720,7 @@ tools:
- data-collection - data-collection
platforms: platforms:
- Windows - Windows
domain-agnostic-software:
skillLevel: beginner skillLevel: beginner
accessType: commercial accessType: commercial
url: https://www.exterro.com/digital-forensics-software/ftk-imager url: https://www.exterro.com/digital-forensics-software/ftk-imager
@ -714,6 +741,7 @@ tools:
- data-collection - data-collection
platforms: platforms:
- Linux - Linux
domain-agnostic-software:
skillLevel: novice skillLevel: novice
accessType: download accessType: download
url: https://guymager.sourceforge.io/ url: https://guymager.sourceforge.io/
@ -732,6 +760,7 @@ tools:
- data-collection - data-collection
platforms: platforms:
- macOS - macOS
domain-agnostic-software:
skillLevel: novice skillLevel: novice
accessType: download accessType: download
url: https://github.com/Lazza/Fuji url: https://github.com/Lazza/Fuji
@ -754,6 +783,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: download accessType: download
url: https://github.com/abrignoni/ALEAPP url: https://github.com/abrignoni/ALEAPP
@ -776,6 +806,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: download accessType: download
url: https://github.com/abrignoni/iLEAPP url: https://github.com/abrignoni/iLEAPP
@ -798,6 +829,7 @@ tools:
- Windows - Windows
- Linux - Linux
- macOS - macOS
domain-agnostic-software:
skillLevel: intermediate skillLevel: intermediate
accessType: download accessType: download
url: https://github.com/abrignoni/VLEAPP url: https://github.com/abrignoni/VLEAPP
@ -805,6 +837,30 @@ tools:
license: MIT license: MIT
knowledgebase: false knowledgebase: false
tags: [vehicle-forensics, car-data, infotainment-analysis, embedded-systems, automotive] tags: [vehicle-forensics, car-data, infotainment-analysis, embedded-systems, automotive]
- name: Kali Linux
description: >-
Spezielle Linux-Distribution, die sich an Pentester richtet. Hat aber auch zahlreiche Forensik-Software an Bord.
domains:
- incident-response
- law-enforcement
- malware-analysis
- fraud-investigation
- network-forensics
- mobile-forensics
- cloud-forensics
- ics-forensics
phases:
platforms:
- OS
domain-agnostic-software:
- specific-os
skillLevel: novice
accessType: OS
url: https://kali.org/
projectUrl:
license: GPL-3
knowledgebase: true
tags: [pentesting, OS, Linux]
domains: domains:
- id: incident-response - id: incident-response
name: Incident Response & Breach-Untersuchung name: Incident Response & Breach-Untersuchung
@ -835,6 +891,10 @@ phases:
- id: reporting - id: reporting
name: Bericht & Präsentation name: Bericht & Präsentation
description: "Documentation, Visualization, Presentation Tools (z.B. QGIS für Geodaten, Timeline-Tools)" description: "Documentation, Visualization, Presentation Tools (z.B. QGIS für Geodaten, Timeline-Tools)"
domain-agnostic-software:
- id: collaboration-general - id: collaboration-general
name: Übergreifend & Kollaboration name: Übergreifend & Kollaboration
description: "Cross-cutting tools and collaboration platforms" description: "Cross-cutting tools and collaboration platforms"
- id: specific-os
name: Betriebssysteme
description: "Operating Systems which focus on forensics"

View File

@ -97,8 +97,19 @@ function createSystemPrompt(toolsData: any): string {
projectUrl: tool.projectUrl ? 'self-hosted' : 'external' projectUrl: tool.projectUrl ? 'self-hosted' : 'external'
})); }));
// Dynamically build phases list from configuration // Get regular phases (no more filtering needed)
const phasesDescription = toolsData.phases.map((phase: any) => const regularPhases = toolsData.phases || [];
// Get domain-agnostic software phases
const domainAgnosticSoftware = toolsData['domain-agnostic-software'] || [];
// Combine all phases for the description
const allPhaseItems = [
...regularPhases,
...domainAgnosticSoftware
];
const phasesDescription = allPhaseItems.map((phase: any) =>
`- ${phase.id}: ${phase.name}` `- ${phase.id}: ${phase.name}`
).join('\n'); ).join('\n');
@ -107,10 +118,28 @@ function createSystemPrompt(toolsData: any): string {
`- ${domain.id}: ${domain.name}` `- ${domain.id}: ${domain.name}`
).join('\n'); ).join('\n');
const phaseDescriptions = toolsData.phases // Build dynamic phase descriptions for tool selection
.filter((phase: any) => phase.id !== 'collaboration-general') const phaseDescriptions = regularPhases.map((phase: any) => {
.map((phase: any) => `- ${phase.name}: ${phase.description || 'Tools for this phase'}`) // Create generic descriptions or you could add a 'description' field to the YAML
.join('\n'); const descriptions = {
'data-collection': 'Imaging, Acquisition, Remote Collection Tools',
'examination': 'Parsing, Extraction, Initial Analysis Tools',
'analysis': 'Deep Analysis, Correlation, Visualization Tools',
'reporting': 'Documentation, Visualization, Presentation Tools (z.B. QGIS für Geodaten, Timeline-Tools)'
};
return `- ${phase.name}: ${phase.description || descriptions[phase.id] || 'Tools for this phase'}`;
}).join('\n');
// Add domain-agnostic software descriptions
const domainAgnosticDescriptions = domainAgnosticSoftware.map((section: any) =>
`- ${section.name}: ${section.description || 'Cross-cutting tools and platforms'}`
).join('\n');
// Create valid phase values for JSON schema
const validPhases = [
...regularPhases.map((p: any) => p.id),
...domainAgnosticSoftware.map((s: any) => s.id)
].join('|');
return `Du bist ein DFIR (Digital Forensics and Incident Response) Experte, der Ermittlern bei der Toolauswahl hilft. return `Du bist ein DFIR (Digital Forensics and Incident Response) Experte, der Ermittlern bei der Toolauswahl hilft.
@ -135,6 +164,9 @@ WICHTIGE REGELN:
TOOL-AUSWAHL NACH PHASE: TOOL-AUSWAHL NACH PHASE:
${phaseDescriptions} ${phaseDescriptions}
DOMAIN-AGNOSTIC SOFTWARE:
${domainAgnosticDescriptions}
ANTWORT-FORMAT (strict JSON): ANTWORT-FORMAT (strict JSON):
{ {
"scenario_analysis": "Detaillierte Analyse des Szenarios auf Deutsch/English", "scenario_analysis": "Detaillierte Analyse des Szenarios auf Deutsch/English",
@ -142,7 +174,7 @@ ANTWORT-FORMAT (strict JSON):
{ {
"name": "EXAKTER Name aus der Database", "name": "EXAKTER Name aus der Database",
"priority": "high|medium|low", "priority": "high|medium|low",
"phase": "${toolsData.phases.filter((p: any) => p.id !== 'collaboration-general').map((p: any) => p.id).join('|')}", "phase": "${validPhases}",
"justification": "Warum dieses Tool für diese Phase und Szenario geeignet ist" "justification": "Warum dieses Tool für diese Phase und Szenario geeignet ist"
} }
], ],