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

@@ -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
---
<!-- AI Query Interface -->
@@ -88,7 +89,7 @@ const phases = data.phases;
</div>
</section>
<script define:vars={{ tools, phases }}>
<script define:vars={{ tools, phases, domainAgnosticSoftware }}>
document.addEventListener('DOMContentLoaded', () => {
const aiInterface = document.getElementById('ai-interface');
const aiInput = document.getElementById('ai-query-input');
@@ -280,8 +281,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Group tools by phase
const toolsByPhase = {};
// Replace hardcoded values with dynamic data from YAML
const phaseOrder = phases.filter(phase => phase.id !== 'collaboration-general').map(phase => phase.id);
const phaseOrder = phases.map(phase => phase.id);
const phaseNames = phases.reduce((acc, phase) => {
acc[phase.id] = phase.name;
return acc;

View File

@@ -11,72 +11,79 @@ const data = load(yamlContent) as any;
const domains = data.domains;
const phases = data.phases;
const tools = data.tools;
const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
// Separate collaboration tools from domain-specific tools
const collaborationTools = tools.filter((tool: any) =>
tool.phases && tool.phases.includes('collaboration-general')
);
// Get tools for each domain-agnostic section based on the tool's domain-agnostic-software field
const domainAgnosticTools = domainAgnosticSoftware.map((section: any) => ({
section,
tools: tools.filter((tool: any) =>
tool['domain-agnostic-software'] && tool['domain-agnostic-software'].includes(section.id)
)
}));
const domainTools = tools.filter((tool: any) =>
!tool.phases || !tool.phases.includes('collaboration-general')
);
// Create matrix structure for domain-specific tools only
// Matrix shows ALL tools based on domains × phases (independent of domain-agnostic-software)
const matrix: Record<string, Record<string, any[]>> = {};
domains.forEach((domain: any) => {
matrix[domain.id] = {};
phases.filter((phase: any) => phase.id !== 'collaboration-general').forEach((phase: any) => {
matrix[domain.id][phase.id] = domainTools.filter((tool: any) =>
tool.domains && tool.domains.includes(domain.id) && tool.phases && tool.phases.includes(phase.id)
phases.forEach((phase: any) => {
matrix[domain.id][phase.id] = tools.filter((tool: any) =>
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="collaboration-tools-section" class="collaboration-section-collapsed" style="margin-bottom: 1.5rem;">
<div class="collaboration-header" onclick="toggleCollaborationSection()" 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;">Übergreifend & Kollaboration</h3>
<div class="collaboration-expand-icon">
<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>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class="collaboration-content" style="display: none;">
<div class="collaboration-tools-compact" id="collaboration-tools-container">
{collaborationTools.map((tool: any) => {
const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
tool.projectUrl !== "" &&
tool.projectUrl.trim() !== "";
return (
<div class={`collaboration-tool-compact ${hasValidProjectUrl ? 'hosted' : tool.license !== 'Proprietary' ? 'oss' : ''}`}
onclick={`window.showToolDetails('${tool.name}')`}>
<div class="tool-compact-header">
<h4 style="margin: 0; font-size: 0.875rem; font-weight: 600;">{tool.name}</h4>
<div style="display: flex; gap: 0.25rem;">
{hasValidProjectUrl && <span class="badge badge--mini badge-primary">Self-Hosted</span>}
{tool.license !== 'Proprietary' && <span class="badge badge--mini badge-success">OSS</span>}
{tool.knowledgebase === true && <span class="badge badge--mini badge-error">Infos 📖</span>}
<!-- Domain-Agnostic Software Sections -->
{domainAgnosticTools.map((sectionData: any, index: number) => (
<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">
<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>
</svg>
</div>
</div>
<div class="collaboration-content" style="display: none;">
<div class="collaboration-tools-compact" id={`domain-agnostic-tools-${sectionData.section.id}`}>
{sectionData.tools.map((tool: any) => {
const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
tool.projectUrl !== "" &&
tool.projectUrl.trim() !== "";
return (
<div class={`collaboration-tool-compact ${hasValidProjectUrl ? 'hosted' : tool.license !== 'Proprietary' ? 'oss' : ''}`}
onclick={`window.showToolDetails('${tool.name}')`}>
<div class="tool-compact-header">
<h4 style="margin: 0; font-size: 0.875rem; font-weight: 600;">{tool.name}</h4>
<div style="display: flex; gap: 0.25rem;">
{hasValidProjectUrl && <span class="badge badge--mini badge-primary">Self-Hosted</span>}
{tool.license !== 'Proprietary' && <span class="badge badge--mini badge-success">OSS</span>}
{tool.knowledgebase === true && <span class="badge badge--mini badge-error">Infos 📖</span>}
</div>
</div>
<p class="text-muted">
{tool.description}
</p>
<div style="display: flex; gap: 0.75rem; font-size: 0.6875rem; color: var(--color-text-secondary);">
<span>{tool.platforms.join(', ')}</span>
<span>•</span>
<span>{tool.skillLevel}</span>
</div>
</div>
</div>
<p class="text-muted">
{tool.description}
</p>
<div style="display: flex; gap: 0.75rem; font-size: 0.6875rem; color: var(--color-text-secondary);">
<span>{tool.platforms.join(', ')}</span>
<span>•</span>
<span>{tool.skillLevel}</span>
</div>
</div>
);
})}
);
})}
</div>
</div>
</div>
</div>
))}
<!-- DFIR Tools Matrix -->
<div id="dfir-matrix-section">
@@ -85,7 +92,7 @@ domains.forEach((domain: any) => {
<thead>
<tr>
<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>
))}
</tr>
@@ -94,7 +101,7 @@ domains.forEach((domain: any) => {
{domains.map((domain: any) => (
<tr data-domain={domain.id}>
<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}>
{matrix[domain.id][phase.id].map((tool: any) => {
const hasValidProjectUrl = tool.projectUrl !== undefined &&
@@ -122,7 +129,7 @@ domains.forEach((domain: any) => {
</div>
</div>
<!-- Tool Details Modal -->
<!-- Tool Details Modal stays the same -->
<div class="modal-overlay" id="modal-overlay" onclick="window.hideToolDetails()"></div>
<div class="tool-details" id="tool-details">
<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>
<!-- Updated button container for dual buttons -->
<div id="tool-links" style="display: flex; gap: 0.5rem; flex-direction: column;"></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
function getSelectedPhase() {
const activePhaseButton = document.querySelector('.phase-button.active');
@@ -200,34 +206,35 @@ domains.forEach((domain: any) => {
}
}
// Toggle collaboration section
function toggleCollaborationSection() {
const section = document.getElementById('collaboration-tools-section');
const content = document.querySelector('.collaboration-content');
const icon = document.querySelector('.collaboration-expand-icon svg');
if (!section || !content || !icon) return;
const isExpanded = section.classList.contains('collaboration-section-expanded');
if (isExpanded) {
// Collapse
section.classList.remove('collaboration-section-expanded');
section.classList.add('collaboration-section-collapsed');
content.style.display = 'none';
icon.style.transform = 'rotate(0deg)';
} else {
// Expand
section.classList.remove('collaboration-section-collapsed');
section.classList.add('collaboration-section-expanded');
content.style.display = 'block';
icon.style.transform = 'rotate(180deg)';
// Toggle domain-agnostic section
function toggleDomainAgnosticSection(sectionId) {
const section = document.getElementById(`domain-agnostic-section-${sectionId}`);
const content = section?.querySelector('.collaboration-content');
const icon = section?.querySelector('.collaboration-expand-icon svg');
if (!section || !content || !icon) return;
const isExpanded = section.classList.contains('collaboration-section-expanded');
if (isExpanded) {
// Collapse
section.classList.remove('collaboration-section-expanded');
section.classList.add('collaboration-section-collapsed');
content.style.display = 'none';
icon.style.transform = 'rotate(0deg)';
} else {
// Expand
section.classList.remove('collaboration-section-collapsed');
section.classList.add('collaboration-section-expanded');
content.style.display = 'block';
icon.style.transform = 'rotate(180deg)';
}
}
}
// Make function globally available
window.toggleCollaborationSection = toggleCollaborationSection;
// Helper function to create compact collaboration tool cards for matrix view
// Make functions globally available
window.toggleDomainAgnosticSection = toggleDomainAgnosticSection;
// Helper function to create compact tool cards
function createCollaborationToolCardCompact(tool) {
const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
@@ -261,7 +268,7 @@ window.toggleCollaborationSection = toggleCollaborationSection;
return cardDiv;
}
// Tool details functions
// Tool details functions (unchanged)
window.showToolDetails = function(toolName) {
const tool = toolsData.find(t => t.name === toolName);
if (!tool) return;
@@ -314,14 +321,12 @@ window.toggleCollaborationSection = toggleCollaborationSection;
</div>
`;
// Links - Updated to handle dual buttons for hosted tools AND knowledgebase links
// Links
const linksContainer = document.getElementById('tool-links');
let linksHTML = '';
// Main action buttons
if (hasValidProjectUrl) {
// Two buttons for tools we're hosting
linksHTML += `
<div style="display: flex; gap: 0.5rem;">
<a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
@@ -333,7 +338,6 @@ window.toggleCollaborationSection = toggleCollaborationSection;
</div>
`;
} else {
// Single button for tools we're not hosting
linksHTML += `
<a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
Software-Homepage
@@ -341,7 +345,6 @@ window.toggleCollaborationSection = toggleCollaborationSection;
`;
}
// Add knowledgebase link if available
if (tool.knowledgebase === true) {
const kbId = tool.name.toLowerCase().replace(/\s+/g, '-');
linksHTML += `
@@ -374,7 +377,6 @@ window.toggleCollaborationSection = toggleCollaborationSection;
window.addEventListener('viewChanged', (event) => {
const view = event.detail;
if (view === 'matrix') {
// Delay highlighting to ensure matrix is visible
setTimeout(updateMatrixHighlighting, 100);
}
});
@@ -393,56 +395,34 @@ window.toggleCollaborationSection = toggleCollaborationSection;
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
if (currentView === 'matrix') {
// Get selected phase from active button instead of dropdown
const selectedPhase = getSelectedPhase();
// Handle collaboration tools section
const collaborationSection = document.getElementById('collaboration-tools-section');
const dfirMatrixSection = document.getElementById('dfir-matrix-section');
const collaborationContainer = document.getElementById('collaboration-tools-container');
// Get all domain-agnostic phase IDs
const domainAgnosticPhaseIds = domainAgnosticSoftware.map(section => section.id);
if (selectedPhase === 'collaboration-general') {
// Show only collaboration tools, hide matrix
collaborationSection.style.display = 'block';
dfirMatrixSection.style.display = 'none';
// Check if selected phase is a domain-agnostic phase
const isDomainAgnosticPhase = domainAgnosticPhaseIds.includes(selectedPhase);
// Handle domain-agnostic sections
domainAgnosticSoftware.forEach(sectionData => {
const section = document.getElementById(`domain-agnostic-section-${sectionData.id}`);
const container = document.getElementById(`domain-agnostic-tools-${sectionData.id}`);
// Filter collaboration tools
const filteredCollaboration = filtered.filter(tool => (tool.phases || []).includes('collaboration-general'));
collaborationContainer.innerHTML = '';
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 (!isDomainAgnosticPhase) {
// Show matrix for regular phases
document.getElementById('dfir-matrix-section').style.display = 'block';
if (selectedPhase === '' || selectedPhase === null) {
// Show collaboration tools when no specific phase is selected
collaborationSection.style.display = 'block';
// Show all collaboration tools that pass general filters
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
// Clear and update matrix cells with ALL tools (based on domains × phases)
document.querySelectorAll('.matrix-cell').forEach(cell => {
cell.innerHTML = '';
});
// Re-populate with filtered DFIR tools - with safe array handling
const filteredDfirTools = filtered.filter(tool => !(tool.phases || []).includes('collaboration-general'));
filteredDfirTools.forEach(tool => {
// Re-populate with filtered tools based on domains × phases
filtered.forEach(tool => {
const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
tool.projectUrl !== "" &&
@@ -453,22 +433,19 @@ window.toggleCollaborationSection = toggleCollaborationSection;
domains.forEach(domain => {
phases.forEach(phase => {
if (phase !== 'collaboration-general') {
const cell = document.querySelector(`[data-domain="${domain}"][data-phase="${phase}"]`);
if (cell) {
const chip = document.createElement('span');
chip.className = `tool-chip ${hasValidProjectUrl ? 'tool-chip-hosted' : tool.license !== 'Proprietary' ? 'tool-chip-oss' : ''}`;
chip.setAttribute('title', `${tool.name}${tool.knowledgebase === true ? ' (KB verfügbar)' : ''}`);
chip.innerHTML = `${tool.name}${tool.knowledgebase === true ? '<span style="margin-left: 0.25rem; font-size: 0.6875rem;">📖</span>' : ''}`;
chip.onclick = () => window.showToolDetails(tool.name);
cell.appendChild(chip);
}
const cell = document.querySelector(`[data-domain="${domain}"][data-phase="${phase}"]`);
if (cell) {
const chip = document.createElement('span');
chip.className = `tool-chip ${hasValidProjectUrl ? 'tool-chip-hosted' : tool.license !== 'Proprietary' ? 'tool-chip-oss' : ''}`;
chip.setAttribute('title', `${tool.name}${tool.knowledgebase === true ? ' (KB verfügbar)' : ''}`);
chip.innerHTML = `${tool.name}${tool.knowledgebase === true ? '<span style="margin-left: 0.25rem; font-size: 0.6875rem;">📖</span>' : ''}`;
chip.onclick = () => window.showToolDetails(tool.name);
cell.appendChild(chip);
}
});
});
});
// Update highlighting after matrix content is updated
setTimeout(updateMatrixHighlighting, 50);
}
}