remove dev comments

This commit is contained in:
overcuriousity
2025-07-26 15:14:02 +02:00
parent 86d2370976
commit f24531d86d
34 changed files with 71 additions and 693 deletions

View File

@@ -1,7 +1,6 @@
---
import { getToolsData } from '../utils/dataService.js';
// Load tools data for validation
const data = await getToolsData();
const tools = data.tools;
@@ -9,7 +8,6 @@ const phases = data.phases;
const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
---
<!-- AI Query Interface -->
<section id="ai-interface" class="ai-interface" style="display: none;">
<div class="ai-query-section">
<div style="text-align: center; margin-bottom: 2rem;">
@@ -27,7 +25,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div>
<div class="ai-input-container" style="max-width: 800px; margin: 0 auto;">
<!-- Mode Toggle -->
<div class="ai-mode-toggle" style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.75rem; border: 1px solid var(--color-border);">
<span id="workflow-label" class="toggle-label active" style="font-weight: 500; color: var(--color-primary); cursor: pointer; transition: var(--transition-fast);">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;">
@@ -56,7 +53,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
maxlength="2000"
></textarea>
<!-- Privacy Notice -->
<div style="margin-top: 0.5rem; margin-bottom: 1rem;">
<p style="font-size: 0.75rem; color: var(--color-text-secondary); text-align: center; line-height: 1.4;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
@@ -82,8 +78,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div>
</div>
<!-- This should be your loading section in AIQueryInterface.astro -->
<!-- Loading State -->
<div id="ai-loading" class="ai-loading" style="display: none; text-align: center; padding: 2rem;">
<div style="display: inline-block; margin-bottom: 1rem;">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2" style="animation: pulse 2s ease-in-out infinite;">
@@ -94,7 +88,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div>
<p id="loading-text" style="color: var(--color-text-secondary);">Analysiere Szenario und generiere Empfehlungen...</p>
<!-- Queue Status Display - THIS SECTION SHOULD BE PRESENT -->
<div id="queue-status" style="margin-top: 1rem; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.5rem; border: 1px solid var(--color-border); display: none;">
<div style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
@@ -114,14 +107,12 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div>
</div>
<!-- Progress bar -->
<div style="margin-top: 1rem; background-color: var(--color-bg-tertiary); border-radius: 0.25rem; height: 4px; overflow: hidden;">
<div id="queue-progress" style="height: 100%; background-color: var(--color-primary); width: 0%; transition: width 0.3s ease;"></div>
</div>
</div>
</div>
<!-- Error State -->
<div id="ai-error" class="ai-error" style="display: none; text-align: center; padding: 2rem;">
<div style="background-color: var(--color-error); color: white; padding: 1rem; border-radius: 0.5rem; max-width: 600px; margin: 0 auto;">
<h3 style="margin-bottom: 0.5rem;">Fehler bei der KI-Anfrage</h3>
@@ -129,9 +120,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div>
</div>
<!-- AI Results -->
<div id="ai-results" class="ai-results" style="display: none;">
<!-- Results will be populated here by JavaScript -->
</div>
</section>
@@ -154,21 +143,19 @@ document.addEventListener('DOMContentLoaded', () => {
const aiResults = document.getElementById('ai-results');
const aiDescription = document.getElementById('ai-description');
// Toggle elements
const toggleSwitch = document.querySelector('.toggle-switch');
const toggleSlider = document.querySelector('.toggle-slider');
const workflowLabel = document.getElementById('workflow-label');
const toolLabel = document.getElementById('tool-label');
let currentRecommendation = null;
let currentMode = 'workflow'; // 'workflow' or 'tool'
let currentMode = 'workflow';
if (!aiInput || !aiSubmitBtn || !aiLoading || !aiError || !aiResults) {
console.error('AI interface elements not found');
return;
}
// Mode content configuration
const modeConfig = {
workflow: {
placeholder: "Beschreiben Sie Ihr forensisches Szenario... z.B. 'Verdacht auf Ransomware-Angriff auf Windows-Domänencontroller mit verschlüsselten Dateien und verdächtigen Netzwerkverbindungen'",
@@ -184,23 +171,17 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
// Update UI based on current mode
function updateModeUI() {
const config = modeConfig[currentMode];
// Update placeholder
aiInput.placeholder = config.placeholder;
// Update description
aiDescription.textContent = config.description;
// Update submit button text
submitBtnText.textContent = config.submitText;
// Update loading text
loadingText.textContent = config.loadingText;
// Update toggle visual state
if (currentMode === 'workflow') {
toggleSlider.style.transform = 'translateX(0)';
toggleSwitch.style.backgroundColor = 'var(--color-primary)';
@@ -217,13 +198,11 @@ document.addEventListener('DOMContentLoaded', () => {
workflowLabel.classList.remove('active');
}
// Clear previous results when switching modes
aiResults.style.display = 'none';
aiError.style.display = 'none';
currentRecommendation = null;
}
// Toggle mode handlers
function switchToMode(mode) {
if (currentMode !== mode) {
currentMode = mode;
@@ -243,7 +222,6 @@ document.addEventListener('DOMContentLoaded', () => {
switchToMode('tool');
});
// Character counter for input
const updateCharacterCount = () => {
const length = aiInput.value.length;
const maxLength = 2000;
@@ -267,7 +245,6 @@ document.addEventListener('DOMContentLoaded', () => {
aiInput.addEventListener('input', updateCharacterCount);
updateCharacterCount();
// Submit handler with enhanced queue feedback
const handleSubmit = async () => {
const query = aiInput.value.trim();
@@ -281,15 +258,12 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
// Generate task ID for tracking
const taskId = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
// Hide previous results and errors
aiResults.style.display = 'none';
aiError.style.display = 'none';
aiLoading.style.display = 'block';
// Show queue status section
const queueStatus = document.getElementById('queue-status');
const taskIdDisplay = document.getElementById('current-task-id');
if (queueStatus && taskIdDisplay) {
@@ -297,11 +271,9 @@ document.addEventListener('DOMContentLoaded', () => {
taskIdDisplay.textContent = taskId;
}
// Disable submit button
aiSubmitBtn.disabled = true;
submitBtnText.textContent = currentMode === 'workflow' ? 'Generiere Empfehlungen...' : 'Suche passende Methode...';
// Start queue status polling
let statusInterval;
let startTime = Date.now();
@@ -329,14 +301,12 @@ document.addEventListener('DOMContentLoaded', () => {
if (positionBadge && data.currentPosition) {
positionBadge.textContent = data.currentPosition;
// Update progress bar (inverse of position)
if (progressBar && data.queueLength > 0) {
const progress = Math.max(0, ((data.queueLength - data.currentPosition + 1) / data.queueLength) * 100);
progressBar.style.width = `${progress}%`;
}
}
// If processing and no position (request is being handled)
if (data.isProcessing && !data.currentPosition) {
if (positionBadge) positionBadge.textContent = '⚡';
if (progressBar) progressBar.style.width = '100%';
@@ -348,10 +318,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
// Initial status update
updateQueueStatus();
// Poll every 500ms for status updates
statusInterval = setInterval(updateQueueStatus, 500);
try {
@@ -363,13 +331,12 @@ document.addEventListener('DOMContentLoaded', () => {
body: JSON.stringify({
query,
mode: currentMode,
taskId // Include task ID for backend tracking
taskId
})
});
const data = await response.json();
// Clear status polling
if (statusInterval) clearInterval(statusInterval);
if (!response.ok) {
@@ -380,10 +347,8 @@ document.addEventListener('DOMContentLoaded', () => {
throw new Error(data.error || 'Unknown error');
}
// Store recommendation for restoration
currentRecommendation = data.recommendation;
// Display results based on mode
if (currentMode === 'workflow') {
displayWorkflowResults(data.recommendation, query);
} else {
@@ -396,13 +361,11 @@ document.addEventListener('DOMContentLoaded', () => {
} catch (error) {
console.error('AI query failed:', error);
// Clear status polling
if (statusInterval) clearInterval(statusInterval);
aiLoading.style.display = 'none';
aiError.style.display = 'block';
// Show user-friendly error messages
if (error.message.includes('429')) {
aiErrorMessage.textContent = 'Zu viele Anfragen. Bitte warten Sie einen Moment und versuchen Sie es erneut.';
} else if (error.message.includes('401')) {
@@ -413,7 +376,6 @@ document.addEventListener('DOMContentLoaded', () => {
aiErrorMessage.textContent = `Fehler: ${error.message}`;
}
} finally {
// Re-enable submit button and hide queue status
aiSubmitBtn.disabled = false;
const config = modeConfig[currentMode];
submitBtnText.textContent = config.submitText;
@@ -423,7 +385,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
// Event listeners
aiSubmitBtn.addEventListener('click', handleSubmit);
aiInput.addEventListener('keydown', (e) => {
@@ -433,7 +394,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// Function to restore AI results when switching back to AI view
window.restoreAIResults = () => {
if (currentRecommendation) {
aiResults.style.display = 'block';
@@ -442,7 +402,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
// Helper function to format workflow suggestions as proper lists
function formatWorkflowSuggestion(text) {
const numberedListPattern = /(\d+\.\s)/g;
@@ -485,7 +444,6 @@ document.addEventListener('DOMContentLoaded', () => {
return `<p style="margin: 0; line-height: 1.6; color: var(--color-text);">${text}</p>`;
}
// NEW: Function to render background knowledge concepts
function renderBackgroundKnowledge(backgroundKnowledge) {
if (!backgroundKnowledge || backgroundKnowledge.length === 0) {
return '';
@@ -523,9 +481,7 @@ document.addEventListener('DOMContentLoaded', () => {
`;
}
// Display results for workflow mode (ENHANCED with concepts)
function displayWorkflowResults(recommendation, originalQuery) {
// Group tools by phase
const toolsByPhase = {};
const phaseOrder = phases.map(phase => phase.id);
@@ -534,12 +490,10 @@ document.addEventListener('DOMContentLoaded', () => {
return acc;
}, {});
// Initialize phases
phaseOrder.forEach(phase => {
toolsByPhase[phase] = [];
});
// Group recommended tools by phase
recommendation.recommended_tools?.forEach(recTool => {
if (toolsByPhase[recTool.phase]) {
const fullTool = tools.find(t => t.name === recTool.name);
@@ -682,7 +636,6 @@ document.addEventListener('DOMContentLoaded', () => {
aiResults.appendChild(tempDiv);
}
// Display results for tool-specific mode (ENHANCED with concepts)
function displayToolResults(recommendation, originalQuery) {
function getSuitabilityText(score) {
const suitabilityTexts = {
@@ -693,7 +646,6 @@ document.addEventListener('DOMContentLoaded', () => {
return suitabilityTexts[score] || 'GEEIGNET';
}
// Helper function to get phase names for a tool
function getToolPhases(tool) {
if (!tool.phases || tool.phases.length === 0) return '';
@@ -900,7 +852,6 @@ document.addEventListener('DOMContentLoaded', () => {
aiResults.appendChild(tempDiv);
}
// Initialize UI
updateModeUI();
});
</script>

View File

@@ -18,7 +18,6 @@ const {
style = ''
} = Astro.props;
// Generate appropriate URLs and text based on type
let href: string;
let defaultText: string;
let icon: string;

View File

@@ -17,27 +17,27 @@ const currentPath = Astro.url.pathname;
<ul class="nav-links">
<li>
<a href="/" class={`nav-link ${currentPath === '/' ? 'active' : ''}`}>
~/
Start
</a>
</li>
<li>
<a href="/knowledgebase" class={`nav-link ${currentPath === '/knowledgebase' ? 'active' : ''}`}>
~/knowledgebase
Knowledgebase
</a>
</li>
<li>
<a href="/contribute" class={`nav-link ${currentPath.startsWith('/contribute') ? 'active' : ''}`}>
~/contribute
Beitragen
</a>
</li>
<li>
<a href="/status" class={`nav-link ${currentPath === '/status' ? 'active' : ''}`}>
~/status
Status
</a>
</li>
<li>
<a href="/about" class={`nav-link ${currentPath === '/about' ? 'active' : ''}`}>
~/about
Über das Projekt
</a>
</li>
<li>

View File

@@ -9,7 +9,6 @@ export interface Props {
const { toolName, context, size = 'small' } = Astro.props;
// AFTER: Single line with centralized function
const toolSlug = createToolSlug(toolName);
const iconSize = size === 'small' ? '14' : '16';

View File

@@ -24,26 +24,21 @@ export interface Props {
const { tool } = Astro.props;
// Check types
const isMethod = tool.type === 'method';
const isConcept = tool.type === 'concept';
// Check if tool has a valid project URL (means we're hosting it)
const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
tool.projectUrl !== "" &&
tool.projectUrl.trim() !== "";
// Check if tool has knowledgebase entry
const hasKnowledgebase = tool.knowledgebase === true;
// Determine card styling based on type and hosting status
const cardClass = isConcept ? 'card card-concept tool-card cursor-pointer' :
isMethod ? 'card card-method tool-card cursor-pointer' :
hasValidProjectUrl ? 'card card-hosted tool-card cursor-pointer' :
(tool.license !== 'Proprietary' ? 'card card-oss tool-card cursor-pointer' : 'card tool-card cursor-pointer');
// ENHANCED: Data attributes for filtering
const toolDataAttributes = {
'data-tool-name': tool.name.toLowerCase(),
'data-tool-type': tool.type,
@@ -62,26 +57,22 @@ const toolDataAttributes = {
{...toolDataAttributes}
onclick={`window.showToolDetails('${tool.name}')`}
>
<!-- Card Header with Fixed Height -->
<div class="tool-card-header">
<h3>
{tool.icon && <span class="mr-2 text-lg">{tool.icon}</span>}
{tool.name}
</h3>
<div class="tool-card-badges">
<!-- Only show CC24-Server and Knowledgebase badges -->
{!isMethod && hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
{hasKnowledgebase && <span class="badge badge-error">📖</span>}
<ShareButton toolName={tool.name} context="card" size="small" />
</div>
</div>
<!-- Description - Truncated to 2 lines -->
<p class="text-muted">
{tool.description}
</p>
<!-- Metadata - Compact Icons with Better Alignment -->
<div class="tool-card-metadata flex items-center gap-4 mb-3" style="line-height: 1;">
<div class="metadata-item flex items-center gap-2 text-xs text-secondary flex-shrink-1 min-w-0">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="flex-shrink-0">
@@ -115,14 +106,12 @@ const toolDataAttributes = {
</div>
</div>
<!-- Tags - Two Lines with Fade -->
<div class="tool-tags-container">
{tool.tags.slice(0, 8).map(tag => (
<span class="tag">{tag}</span>
))}
</div>
<!-- Buttons - Fixed at Bottom (NO EDIT BUTTONS - Available in modals) -->
<div class="tool-card-buttons" onclick="event.stopPropagation();">
{isConcept ? (
<a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary single-button" style="background-color: var(--color-concept); border-color: var(--color-concept);">

View File

@@ -1,13 +1,11 @@
---
import { getToolsData } from '../utils/dataService.js';
// Load tools data
const data = await getToolsData();
const domains = data.domains;
const phases = data.phases;
// Get unique tags from all tools with frequency count
const tagFrequency = data.tools.reduce((acc: Record<string, number>, tool: any) => {
tool.tags.forEach((tag: string) => {
acc[tag] = (acc[tag] || 0) + 1;
@@ -15,14 +13,12 @@ const tagFrequency = data.tools.reduce((acc: Record<string, number>, tool: any)
return acc;
}, {});
// Sort tags by frequency (descending)
const sortedTags = Object.entries(tagFrequency)
.sort(([,a], [,b]) => (b as number) - (a as number))
.map(([tag]) => tag);
---
<div class="filters-container">
<!-- Search Bar -->
<div style="margin-bottom: 1.5rem;">
<input
type="text"
@@ -32,9 +28,7 @@ const sortedTags = Object.entries(tagFrequency)
/>
</div>
<!-- Domain Dropdown and Phase Buttons -->
<div class="domain-phase-container" style="margin-bottom: 1.5rem;">
<!-- Domain Selection -->
<div class="domain-section">
<label for="domain-select" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">
Forensische Domäne
@@ -47,7 +41,6 @@ const sortedTags = Object.entries(tagFrequency)
</select>
</div>
<!-- Phase Selection Buttons -->
<div class="phase-section">
<label style="display: block; margin-bottom: 0.75rem; font-weight: 500;">
Untersuchungsphase
@@ -66,14 +59,12 @@ const sortedTags = Object.entries(tagFrequency)
</div>
</div>
<!-- Additional Filters -->
<div style="margin-bottom: 1.5rem;">
<div class="checkbox-wrapper" style="margin-bottom: 1rem;">
<input type="checkbox" id="include-proprietary" checked />
<label for="include-proprietary">Proprietäre Software mit einschließen</label>
</div>
<!-- Tag Cloud -->
<div style="margin-bottom: 1rem;">
<div class="tag-header">
<label style="font-weight: 500;">
@@ -103,12 +94,10 @@ const sortedTags = Object.entries(tagFrequency)
</div>
</div>
<!-- View Toggle -->
<div style="display: flex; gap: 1rem; margin-bottom: 1.5rem; align-items: center;">
<button class="btn btn-secondary view-toggle active" style="height:50px" data-view="grid">Kachelansicht</button>
<button class="btn btn-secondary view-toggle" style="height:50px" data-view="matrix">Matrix-Ansicht</button>
<!-- AI Recommendations Button (only visible when authenticated) -->
<button
id="ai-view-toggle"
class="btn btn-secondary view-toggle"
@@ -125,10 +114,8 @@ const sortedTags = Object.entries(tagFrequency)
</div>
<script define:vars={{ toolsData: data.tools, tagFrequency, sortedTags }}>
// Store tools data globally for filtering
window.toolsData = toolsData;
// Initialize filters
document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('search-input');
const domainSelect = document.getElementById('domain-select');
@@ -140,12 +127,10 @@ const sortedTags = Object.entries(tagFrequency)
const viewToggles = document.querySelectorAll('.view-toggle');
const aiViewToggle = document.getElementById('ai-view-toggle');
// Track selected tags and phase
let selectedTags = new Set();
let selectedPhase = '';
let isTagCloudExpanded = false;
// Initialize tag cloud state
function initTagCloud() {
const visibleCount = 22;
tagCloudItems.forEach((item, index) => {
@@ -155,7 +140,6 @@ const sortedTags = Object.entries(tagFrequency)
});
}
// Toggle tag cloud expansion
function toggleTagCloud() {
isTagCloudExpanded = !isTagCloudExpanded;
const visibleCount = 22;
@@ -189,7 +173,6 @@ const sortedTags = Object.entries(tagFrequency)
}
}
// Filter tag cloud based on search input
function filterTagCloud() {
const searchTerm = searchInput.value.toLowerCase();
let visibleCount = 0;
@@ -219,7 +202,6 @@ const sortedTags = Object.entries(tagFrequency)
tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
}
// Check if tool is hosted (has valid projectUrl)
function isToolHosted(tool) {
return tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
@@ -227,12 +209,10 @@ const sortedTags = Object.entries(tagFrequency)
tool.projectUrl.trim() !== "";
}
// Check if item is a method
function isMethod(tool) {
return tool.type === 'method';
}
// Update matrix highlighting based on current filters
function updateMatrixHighlighting() {
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
if (currentView !== 'matrix') return;
@@ -240,14 +220,12 @@ const sortedTags = Object.entries(tagFrequency)
const matrixTable = document.querySelector('.matrix-table');
if (!matrixTable) return;
// Clear existing highlights
matrixTable.querySelectorAll('.highlight-row, .highlight-column').forEach(el => {
el.classList.remove('highlight-row', 'highlight-column');
});
const selectedDomain = domainSelect.value;
// Highlight selected domain row
if (selectedDomain) {
const domainRow = matrixTable.querySelector(`tr[data-domain="${selectedDomain}"]`);
if (domainRow) {
@@ -255,7 +233,6 @@ const sortedTags = Object.entries(tagFrequency)
}
}
// Highlight selected phase column
if (selectedPhase) {
const phaseHeaders = matrixTable.querySelectorAll('thead th[data-phase]');
const phaseIndex = Array.from(phaseHeaders).findIndex(th =>
@@ -263,7 +240,7 @@ const sortedTags = Object.entries(tagFrequency)
);
if (phaseIndex >= 0) {
const columnIndex = phaseIndex + 1; // +1 because first column is domain names
const columnIndex = phaseIndex + 1;
matrixTable.querySelectorAll(`tr`).forEach(row => {
const cell = row.children[columnIndex];
if (cell) {
@@ -274,19 +251,16 @@ const sortedTags = Object.entries(tagFrequency)
}
}
// ENHANCED: Filter function with better performance for show/hide pattern
function filterTools() {
const searchTerm = searchInput.value.toLowerCase();
const selectedDomain = domainSelect.value;
const includeProprietary = proprietaryCheckbox.checked;
const filtered = window.toolsData.filter(tool => {
// Ensure arrays exist with fallbacks
const domains = tool.domains || [];
const phases = tool.phases || [];
const tags = tool.tags || [];
// Search filter - more comprehensive
if (searchTerm && !(
tool.name.toLowerCase().includes(searchTerm) ||
tool.description.toLowerCase().includes(searchTerm) ||
@@ -295,22 +269,18 @@ const sortedTags = Object.entries(tagFrequency)
return false;
}
// Domain filter
if (selectedDomain && !domains.includes(selectedDomain)) {
return false;
}
// Phase filter
if (selectedPhase && !phases.includes(selectedPhase)) {
return false;
}
// Proprietary filter (skip for methods and concepts since they don't have licenses)
if (!includeProprietary && !isMethod(tool) && tool.type !== 'concept' && tool.license === 'Proprietary') {
return false;
}
// Tag filter - ensure all selected tags are present
if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => tags.includes(tag))) {
return false;
}
@@ -318,14 +288,11 @@ const sortedTags = Object.entries(tagFrequency)
return true;
});
// Update matrix highlighting
updateMatrixHighlighting();
// Emit custom event with filtered results
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: filtered }));
}
// Handle tag cloud clicks
function handleTagClick(tagItem) {
const tag = tagItem.getAttribute('data-tag');
@@ -340,7 +307,6 @@ const sortedTags = Object.entries(tagFrequency)
filterTools();
}
// Handle phase button clicks
function handlePhaseClick(button) {
const phase = button.getAttribute('data-phase');
@@ -356,7 +322,6 @@ const sortedTags = Object.entries(tagFrequency)
filterTools();
}
// View toggle handler
function handleViewToggle(view) {
viewToggles.forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-view') === view);
@@ -372,14 +337,12 @@ const sortedTags = Object.entries(tagFrequency)
}
}
// Clear all tag filters function
function clearTagFilters() {
selectedTags.clear();
tagCloudItems.forEach(item => item.classList.remove('active'));
filterTools();
}
// Clear all filters function
function clearAllFilters() {
searchInput.value = '';
domainSelect.value = '';
@@ -389,7 +352,6 @@ const sortedTags = Object.entries(tagFrequency)
filterTagCloud();
}
// Event listeners
searchInput.addEventListener('input', () => {
filterTagCloud();
filterTools();
@@ -411,15 +373,12 @@ const sortedTags = Object.entries(tagFrequency)
btn.addEventListener('click', () => handleViewToggle(btn.getAttribute('data-view')));
});
// Expose functions globally for potential use
window.clearTagFilters = clearTagFilters;
window.clearAllFilters = clearAllFilters;
// Initialize
initTagCloud();
filterTagCloud();
// Delay initial filter to ensure all event listeners are ready
setTimeout(() => {
filterTools();
}, 100);

View File

@@ -2,7 +2,6 @@
import { getToolsData } from '../utils/dataService.js';
import ShareButton from './ShareButton.astro';
// Load tools data
const data = await getToolsData();
const domains = data.domains;
@@ -10,7 +9,6 @@ const phases = data.phases;
const tools = data.tools;
const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
// 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) =>
@@ -18,13 +16,12 @@ const domainAgnosticTools = domainAgnosticSoftware.map((section: any) => ({
)
}));
// 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.forEach((phase: any) => {
matrix[domain.id][phase.id] = tools.filter((tool: any) =>
tool.type !== 'concept' && // Exclude concepts from matrix
tool.type !== 'concept' &&
tool.domains && tool.domains.includes(domain.id) &&
tool.phases && tool.phases.includes(phase.id)
);
@@ -33,7 +30,6 @@ domains.forEach((domain: any) => {
---
<div id="matrix-container" class="matrix-wrapper" style="display: none;">
<!-- Domain-Agnostic Software Sections -->
{domainAgnosticTools.map((sectionData: any, index: number) => (
<div id={`domain-agnostic-section-${sectionData.section.id}`} class="card collaboration-section-collapsed mb-6 border-l-4" style="border-left-color: var(--color-accent);">
<div class="collaboration-header cursor-pointer flex items-center gap-3" onclick={`toggleDomainAgnosticSection('${sectionData.section.id}')`} style="margin-bottom: 0.1rem;">
@@ -91,7 +87,6 @@ domains.forEach((domain: any) => {
</div>
))}
<!-- DFIR Tools Matrix -->
<div id="dfir-matrix-section">
<h2 class="mb-4" style="color: var(--color-text);">MATRIX</h2>
<table class="matrix-table">
@@ -139,19 +134,15 @@ domains.forEach((domain: any) => {
</div>
</div>
<!-- Tool Details Modals - Dual Modal System -->
<div class="modal-overlay" id="modal-overlay" onclick="window.hideAllToolDetails()"></div>
<!-- Primary Modal -->
<div class="tool-details" id="tool-details-primary">
<div class="flex justify-between items-start mb-4">
<h2 id="tool-name-primary" class="m-0">Tool Name</h2>
<div class="flex items-center gap-2">
<div id="share-button-primary" style="display: none;">
<!-- Share button will be populated by JavaScript -->
</div>
<div id="contribute-button-primary" style="display: none;">
<!-- Contribution button will be populated by JavaScript -->
</div>
<button class="btn-icon" onclick="window.hideToolDetails('primary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -173,16 +164,13 @@ domains.forEach((domain: any) => {
<div id="tool-links-primary" class="flex flex-col gap-2"></div>
</div>
<!-- Secondary Modal -->
<div class="tool-details" id="tool-details-secondary">
<div class="flex justify-between items-start mb-4">
<h2 id="tool-name-secondary" class="m-0">Tool Name</h2>
<div class="flex items-center gap-2">
<div id="share-button-secondary" style="display: none;">
<!-- Share button will be populated by JavaScript -->
</div>
<div id="contribute-button-secondary" style="display: none;">
<!-- Contribution button will be populated by JavaScript -->
</div>
<button class="btn-icon" onclick="window.hideToolDetails('secondary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -205,24 +193,20 @@ domains.forEach((domain: any) => {
</div>
<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');
return activePhaseButton ? activePhaseButton.getAttribute('data-phase') : '';
}
// Helper function to get selected domain from dropdown
function getSelectedDomain() {
const domainSelect = document.getElementById('domain-select');
return domainSelect ? domainSelect.value : '';
}
// Update matrix highlighting based on current filters
function updateMatrixHighlighting() {
const matrixTable = document.querySelector('.matrix-table');
if (!matrixTable) return;
// Clear existing highlights
matrixTable.querySelectorAll('.highlight-row, .highlight-column').forEach(el => {
el.classList.remove('highlight-row', 'highlight-column');
});
@@ -230,7 +214,6 @@ domains.forEach((domain: any) => {
const selectedDomain = getSelectedDomain();
const selectedPhase = getSelectedPhase();
// Highlight selected domain row
if (selectedDomain) {
const domainRow = matrixTable.querySelector(`tr[data-domain="${selectedDomain}"]`);
if (domainRow) {
@@ -238,7 +221,6 @@ domains.forEach((domain: any) => {
}
}
// Highlight selected phase column
if (selectedPhase) {
const phaseHeaders = matrixTable.querySelectorAll('thead th[data-phase]');
const phaseIndex = Array.from(phaseHeaders).findIndex(th =>
@@ -246,7 +228,7 @@ domains.forEach((domain: any) => {
);
if (phaseIndex >= 0) {
const columnIndex = phaseIndex + 1; // +1 because first column is domain names
const columnIndex = phaseIndex + 1;
matrixTable.querySelectorAll(`tr`).forEach(row => {
const cell = row.children[columnIndex];
if (cell) {
@@ -257,7 +239,6 @@ domains.forEach((domain: any) => {
}
}
// Toggle domain-agnostic section
function toggleDomainAgnosticSection(sectionId) {
const section = document.getElementById(`domain-agnostic-section-${sectionId}`);
const content = section?.querySelector('.collaboration-content');
@@ -268,13 +249,11 @@ domains.forEach((domain: any) => {
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';
@@ -282,7 +261,6 @@ domains.forEach((domain: any) => {
}
}
// Generate share URLs
function generateShareURL(toolName, view, modal = null) {
const toolSlug = window.createToolSlug(toolName);
const baseUrl = window.location.origin + window.location.pathname;
@@ -295,12 +273,10 @@ domains.forEach((domain: any) => {
return `${baseUrl}?${params.toString()}`;
}
// Copy to clipboard with feedback
async function copyToClipboard(text, button) {
try {
await navigator.clipboard.writeText(text);
// Show feedback
const originalHTML = button.innerHTML;
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20,6 9,17 4,12"/></svg> Kopiert!';
button.style.color = 'var(--color-accent)';
@@ -310,7 +286,6 @@ domains.forEach((domain: any) => {
button.style.color = '';
}, 2000);
} catch (err) {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
@@ -318,7 +293,6 @@ domains.forEach((domain: any) => {
document.execCommand('copy');
document.body.removeChild(textArea);
// Show feedback
const originalHTML = button.innerHTML;
button.innerHTML = 'Kopiert!';
setTimeout(() => {
@@ -327,12 +301,10 @@ domains.forEach((domain: any) => {
}
}
// Show share dialog
window.showShareDialog = function(shareButton) {
const toolName = shareButton.getAttribute('data-tool-name');
const context = shareButton.getAttribute('data-context');
// Create modal backdrop
let backdrop = document.getElementById('share-modal-backdrop');
if (!backdrop) {
backdrop = document.createElement('div');
@@ -354,7 +326,6 @@ domains.forEach((domain: any) => {
document.body.appendChild(backdrop);
}
// Create share dialog
const dialog = document.createElement('div');
dialog.style.cssText = `
background-color: var(--color-bg);
@@ -440,13 +411,11 @@ domains.forEach((domain: any) => {
backdrop.appendChild(dialog);
// Show with animation
requestAnimationFrame(() => {
backdrop.style.opacity = '1';
dialog.style.transform = 'scale(1)';
});
// Event handlers
const closeDialog = () => {
backdrop.style.opacity = '0';
dialog.style.transform = 'scale(0.9)';
@@ -463,7 +432,6 @@ domains.forEach((domain: any) => {
document.getElementById('close-share-dialog').addEventListener('click', closeDialog);
// Share option handlers
dialog.querySelectorAll('.share-option-btn').forEach(btn => {
btn.addEventListener('click', () => {
const url = btn.getAttribute('data-url');
@@ -472,10 +440,8 @@ domains.forEach((domain: any) => {
});
};
// Make functions globally available
window.toggleDomainAgnosticSection = toggleDomainAgnosticSection;
// Enhanced modal system for side-by-side display
window.showToolDetails = function(toolName, modalType = 'primary') {
const tool = toolsData.find(t => t.name === toolName);
if (!tool) {
@@ -486,7 +452,6 @@ domains.forEach((domain: any) => {
const isMethod = tool.type === 'method';
const isConcept = tool.type === 'concept';
// Get modal-specific element IDs
const elements = {
name: document.getElementById(`tool-name-${modalType}`),
description: document.getElementById(`tool-description-${modalType}`),
@@ -496,7 +461,6 @@ domains.forEach((domain: any) => {
links: document.getElementById(`tool-links-${modalType}`)
};
// Check if all elements exist
for (const [key, element] of Object.entries(elements)) {
if (!element) {
console.error(`Element not found: tool-${key}-${modalType}`);
@@ -504,12 +468,10 @@ domains.forEach((domain: any) => {
}
}
// Update modal content
const iconHtml = tool.icon ? `<span class="mr-3 text-xl">${tool.icon}</span>` : '';
elements.name.innerHTML = `${iconHtml}${tool.name}`;
elements.description.textContent = tool.description;
// Badges
const hasValidProjectUrl = window.isToolHosted(tool);
elements.badges.innerHTML = '';
@@ -525,7 +487,6 @@ domains.forEach((domain: any) => {
elements.badges.innerHTML += '<span class="badge badge-error">📖</span>';
}
// Metadata
const domains = tool.domains || [];
const phases = tool.phases || [];
const domainsText = domains.length > 0 ? domains.join(', ') : 'Domain-agnostic';
@@ -551,7 +512,6 @@ domains.forEach((domain: any) => {
elements.metadata.innerHTML = metadataHTML;
// Tags and Related Concepts
const tags = tool.tags || [];
let tagsHTML = `
<div class="flex flex-wrap gap-1">
@@ -559,7 +519,6 @@ domains.forEach((domain: any) => {
</div>
`;
// Related Concepts section - only show in primary modal to avoid infinite loops
const relatedConcepts = tool.related_concepts || [];
if (relatedConcepts.length > 0 && modalType === 'primary') {
const conceptLinks = relatedConcepts.map(conceptName => {
@@ -575,7 +534,6 @@ domains.forEach((domain: any) => {
return `<span class="tag" style="background-color: var(--color-bg-tertiary); color: var(--color-text-secondary);">${conceptName}</span>`;
}).join('');
// Check if mobile device
const isMobile = window.innerWidth <= 768;
const collapseOnMobile = isMobile && relatedConcepts.length > 2;
@@ -600,7 +558,6 @@ domains.forEach((domain: any) => {
elements.tags.innerHTML = tagsHTML;
// Links
let linksHTML = '';
if (isConcept) {
@@ -652,7 +609,6 @@ domains.forEach((domain: any) => {
elements.links.innerHTML = linksHTML;
// ===== POPULATE SHARE BUTTON =====
const shareButtonContainer = document.getElementById(`share-button-${modalType}`);
if (shareButtonContainer) {
const toolSlug = window.createToolSlug(tool.name);
@@ -676,7 +632,6 @@ domains.forEach((domain: any) => {
shareButtonContainer.style.display = 'block';
}
// ===== POPULATE CONTRIBUTION BUTTON =====
const contributeButtonContainer = document.getElementById(`contribute-button-${modalType}`);
if (contributeButtonContainer) {
contributeButtonContainer.innerHTML = `
@@ -696,7 +651,6 @@ domains.forEach((domain: any) => {
contributeButtonContainer.style.display = 'block';
}
// Show modals and update layout
const overlay = document.getElementById('modal-overlay');
const primaryModal = document.getElementById('tool-details-primary');
const secondaryModal = document.getElementById('tool-details-secondary');
@@ -705,7 +659,6 @@ domains.forEach((domain: any) => {
if (modalType === 'primary' && primaryModal) primaryModal.classList.add('active');
if (modalType === 'secondary' && secondaryModal) secondaryModal.classList.add('active');
// Check if both modals are now active
const primaryActive = primaryModal && primaryModal.classList.contains('active');
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
@@ -749,7 +702,6 @@ domains.forEach((domain: any) => {
if (contributeButtonSecondary) contributeButtonSecondary.style.display = 'none';
}
// Check if any modal is still active
const primaryActive = primaryModal && primaryModal.classList.contains('active');
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
@@ -757,7 +709,6 @@ domains.forEach((domain: any) => {
if (overlay) overlay.classList.remove('active');
document.body.classList.remove('modals-side-by-side');
} else if (primaryActive !== secondaryActive) {
// Only one modal left - remove side-by-side layout
document.body.classList.remove('modals-side-by-side');
}
};
@@ -766,7 +717,6 @@ domains.forEach((domain: any) => {
window.hideToolDetails('both');
};
// Keep existing event listeners
window.addEventListener('viewChanged', (event) => {
const view = event.detail;
if (view === 'matrix') {