add knowledgebase
This commit is contained in:
		
							parent
							
								
									d18cc060e5
								
							
						
					
					
						commit
						a057120d7a
					
				
							
								
								
									
										124
									
								
								deploy-static.sh
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								deploy-static.sh
									
									
									
									
									
								
							@ -1,124 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# CC24-Hub - Node.js Deployment Script
 | 
			
		||||
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
PROJECT_DIR="/var/www/dfir-tools-hub"
 | 
			
		||||
SERVICE_NAME="dfir-tools-hub"
 | 
			
		||||
SERVICE_PORT="3000"
 | 
			
		||||
 | 
			
		||||
echo "🔧 Setting up Node.js deployment..."
 | 
			
		||||
 | 
			
		||||
cd $PROJECT_DIR
 | 
			
		||||
 | 
			
		||||
# Install PM2 globally if not present
 | 
			
		||||
if ! command -v pm2 &> /dev/null; then
 | 
			
		||||
    sudo npm install -g pm2
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Install dependencies
 | 
			
		||||
npm ci --production
 | 
			
		||||
 | 
			
		||||
# Update astro.config.mjs for Node.js adapter
 | 
			
		||||
cat > astro.config.mjs << 'EOF'
 | 
			
		||||
import { defineConfig } from 'astro/config';
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  output: 'server',
 | 
			
		||||
  adapter: '@astrojs/node',
 | 
			
		||||
  server: {
 | 
			
		||||
    port: 3000,
 | 
			
		||||
    host: '127.0.0.1'
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
# Install Node.js adapter
 | 
			
		||||
npm install @astrojs/node
 | 
			
		||||
 | 
			
		||||
# Build for Node.js
 | 
			
		||||
npm run build
 | 
			
		||||
 | 
			
		||||
# Create PM2 ecosystem file
 | 
			
		||||
cat > ecosystem.config.js << EOF
 | 
			
		||||
module.exports = {
 | 
			
		||||
  apps: [{
 | 
			
		||||
    name: '$SERVICE_NAME',
 | 
			
		||||
    script: './dist/server/entry.mjs',
 | 
			
		||||
    instances: 'max',
 | 
			
		||||
    exec_mode: 'cluster',
 | 
			
		||||
    env: {
 | 
			
		||||
      NODE_ENV: 'production',
 | 
			
		||||
      PORT: $SERVICE_PORT,
 | 
			
		||||
      HOST: '127.0.0.1'
 | 
			
		||||
    },
 | 
			
		||||
    error_file: '/var/log/pm2/$SERVICE_NAME-error.log',
 | 
			
		||||
    out_file: '/var/log/pm2/$SERVICE_NAME-out.log',
 | 
			
		||||
    log_file: '/var/log/pm2/$SERVICE_NAME.log',
 | 
			
		||||
    time: true,
 | 
			
		||||
    max_memory_restart: '1G'
 | 
			
		||||
  }]
 | 
			
		||||
};
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
# Create log directory
 | 
			
		||||
sudo mkdir -p /var/log/pm2
 | 
			
		||||
sudo chown -R $(whoami):$(whoami) /var/log/pm2
 | 
			
		||||
 | 
			
		||||
# Start/restart with PM2
 | 
			
		||||
pm2 delete $SERVICE_NAME 2>/dev/null || true
 | 
			
		||||
pm2 start ecosystem.config.js
 | 
			
		||||
pm2 save
 | 
			
		||||
pm2 startup
 | 
			
		||||
 | 
			
		||||
echo "🔧 Configuring nginx reverse proxy..."
 | 
			
		||||
 | 
			
		||||
# Create nginx configuration for Node.js
 | 
			
		||||
sudo tee /etc/nginx/sites-available/$SERVICE_NAME << EOF
 | 
			
		||||
upstream dfir_backend {
 | 
			
		||||
    server 127.0.0.1:$SERVICE_PORT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
server {
 | 
			
		||||
    listen 80;
 | 
			
		||||
    server_name dfir-tools.yourdomain.com;  # Replace with your domain
 | 
			
		||||
    
 | 
			
		||||
    # Security headers
 | 
			
		||||
    add_header X-Frame-Options "SAMEORIGIN" always;
 | 
			
		||||
    add_header X-Content-Type-Options "nosniff" always;
 | 
			
		||||
    add_header X-XSS-Protection "1; mode=block" always;
 | 
			
		||||
    
 | 
			
		||||
    # Proxy to Node.js application
 | 
			
		||||
    location / {
 | 
			
		||||
        proxy_pass http://dfir_backend;
 | 
			
		||||
        proxy_http_version 1.1;
 | 
			
		||||
        proxy_set_header Upgrade \$http_upgrade;
 | 
			
		||||
        proxy_set_header Connection 'upgrade';
 | 
			
		||||
        proxy_set_header Host \$host;
 | 
			
		||||
        proxy_set_header X-Real-IP \$remote_addr;
 | 
			
		||||
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
 | 
			
		||||
        proxy_set_header X-Forwarded-Proto \$scheme;
 | 
			
		||||
        proxy_cache_bypass \$http_upgrade;
 | 
			
		||||
        
 | 
			
		||||
        # Timeouts
 | 
			
		||||
        proxy_connect_timeout 60s;
 | 
			
		||||
        proxy_send_timeout 60s;
 | 
			
		||||
        proxy_read_timeout 60s;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    # Health check endpoint
 | 
			
		||||
    location /health {
 | 
			
		||||
        access_log off;
 | 
			
		||||
        proxy_pass http://dfir_backend/health;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
# Enable site and reload nginx
 | 
			
		||||
sudo ln -sf /etc/nginx/sites-available/$SERVICE_NAME /etc/nginx/sites-enabled/
 | 
			
		||||
sudo nginx -t && sudo systemctl reload nginx
 | 
			
		||||
 | 
			
		||||
echo "✅ Node.js deployment completed!"
 | 
			
		||||
echo "🔍 Status: pm2 status"
 | 
			
		||||
echo "📋 Logs: pm2 logs $SERVICE_NAME"
 | 
			
		||||
							
								
								
									
										47
									
								
								framework.md
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								framework.md
									
									
									
									
									
								
							@ -1,47 +0,0 @@
 | 
			
		||||
Proposed Framework Categories (Y-axis):
 | 
			
		||||
1. Storage & File System Artifacts
 | 
			
		||||
 | 
			
		||||
Static file system Analyse (encrypted/unencrypted)
 | 
			
		||||
Registry Analyse
 | 
			
		||||
Database forensics
 | 
			
		||||
 | 
			
		||||
2. Memory & Runtime Artifacts
 | 
			
		||||
 | 
			
		||||
Memory forensics of live systems
 | 
			
		||||
Process Analyse
 | 
			
		||||
Virtualization forensics
 | 
			
		||||
 | 
			
		||||
3. Network & Communication Artifacts
 | 
			
		||||
 | 
			
		||||
Webserver log Analyse
 | 
			
		||||
System log Analyse
 | 
			
		||||
PKI Auswertung
 | 
			
		||||
Radio signal Analyse
 | 
			
		||||
VoIP forensics
 | 
			
		||||
Network packet Analyse (PCAP)
 | 
			
		||||
 | 
			
		||||
4. Application & Code Artifacts
 | 
			
		||||
 | 
			
		||||
Malware Analyse
 | 
			
		||||
Darknet website source code Analyse
 | 
			
		||||
Browser forensics
 | 
			
		||||
Email forensics
 | 
			
		||||
 | 
			
		||||
5. Multimedia & Content Artifacts
 | 
			
		||||
 | 
			
		||||
Video/image/audio authenticity Analyse
 | 
			
		||||
Steganography detection
 | 
			
		||||
Content recovery
 | 
			
		||||
 | 
			
		||||
6. Transaction & Financial Artifacts
 | 
			
		||||
 | 
			
		||||
Blockchain payment Analyse
 | 
			
		||||
Cryptocurrency exchange Analyse
 | 
			
		||||
Financial transaction forensics
 | 
			
		||||
 | 
			
		||||
7. Platform & Infrastructure Artifacts
 | 
			
		||||
 | 
			
		||||
Mobile forensics
 | 
			
		||||
Cloud forensics
 | 
			
		||||
IoT device forensics
 | 
			
		||||
Social media/OSINT Analyse
 | 
			
		||||
@ -18,6 +18,11 @@ const currentPath = Astro.url.pathname;
 | 
			
		||||
            ~/
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <a href="/knowledgebase" class={`nav-link ${currentPath === '/knowledgebase' ? 'active' : ''}`}>
 | 
			
		||||
            ~/knowledgebase
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <a href="/status" class={`nav-link ${currentPath === '/status' ? 'active' : ''}`}>
 | 
			
		||||
            ~/status
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ export interface Props {
 | 
			
		||||
    license: string;
 | 
			
		||||
    tags: string[];
 | 
			
		||||
    statusUrl?: string;
 | 
			
		||||
    knowledgebase?: boolean;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,6 +25,9 @@ const hasValidProjectUrl = tool.projectUrl !== undefined &&
 | 
			
		||||
                          tool.projectUrl !== "" && 
 | 
			
		||||
                          tool.projectUrl.trim() !== "";
 | 
			
		||||
 | 
			
		||||
// Check if tool has knowledgebase entry
 | 
			
		||||
const hasKnowledgebase = tool.knowledgebase === true;
 | 
			
		||||
 | 
			
		||||
// Determine card styling based on hosting status (derived from projectUrl)
 | 
			
		||||
const cardClass = hasValidProjectUrl ? 'card card-hosted' : (tool.license !== 'Proprietary' ? 'card card-oss' : 'card');
 | 
			
		||||
---
 | 
			
		||||
@ -31,9 +35,10 @@ const cardClass = hasValidProjectUrl ? 'card card-hosted' : (tool.license !== 'P
 | 
			
		||||
<div class={cardClass} onclick={`window.showToolDetails('${tool.name}')`} style="cursor: pointer;">
 | 
			
		||||
  <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
 | 
			
		||||
    <h3 style="margin: 0;">{tool.name}</h3>
 | 
			
		||||
    <div style="display: flex; gap: 0.5rem;">
 | 
			
		||||
    <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
 | 
			
		||||
      {hasValidProjectUrl && <span class="badge badge-primary">Self-Hosted</span>}
 | 
			
		||||
      {tool.license !== 'Proprietary' && <span class="badge badge-success">Open Source</span>}
 | 
			
		||||
      {hasKnowledgebase && <span class="badge badge-error">Infos 📖</span>}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
@ -259,29 +259,34 @@ const sortedTags = Object.entries(tagFrequency)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Filter function
 | 
			
		||||
// Filter function
 | 
			
		||||
    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
 | 
			
		||||
        if (searchTerm && !(
 | 
			
		||||
          tool.name.toLowerCase().includes(searchTerm) ||
 | 
			
		||||
          tool.description.toLowerCase().includes(searchTerm) ||
 | 
			
		||||
          tool.tags.some(tag => tag.toLowerCase().includes(searchTerm))
 | 
			
		||||
          tags.some(tag => tag.toLowerCase().includes(searchTerm))
 | 
			
		||||
        )) {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Domain filter
 | 
			
		||||
        if (selectedDomain && !tool.domains.includes(selectedDomain)) {
 | 
			
		||||
        if (selectedDomain && !domains.includes(selectedDomain)) {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Phase filter
 | 
			
		||||
        if (selectedPhase && !tool.phases.includes(selectedPhase)) {
 | 
			
		||||
        if (selectedPhase && !phases.includes(selectedPhase)) {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
@ -291,7 +296,7 @@ const sortedTags = Object.entries(tagFrequency)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Tag filter
 | 
			
		||||
        if (selectedTags.size > 0 && !Array.from(selectedTags).some(tag => tool.tags.includes(tag))) {
 | 
			
		||||
        if (selectedTags.size > 0 && !Array.from(selectedTags).some(tag => tags.includes(tag))) {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
@ -14,11 +14,11 @@ const tools = data.tools;
 | 
			
		||||
 | 
			
		||||
// Separate collaboration tools from domain-specific tools
 | 
			
		||||
const collaborationTools = tools.filter((tool: any) => 
 | 
			
		||||
  tool.phases.includes('collaboration')
 | 
			
		||||
  tool.phases && tool.phases.includes('collaboration')
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const domainTools = tools.filter((tool: any) => 
 | 
			
		||||
  !tool.phases.includes('collaboration')
 | 
			
		||||
  !tool.phases || !tool.phases.includes('collaboration')
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Create matrix structure for domain-specific tools only
 | 
			
		||||
@ -27,7 +27,7 @@ domains.forEach((domain: any) => {
 | 
			
		||||
  matrix[domain.id] = {};
 | 
			
		||||
  phases.filter((phase: any) => phase.id !== 'collaboration').forEach((phase: any) => {
 | 
			
		||||
    matrix[domain.id][phase.id] = domainTools.filter((tool: any) => 
 | 
			
		||||
      tool.domains.includes(domain.id) && tool.phases.includes(phase.id)
 | 
			
		||||
      tool.domains && tool.domains.includes(domain.id) && tool.phases && tool.phases.includes(phase.id)
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -51,6 +51,7 @@ domains.forEach((domain: any) => {
 | 
			
		||||
              <div style="display: flex; gap: 0.25rem;">
 | 
			
		||||
                {hasValidProjectUrl && <span class="badge-mini badge-primary">Self-Hosted</span>}
 | 
			
		||||
                {tool.license !== 'Proprietary' && <span class="badge-mini badge-success">OSS</span>}
 | 
			
		||||
                {tool.knowledgebase === true && <span class="badge-mini badge-error">Infos 📖</span>}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <p style="font-size: 0.75rem; color: var(--color-text-secondary); margin: 0.25rem 0; line-height: 1.3;">
 | 
			
		||||
@ -95,8 +96,10 @@ domains.forEach((domain: any) => {
 | 
			
		||||
                    class={`tool-chip ${hasValidProjectUrl ? 'tool-chip-hosted' : tool.license !== 'Proprietary' ? 'tool-chip-oss' : ''}`}
 | 
			
		||||
                    data-tool-name={tool.name}
 | 
			
		||||
                    onclick={`window.showToolDetails('${tool.name}')`}
 | 
			
		||||
                    title={`${tool.name}${tool.knowledgebase === true ? ' (KB verfügbar)' : ''}`}
 | 
			
		||||
                  >
 | 
			
		||||
                    {tool.name}
 | 
			
		||||
                    {tool.knowledgebase === true && <span style="margin-left: 0.25rem; font-size: 0.6875rem;">📖</span>}
 | 
			
		||||
                  </span>
 | 
			
		||||
                );
 | 
			
		||||
              })}
 | 
			
		||||
@ -131,7 +134,7 @@ 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;"></div>
 | 
			
		||||
  <div id="tool-links" style="display: flex; gap: 0.5rem; flex-direction: column;"></div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script define:vars={{ toolsData: tools, collaborationTools, domainTools }}>
 | 
			
		||||
@ -205,13 +208,14 @@ domains.forEach((domain: any) => {
 | 
			
		||||
        <div style="display: flex; gap: 0.25rem;">
 | 
			
		||||
          ${hasValidProjectUrl ? '<span class="badge-mini badge-primary">Self-Hosted</span>' : ''}
 | 
			
		||||
          ${tool.license !== 'Proprietary' ? '<span class="badge-mini badge-success">OSS</span>' : ''}
 | 
			
		||||
          ${tool.knowledgebase === true ? '<span class="badge-mini badge-error">Infos 📖</span>' : ''}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <p style="font-size: 0.75rem; color: var(--color-text-secondary); margin: 0.25rem 0; line-height: 1.3;">
 | 
			
		||||
        ${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>${(tool.platforms || []).join(', ')}</span>
 | 
			
		||||
        <span>•</span>
 | 
			
		||||
        <span>${tool.skillLevel}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -243,14 +247,19 @@ domains.forEach((domain: any) => {
 | 
			
		||||
    if (tool.license !== 'Proprietary') {
 | 
			
		||||
      badgesContainer.innerHTML += '<span class="badge badge-success">Open Source</span>';
 | 
			
		||||
    }
 | 
			
		||||
    if (tool.knowledgebase === true) {
 | 
			
		||||
      badgesContainer.innerHTML += '<span class="badge badge-error">Infos 📖</span>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Metadata
 | 
			
		||||
    // Metadata - safe array handling
 | 
			
		||||
    const metadataContainer = document.getElementById('tool-metadata');
 | 
			
		||||
    const domainsText = tool.domains.length > 0 ? tool.domains.join(', ') : 'Domain-agnostic';
 | 
			
		||||
    const phasesText = tool.phases.join(', ');
 | 
			
		||||
    const domains = tool.domains || [];
 | 
			
		||||
    const phases = tool.phases || [];
 | 
			
		||||
    const domainsText = domains.length > 0 ? domains.join(', ') : 'Domain-agnostic';
 | 
			
		||||
    const phasesText = phases.join(', ');
 | 
			
		||||
    metadataContainer.innerHTML = `
 | 
			
		||||
      <div style="display: grid; gap: 0.5rem;">
 | 
			
		||||
        <div><strong>Betriebssystem:</strong> ${tool.platforms.join(', ')}</div>
 | 
			
		||||
        <div><strong>Betriebssystem:</strong> ${(tool.platforms || []).join(', ')}</div>
 | 
			
		||||
        <div><strong>Skill Level:</strong> ${tool.skillLevel}</div>
 | 
			
		||||
        <div><strong>Lizenzmodell:</strong> ${tool.license}</div>
 | 
			
		||||
        <div><strong>Deployment:</strong> ${tool.accessType}</div>
 | 
			
		||||
@ -259,36 +268,61 @@ domains.forEach((domain: any) => {
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
    
 | 
			
		||||
    // Tags
 | 
			
		||||
    // Tags - safe array handling
 | 
			
		||||
    const tagsContainer = document.getElementById('tool-tags');
 | 
			
		||||
    const tags = tool.tags || [];
 | 
			
		||||
    tagsContainer.innerHTML = `
 | 
			
		||||
      <div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
 | 
			
		||||
        ${tool.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
 | 
			
		||||
        ${tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
    
 | 
			
		||||
    // Links - Updated to handle dual buttons for hosted tools
 | 
			
		||||
    // Links - Updated to handle dual buttons for hosted tools AND knowledgebase links
 | 
			
		||||
    const linksContainer = document.getElementById('tool-links');
 | 
			
		||||
    
 | 
			
		||||
    let linksHTML = '';
 | 
			
		||||
    
 | 
			
		||||
    // Main action buttons
 | 
			
		||||
    if (hasValidProjectUrl) {
 | 
			
		||||
      // Two buttons for tools we're hosting
 | 
			
		||||
      linksContainer.innerHTML = `
 | 
			
		||||
        <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
 | 
			
		||||
          Software-Homepage
 | 
			
		||||
        </a>
 | 
			
		||||
        <a href="${tool.projectUrl}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="flex: 1;">
 | 
			
		||||
          Zugreifen
 | 
			
		||||
        </a>
 | 
			
		||||
      linksHTML += `
 | 
			
		||||
        <div style="display: flex; gap: 0.5rem;">
 | 
			
		||||
          <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
 | 
			
		||||
            Software-Homepage
 | 
			
		||||
          </a>
 | 
			
		||||
          <a href="${tool.projectUrl}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="flex: 1;">
 | 
			
		||||
            Zugreifen
 | 
			
		||||
          </a>
 | 
			
		||||
        </div>
 | 
			
		||||
      `;
 | 
			
		||||
    } else {
 | 
			
		||||
      // Single button for tools we're not hosting
 | 
			
		||||
      linksContainer.innerHTML = `
 | 
			
		||||
      linksHTML += `
 | 
			
		||||
        <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
			
		||||
          Software-Homepage
 | 
			
		||||
        </a>
 | 
			
		||||
      `;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Add knowledgebase link if available
 | 
			
		||||
    if (tool.knowledgebase === true) {
 | 
			
		||||
      const kbId = tool.name.toLowerCase().replace(/\s+/g, '-');
 | 
			
		||||
      linksHTML += `
 | 
			
		||||
        <a href="/knowledgebase#kb-${kbId}" class="btn btn-secondary" style="width: 100%; margin-top: 0.5rem;">
 | 
			
		||||
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
 | 
			
		||||
            <polyline points="14 2 14 8 20 8"/>
 | 
			
		||||
            <line x1="16" y1="13" x2="8" y2="13"/>
 | 
			
		||||
            <line x1="16" y1="17" x2="8" y2="17"/>
 | 
			
		||||
            <polyline points="10 9 9 9 8 9"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          Knowledgebase anzeigen
 | 
			
		||||
        </a>
 | 
			
		||||
      `;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    linksContainer.innerHTML = linksHTML;
 | 
			
		||||
    
 | 
			
		||||
    // Show modal
 | 
			
		||||
    document.getElementById('modal-overlay').classList.add('active');
 | 
			
		||||
    document.getElementById('tool-details').classList.add('active');
 | 
			
		||||
@ -336,7 +370,7 @@ domains.forEach((domain: any) => {
 | 
			
		||||
        dfirMatrixSection.style.display = 'none';
 | 
			
		||||
        
 | 
			
		||||
        // Filter collaboration tools
 | 
			
		||||
        const filteredCollaboration = filtered.filter(tool => tool.phases.includes('collaboration'));
 | 
			
		||||
        const filteredCollaboration = filtered.filter(tool => (tool.phases || []).includes('collaboration'));
 | 
			
		||||
        collaborationContainer.innerHTML = '';
 | 
			
		||||
        
 | 
			
		||||
        filteredCollaboration.forEach(tool => {
 | 
			
		||||
@ -352,7 +386,7 @@ domains.forEach((domain: any) => {
 | 
			
		||||
          collaborationSection.style.display = 'block';
 | 
			
		||||
          
 | 
			
		||||
          // Show all collaboration tools that pass general filters
 | 
			
		||||
          const filteredCollaboration = filtered.filter(tool => tool.phases.includes('collaboration'));
 | 
			
		||||
          const filteredCollaboration = filtered.filter(tool => (tool.phases || []).includes('collaboration'));
 | 
			
		||||
          collaborationContainer.innerHTML = '';
 | 
			
		||||
          
 | 
			
		||||
          filteredCollaboration.forEach(tool => {
 | 
			
		||||
@ -369,22 +403,26 @@ domains.forEach((domain: any) => {
 | 
			
		||||
          cell.innerHTML = '';
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // Re-populate with filtered DFIR tools
 | 
			
		||||
        const filteredDfirTools = filtered.filter(tool => !tool.phases.includes('collaboration'));
 | 
			
		||||
        // Re-populate with filtered DFIR tools - with safe array handling
 | 
			
		||||
        const filteredDfirTools = filtered.filter(tool => !(tool.phases || []).includes('collaboration'));
 | 
			
		||||
        filteredDfirTools.forEach(tool => {
 | 
			
		||||
          const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
                                    tool.projectUrl !== null && 
 | 
			
		||||
                                    tool.projectUrl !== "" && 
 | 
			
		||||
                                    tool.projectUrl.trim() !== "";
 | 
			
		||||
          
 | 
			
		||||
          tool.domains.forEach(domain => {
 | 
			
		||||
            tool.phases.forEach(phase => {
 | 
			
		||||
          const domains = tool.domains || [];
 | 
			
		||||
          const phases = tool.phases || [];
 | 
			
		||||
          
 | 
			
		||||
          domains.forEach(domain => {
 | 
			
		||||
            phases.forEach(phase => {
 | 
			
		||||
              if (phase !== 'collaboration') {
 | 
			
		||||
                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.textContent = tool.name;
 | 
			
		||||
                  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);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,11 @@ tools:
 | 
			
		||||
  # Disk & File System Analysis Tools
 | 
			
		||||
  - name: Autopsy
 | 
			
		||||
    description: >-
 | 
			
		||||
      Open-Source digitale Forensik-Plattform mit grafischer Benutzeroberfläche
 | 
			
		||||
      Open-Source digitale Forensik-Anwendung mit grafischer Benutzeroberfläche
 | 
			
		||||
      für Festplatten- und Dateisystemanalyse. Besonders geeignet für die
 | 
			
		||||
      Analyse-Phase mit umfangreichen Carving- und Timeline-Funktionen.
 | 
			
		||||
      Auswertungs- und Analyse-Phase mit umfangreichen Carving- und Timeline-Funktionen. 
 | 
			
		||||
      DIE Alternative für kommerzielle Software im Bereich, wenn es um die 
 | 
			
		||||
      kriminalistische Untersuchung von Images geht.
 | 
			
		||||
    domains:
 | 
			
		||||
      - incident-response
 | 
			
		||||
      - law-enforcement
 | 
			
		||||
@ -13,15 +15,16 @@ tools:
 | 
			
		||||
      - data-collection
 | 
			
		||||
      - examination
 | 
			
		||||
      - analysis
 | 
			
		||||
      - reporting
 | 
			
		||||
    platforms:
 | 
			
		||||
      - Windows
 | 
			
		||||
      - Linux
 | 
			
		||||
      - macOS
 | 
			
		||||
      - Linux (Snap-Paket)
 | 
			
		||||
    skillLevel: intermediate
 | 
			
		||||
    accessType: download
 | 
			
		||||
    url: https://www.autopsy.com/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - disk-imaging
 | 
			
		||||
      - file-carving
 | 
			
		||||
@ -56,6 +59,7 @@ tools:
 | 
			
		||||
    url: https://www.volatilityfoundation.org/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: VSL
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - memory-analysis
 | 
			
		||||
      - malware-detection
 | 
			
		||||
@ -74,6 +78,7 @@ tools:
 | 
			
		||||
      Kollaborative Security-Incident-Response-Plattform für SOCs, CERTs und
 | 
			
		||||
      Sicherheitsteams mit Case-Management. Ideal für alle Phasen einer
 | 
			
		||||
      Untersuchung, besonders für Koordination und Berichterstattung.
 | 
			
		||||
      Keine Erfahrungswerte.
 | 
			
		||||
    domains:
 | 
			
		||||
      - incident-response
 | 
			
		||||
      - law-enforcement
 | 
			
		||||
@ -83,7 +88,6 @@ tools:
 | 
			
		||||
      - examination
 | 
			
		||||
      - analysis
 | 
			
		||||
      - reporting
 | 
			
		||||
      - collaboration
 | 
			
		||||
    platforms:
 | 
			
		||||
      - Web
 | 
			
		||||
    skillLevel: intermediate
 | 
			
		||||
@ -91,6 +95,7 @@ tools:
 | 
			
		||||
    url: https://strangebee.com/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Community Edition (Free) / Commercial
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - case-management
 | 
			
		||||
      - team-collaboration
 | 
			
		||||
@ -117,7 +122,6 @@ tools:
 | 
			
		||||
      - data-collection
 | 
			
		||||
      - examination
 | 
			
		||||
      - analysis
 | 
			
		||||
      - collaboration
 | 
			
		||||
    platforms:
 | 
			
		||||
      - Web
 | 
			
		||||
    skillLevel: intermediate
 | 
			
		||||
@ -125,6 +129,7 @@ tools:
 | 
			
		||||
    url: https://misp-project.org/
 | 
			
		||||
    projectUrl: https://misp.cc24.dev
 | 
			
		||||
    license: AGPL-3.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - threat-intelligence
 | 
			
		||||
      - ioc-sharing
 | 
			
		||||
@ -156,6 +161,7 @@ tools:
 | 
			
		||||
    url: https://timesketch.org/
 | 
			
		||||
    projectUrl: https://timesketch.cc24.dev
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - timeline-analysis
 | 
			
		||||
      - data-visualization
 | 
			
		||||
@ -190,6 +196,7 @@ tools:
 | 
			
		||||
    url: https://www.wireshark.org/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: GPL-2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - packet-capture
 | 
			
		||||
      - protocol-analysis
 | 
			
		||||
@ -224,6 +231,7 @@ tools:
 | 
			
		||||
    url: https://www.magnetforensics.com/products/magnet-axiom/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Proprietary
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - mobile-forensics
 | 
			
		||||
      - cloud-acquisition
 | 
			
		||||
@ -255,6 +263,7 @@ tools:
 | 
			
		||||
    url: https://cellebrite.com/en/ufed/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Proprietary
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - mobile-extraction
 | 
			
		||||
      - physical-extraction
 | 
			
		||||
@ -283,6 +292,7 @@ tools:
 | 
			
		||||
    url: https://github.com/cert-ee/cuckoo3
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: GPL-3.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - dynamic-analysis
 | 
			
		||||
      - behavior-monitoring
 | 
			
		||||
@ -314,6 +324,7 @@ tools:
 | 
			
		||||
    url: https://ghidra-sre.org/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - reverse-engineering
 | 
			
		||||
      - disassembly
 | 
			
		||||
@ -346,6 +357,7 @@ tools:
 | 
			
		||||
    url: https://plaso.readthedocs.io/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - timeline-generation
 | 
			
		||||
      - log-parsing
 | 
			
		||||
@ -376,6 +388,7 @@ tools:
 | 
			
		||||
    url: https://gchq.github.io/CyberChef/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - data-transformation
 | 
			
		||||
      - encoding-decoding
 | 
			
		||||
@ -410,6 +423,7 @@ tools:
 | 
			
		||||
    url: https://www.velociraptor.app/
 | 
			
		||||
    projectUrl: https://raptor.cc24.dev
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - remote-collection
 | 
			
		||||
      - live-forensics
 | 
			
		||||
@ -442,6 +456,7 @@ tools:
 | 
			
		||||
    url: https://github.com/google/grr
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - remote-forensics
 | 
			
		||||
      - scalable-collection
 | 
			
		||||
@ -473,6 +488,7 @@ tools:
 | 
			
		||||
    url: https://arkime.com/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Apache 2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - full-packet-capture
 | 
			
		||||
      - pcap-indexing
 | 
			
		||||
@ -503,6 +519,7 @@ tools:
 | 
			
		||||
    url: https://www.netresec.com/?page=NetworkMiner
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: GPL-2.0 / Commercial
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - pcap-analysis
 | 
			
		||||
      - file-extraction
 | 
			
		||||
@ -533,6 +550,7 @@ tools:
 | 
			
		||||
      https://www.kroll.com/en/services/cyber-risk/incident-response-litigation-support/kroll-artifact-parser-extractor-kape
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Freeware
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - triage-collection
 | 
			
		||||
      - artifact-parsing
 | 
			
		||||
@ -565,6 +583,7 @@ tools:
 | 
			
		||||
    url: https://exiftool.org/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Perl Artistic License
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - metadata-extraction
 | 
			
		||||
      - exif-analysis
 | 
			
		||||
@ -594,6 +613,7 @@ tools:
 | 
			
		||||
    url: https://www.chainalysis.com/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: Proprietary
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - blockchain-analysis
 | 
			
		||||
      - crypto-tracing
 | 
			
		||||
@ -627,6 +647,7 @@ tools:
 | 
			
		||||
    url: https://neo4j.com/
 | 
			
		||||
    projectUrl: https://graph.cc24.dev
 | 
			
		||||
    license: GPL-3.0 / Commercial
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - graph-database
 | 
			
		||||
      - relationship-analysis
 | 
			
		||||
@ -658,6 +679,7 @@ tools:
 | 
			
		||||
    url: https://qgis.org/
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: GPL-2.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - geospatial-analysis
 | 
			
		||||
      - gps-visualization
 | 
			
		||||
@ -675,12 +697,8 @@ tools:
 | 
			
		||||
      kollaborative Phasen und sichere Speicherung von Beweismitteln
 | 
			
		||||
      mit Versionierung.
 | 
			
		||||
    domains:
 | 
			
		||||
      - incident-response
 | 
			
		||||
      - law-enforcement
 | 
			
		||||
      - fraud-investigation
 | 
			
		||||
    phases:
 | 
			
		||||
      - collaboration
 | 
			
		||||
      - reporting
 | 
			
		||||
      - collaboration-general
 | 
			
		||||
    platforms:
 | 
			
		||||
      - Web
 | 
			
		||||
    skillLevel: novice
 | 
			
		||||
@ -688,6 +706,7 @@ tools:
 | 
			
		||||
    url: https://nextcloud.com/
 | 
			
		||||
    projectUrl: https://cloud.cc24.dev
 | 
			
		||||
    license: AGPL-3.0
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - file-sharing
 | 
			
		||||
      - collaboration
 | 
			
		||||
@ -708,8 +727,7 @@ tools:
 | 
			
		||||
      - incident-response
 | 
			
		||||
      - malware-analysis
 | 
			
		||||
    phases:
 | 
			
		||||
      - collaboration
 | 
			
		||||
      - reporting
 | 
			
		||||
      - collaboration-general
 | 
			
		||||
    platforms:
 | 
			
		||||
      - Web
 | 
			
		||||
    skillLevel: beginner
 | 
			
		||||
@ -717,6 +735,7 @@ tools:
 | 
			
		||||
    url: https://gitea.io/
 | 
			
		||||
    projectUrl: https://git.cc24.dev
 | 
			
		||||
    license: MIT
 | 
			
		||||
    knowledgebase: 
 | 
			
		||||
    tags:
 | 
			
		||||
      - version-control
 | 
			
		||||
      - code-repository
 | 
			
		||||
@ -749,6 +768,7 @@ tools:
 | 
			
		||||
    url: https://github.com/ReFirmLabs/binwalk
 | 
			
		||||
    projectUrl: ''
 | 
			
		||||
    license: MIT
 | 
			
		||||
    knowledgebase:
 | 
			
		||||
    tags:
 | 
			
		||||
      - firmware-analysis
 | 
			
		||||
      - file-carving
 | 
			
		||||
@ -786,5 +806,5 @@ phases:
 | 
			
		||||
    name: Analyse
 | 
			
		||||
  - id: reporting
 | 
			
		||||
    name: Bericht & Präsentation
 | 
			
		||||
  - id: collaboration
 | 
			
		||||
  - id: collaboration-general
 | 
			
		||||
    name: Übergreifend & Kollaboration
 | 
			
		||||
@ -138,13 +138,14 @@ const tools = data.tools;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
// Create tool card element
 | 
			
		||||
function createToolCard(tool) {
 | 
			
		||||
  const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
                            tool.projectUrl !== null && 
 | 
			
		||||
                            tool.projectUrl !== "" && 
 | 
			
		||||
                            tool.projectUrl.trim() !== "";
 | 
			
		||||
  
 | 
			
		||||
  const hasKnowledgebase = tool.knowledgebase === true;
 | 
			
		||||
  
 | 
			
		||||
  const cardDiv = document.createElement('div');
 | 
			
		||||
  const cardClass = hasValidProjectUrl ? 'card card-hosted' : (tool.license !== 'Proprietary' ? 'card card-oss' : 'card');
 | 
			
		||||
  cardDiv.className = cardClass;
 | 
			
		||||
@ -177,9 +178,10 @@ function createToolCard(tool) {
 | 
			
		||||
  cardDiv.innerHTML = `
 | 
			
		||||
    <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
 | 
			
		||||
      <h3 style="margin: 0;">${tool.name}</h3>
 | 
			
		||||
      <div style="display: flex; gap: 0.5rem;">
 | 
			
		||||
      <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
 | 
			
		||||
        ${hasValidProjectUrl ? '<span class="badge badge-primary">Self-Hosted</span>' : ''}
 | 
			
		||||
        ${tool.license !== 'Proprietary' ? '<span class="badge badge-success">Open Source</span>' : ''}
 | 
			
		||||
        ${hasKnowledgebase ? '<span class="badge badge-error">Infos 📖</span>' : ''}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
@ -195,7 +197,7 @@ function createToolCard(tool) {
 | 
			
		||||
          <line x1="9" y1="15" x2="15" y2="15"></line>
 | 
			
		||||
        </svg>
 | 
			
		||||
        <span class="text-muted" style="font-size: 0.75rem;">
 | 
			
		||||
          ${tool.platforms.join(', ')}
 | 
			
		||||
          ${(tool.platforms || []).join(', ')}
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
      
 | 
			
		||||
@ -221,7 +223,7 @@ function createToolCard(tool) {
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <div style="display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 1rem;">
 | 
			
		||||
      ${tool.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
 | 
			
		||||
      ${(tool.tags || []).map(tag => `<span class="tag">${tag}</span>`).join('')}
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    ${buttonHTML}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										277
									
								
								src/pages/knowledgebase.astro
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								src/pages/knowledgebase.astro
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,277 @@
 | 
			
		||||
---
 | 
			
		||||
import BaseLayout from '../layouts/BaseLayout.astro';
 | 
			
		||||
import { promises as fs } from 'fs';
 | 
			
		||||
import { load } from 'js-yaml';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
// Load tools data
 | 
			
		||||
const yamlPath = path.join(process.cwd(), 'src/data/tools.yaml');
 | 
			
		||||
const yamlContent = await fs.readFile(yamlPath, 'utf8');
 | 
			
		||||
const data = load(yamlContent) as any;
 | 
			
		||||
 | 
			
		||||
// Filter tools that have knowledgebase entries
 | 
			
		||||
const knowledgebaseTools = data.tools.filter((tool: any) => tool.knowledgebase === true);
 | 
			
		||||
 | 
			
		||||
// Sort alphabetically by name
 | 
			
		||||
knowledgebaseTools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<BaseLayout title="Knowledgebase" description="Extended documentation and insights for DFIR tools">
 | 
			
		||||
  <section style="padding: 2rem 0;">
 | 
			
		||||
    <!-- Header -->
 | 
			
		||||
    <div style="text-align: center; margin-bottom: 3rem; padding: 2rem; background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%); border-radius: 1rem; border: 1px solid var(--color-border);">
 | 
			
		||||
      <h1 style="margin-bottom: 1rem; font-size: 2.5rem; color: var(--color-primary);">Knowledgebase</h1>
 | 
			
		||||
      <p style="font-size: 1.25rem; color: var(--color-text-secondary); margin-bottom: 0.5rem;">
 | 
			
		||||
        Erweiterte Dokumentation und Erkenntnisse
 | 
			
		||||
      </p>
 | 
			
		||||
      <p style="font-size: 1rem; color: var(--color-text-secondary);">
 | 
			
		||||
        Praktische Erfahrungen, Konfigurationshinweise und Lektionen aus der Praxis
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Search and Filter -->
 | 
			
		||||
    <div style="margin-bottom: 2rem;">
 | 
			
		||||
      <input 
 | 
			
		||||
        type="text" 
 | 
			
		||||
        id="kb-search" 
 | 
			
		||||
        placeholder="Knowledgebase durchsuchen..."
 | 
			
		||||
        style="max-width: 500px; margin: 0 auto; display: block;"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Tools Count -->
 | 
			
		||||
    <div style="text-align: center; margin-bottom: 2rem;">
 | 
			
		||||
      <p class="text-muted" style="font-size: 0.875rem;">
 | 
			
		||||
        <span id="visible-count">{knowledgebaseTools.length}</span> von {knowledgebaseTools.length} Einträgen
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Knowledgebase Entries -->
 | 
			
		||||
    <div style="max-width: 1000px; margin: 0 auto;">
 | 
			
		||||
      {knowledgebaseTools.length === 0 ? (
 | 
			
		||||
        <div class="card" style="text-align: center; padding: 3rem;">
 | 
			
		||||
          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="1.5" style="margin: 0 auto 1rem;">
 | 
			
		||||
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
 | 
			
		||||
            <polyline points="14 2 14 8 20 8"/>
 | 
			
		||||
            <line x1="16" y1="13" x2="8" y2="13"/>
 | 
			
		||||
            <line x1="16" y1="17" x2="8" y2="17"/>
 | 
			
		||||
            <polyline points="10 9 9 9 8 9"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          <h3 style="color: var(--color-text-secondary); margin-bottom: 0.5rem;">Noch keine Knowledgebase-Einträge</h3>
 | 
			
		||||
          <p class="text-muted">
 | 
			
		||||
            Knowledgebase-Einträge werden automatisch angezeigt, sobald Tools das Attribut "knowledgebase: true" haben.
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <div id="kb-entries">
 | 
			
		||||
          {knowledgebaseTools.map((tool: any, index: number) => (
 | 
			
		||||
            <article class="kb-entry card kb-entry-collapsed" id={`kb-${tool.name.toLowerCase().replace(/\s+/g, '-')}`} data-tool-name={tool.name.toLowerCase()}>
 | 
			
		||||
              <!-- Collapsed Header (default state) -->
 | 
			
		||||
              <div class="kb-entry-header" onclick={`toggleKbEntry('${tool.name.toLowerCase().replace(/\s+/g, '-')}')`} style="cursor: pointer;">
 | 
			
		||||
                <div style="display: flex; justify-content: space-between; align-items: center;">
 | 
			
		||||
                  <div style="display: flex; align-items: center; gap: 1rem;">
 | 
			
		||||
                    <h3 style="margin: 0; color: var(--color-primary);">{tool.name}</h3>
 | 
			
		||||
                    <div style="display: flex; gap: 0.5rem;">
 | 
			
		||||
                      {(() => {
 | 
			
		||||
                        const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
                                                  tool.projectUrl !== null && 
 | 
			
		||||
                                                  tool.projectUrl !== "" && 
 | 
			
		||||
                                                  tool.projectUrl.trim() !== "";
 | 
			
		||||
                        return (
 | 
			
		||||
                          <>
 | 
			
		||||
                            {hasValidProjectUrl && <span class="badge badge-primary">Self-Hosted</span>}
 | 
			
		||||
                            {tool.license !== 'Proprietary' && <span class="badge badge-success">Open Source</span>}
 | 
			
		||||
                            <span class="badge badge-error">Infos 📖</span>
 | 
			
		||||
                          </>
 | 
			
		||||
                        );
 | 
			
		||||
                      })()}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="kb-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>
 | 
			
		||||
                <p class="text-muted" style="font-size: 0.875rem; margin: 0.5rem 0 0 0;">
 | 
			
		||||
                  {tool.description}
 | 
			
		||||
                </p>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- Expanded Content (hidden by default) -->
 | 
			
		||||
              <div class="kb-entry-content" style="display: none;">
 | 
			
		||||
                <!-- Quick Info -->
 | 
			
		||||
                <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin: 1.5rem 0; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.5rem;">
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <strong style="font-size: 0.75rem; color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">Plattformen</strong>
 | 
			
		||||
                    <p style="margin: 0.25rem 0 0 0; font-size: 0.875rem;">{tool.platforms.join(', ')}</p>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <strong style="font-size: 0.75rem; color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">Skill Level</strong>
 | 
			
		||||
                    <p style="margin: 0.25rem 0 0 0; font-size: 0.875rem;">{tool.skillLevel}</p>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <strong style="font-size: 0.75rem; color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">Lizenz</strong>
 | 
			
		||||
                    <p style="margin: 0.25rem 0 0 0; font-size: 0.875rem;">{tool.license}</p>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <strong style="font-size: 0.75rem; color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">Phasen</strong>
 | 
			
		||||
                    <p style="margin: 0.25rem 0 0 0; font-size: 0.875rem;">{tool.phases.join(', ')}</p>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Knowledge Content Area -->
 | 
			
		||||
                <div class="kb-content">
 | 
			
		||||
                  <h4 style="margin-bottom: 1rem; color: var(--color-text); font-size: 1.125rem;">Praktische Erkenntnisse</h4>
 | 
			
		||||
                  
 | 
			
		||||
                  {tool.knowledgebaseContent ? (
 | 
			
		||||
                    <div style="line-height: 1.7;">
 | 
			
		||||
                      <p>{tool.knowledgebaseContent}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  ) : (
 | 
			
		||||
                    <div style="padding: 2rem; text-align: center; background-color: var(--color-bg-secondary); border-radius: 0.5rem; border: 2px dashed var(--color-border);">
 | 
			
		||||
                      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="1.5" style="margin-bottom: 0.75rem;">
 | 
			
		||||
                        <path d="M14.828 14.828a4 4 0 0 1-5.656 0"/>
 | 
			
		||||
                        <path d="M9 9a3 3 0 1 1 6 0c0 .749-.269 1.433-.73 1.96L11 14v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1l-3.27-3.04A3 3 0 0 1 5 9a3 3 0 0 1 6 0"/>
 | 
			
		||||
                        <path d="M12 17h.01"/>
 | 
			
		||||
                      </svg>
 | 
			
		||||
                      <p class="text-muted" style="margin: 0; font-style: italic;">
 | 
			
		||||
                        Inhalt wird manuell hinzugefügt
 | 
			
		||||
                      </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Action Links -->
 | 
			
		||||
                <div style="display: flex; gap: 1rem; margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--color-border);">
 | 
			
		||||
                  {(() => {
 | 
			
		||||
                    const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
                                              tool.projectUrl !== null && 
 | 
			
		||||
                                              tool.projectUrl !== "" && 
 | 
			
		||||
                                              tool.projectUrl.trim() !== "";
 | 
			
		||||
                    return (
 | 
			
		||||
                      <>
 | 
			
		||||
                        <a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
 | 
			
		||||
                          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                            <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
                            <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
                            <line x1="10" y1="14" x2="21" y2="3"/>
 | 
			
		||||
                          </svg>
 | 
			
		||||
                          Software-Homepage
 | 
			
		||||
                        </a>
 | 
			
		||||
                        {hasValidProjectUrl && (
 | 
			
		||||
                          <a href={tool.projectUrl} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
 | 
			
		||||
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                              <circle cx="12" cy="12" r="10"/>
 | 
			
		||||
                              <path d="M12 16l4-4-4-4"/>
 | 
			
		||||
                              <path d="M8 12h8"/>
 | 
			
		||||
                            </svg>
 | 
			
		||||
                            Zugreifen
 | 
			
		||||
                          </a>
 | 
			
		||||
                        )}
 | 
			
		||||
                      </>
 | 
			
		||||
                    );
 | 
			
		||||
                  })()}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </article>
 | 
			
		||||
          ))}
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- No Results Message -->
 | 
			
		||||
    <div id="no-kb-results" style="display: none; text-align: center; padding: 4rem 0;">
 | 
			
		||||
      <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="1.5" style="margin-bottom: 1rem;">
 | 
			
		||||
        <circle cx="11" cy="11" r="8"/>
 | 
			
		||||
        <path d="M21 21l-4.35-4.35"/>
 | 
			
		||||
      </svg>
 | 
			
		||||
      <h3 style="color: var(--color-text-secondary); margin-bottom: 0.5rem;">Keine Ergebnisse gefunden</h3>
 | 
			
		||||
      <p class="text-muted">Versuchen Sie es mit anderen Suchbegriffen.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </section>
 | 
			
		||||
</BaseLayout>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  // Toggle knowledgebase entry expansion
 | 
			
		||||
  function toggleKbEntry(entryId: string) {
 | 
			
		||||
    const entry = document.getElementById(`kb-${entryId}`);
 | 
			
		||||
    if (!entry) return;
 | 
			
		||||
    
 | 
			
		||||
    const content = entry.querySelector('.kb-entry-content') as HTMLElement;
 | 
			
		||||
    const icon = entry.querySelector('.kb-expand-icon svg') as HTMLElement;
 | 
			
		||||
    
 | 
			
		||||
    if (!content || !icon) return;
 | 
			
		||||
    
 | 
			
		||||
    const isExpanded = entry.classList.contains('kb-entry-expanded');
 | 
			
		||||
    
 | 
			
		||||
    if (isExpanded) {
 | 
			
		||||
      // Collapse
 | 
			
		||||
      entry.classList.remove('kb-entry-expanded');
 | 
			
		||||
      entry.classList.add('kb-entry-collapsed');
 | 
			
		||||
      content.style.display = 'none';
 | 
			
		||||
      icon.style.transform = 'rotate(0deg)';
 | 
			
		||||
    } else {
 | 
			
		||||
      // Expand
 | 
			
		||||
      entry.classList.remove('kb-entry-collapsed');
 | 
			
		||||
      entry.classList.add('kb-entry-expanded');
 | 
			
		||||
      content.style.display = 'block';
 | 
			
		||||
      icon.style.transform = 'rotate(180deg)';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  // Make toggle function globally available
 | 
			
		||||
  (window as any).toggleKbEntry = toggleKbEntry;
 | 
			
		||||
 | 
			
		||||
  // Search functionality
 | 
			
		||||
  document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
    const searchInput = document.getElementById('kb-search') as HTMLInputElement;
 | 
			
		||||
    const entries = document.querySelectorAll('.kb-entry');
 | 
			
		||||
    const visibleCount = document.getElementById('visible-count');
 | 
			
		||||
    const noResults = document.getElementById('no-kb-results');
 | 
			
		||||
    const entriesContainer = document.getElementById('kb-entries');
 | 
			
		||||
    
 | 
			
		||||
    if (!searchInput || !entries.length) return;
 | 
			
		||||
    
 | 
			
		||||
    // Check if page loaded with hash for specific entry
 | 
			
		||||
    if (window.location.hash && window.location.hash.startsWith('#kb-')) {
 | 
			
		||||
      const targetId = window.location.hash.replace('#kb-', '');
 | 
			
		||||
      setTimeout(() => toggleKbEntry(targetId), 100);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    searchInput.addEventListener('input', () => {
 | 
			
		||||
      const searchTerm = searchInput.value.toLowerCase().trim();
 | 
			
		||||
      let visibleEntries = 0;
 | 
			
		||||
      
 | 
			
		||||
      entries.forEach(entry => {
 | 
			
		||||
        const toolName = entry.getAttribute('data-tool-name') || '';
 | 
			
		||||
        const entryText = entry.textContent?.toLowerCase() || '';
 | 
			
		||||
        
 | 
			
		||||
        if (searchTerm === '' || 
 | 
			
		||||
            toolName.includes(searchTerm) || 
 | 
			
		||||
            entryText.includes(searchTerm)) {
 | 
			
		||||
          (entry as HTMLElement).style.display = 'block';
 | 
			
		||||
          visibleEntries++;
 | 
			
		||||
        } else {
 | 
			
		||||
          (entry as HTMLElement).style.display = 'none';
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      // Update count
 | 
			
		||||
      if (visibleCount) {
 | 
			
		||||
        visibleCount.textContent = visibleEntries.toString();
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Show/hide no results message
 | 
			
		||||
      if (noResults && entriesContainer) {
 | 
			
		||||
        if (visibleEntries === 0 && searchTerm !== '') {
 | 
			
		||||
          (noResults as HTMLElement).style.display = 'block';
 | 
			
		||||
          (entriesContainer as HTMLElement).style.display = 'none';
 | 
			
		||||
        } else {
 | 
			
		||||
          (noResults as HTMLElement).style.display = 'none';
 | 
			
		||||
          (entriesContainer as HTMLElement).style.display = 'block';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
@ -389,6 +389,54 @@ input[type="checkbox"] {
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Knowledgebase entries styling */
 | 
			
		||||
.kb-entry {
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-entry:target {
 | 
			
		||||
  animation: highlight-flash 2s ease-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-entry-collapsed {
 | 
			
		||||
  /* Collapsed state - compact view with minimal spacing */
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-entry-expanded {
 | 
			
		||||
  margin-bottom: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-entry-header {
 | 
			
		||||
  padding: 1rem 0;
 | 
			
		||||
  transition: all 0.2s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-entry-header:hover {
 | 
			
		||||
  background-color: var(--color-bg-secondary);
 | 
			
		||||
  margin: -0.5rem;
 | 
			
		||||
  padding: 1.5rem;
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-expand-icon {
 | 
			
		||||
  transition: transform 0.3s ease;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-expand-icon svg {
 | 
			
		||||
  transition: transform 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-entry-content {
 | 
			
		||||
  margin-top: 1rem;
 | 
			
		||||
  padding-top: 1rem;
 | 
			
		||||
  border-top: 1px solid var(--color-border);
 | 
			
		||||
  animation: slideDown 0.3s ease-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Tool Details Modal */
 | 
			
		||||
.tool-details {
 | 
			
		||||
  display: none;
 | 
			
		||||
@ -522,6 +570,69 @@ footer {
 | 
			
		||||
  to { opacity: 1; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes slideDown {
 | 
			
		||||
  from {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    max-height: 0;
 | 
			
		||||
    padding-top: 0;
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
  }
 | 
			
		||||
  to {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    max-height: 1000px;
 | 
			
		||||
    padding-top: 1rem;
 | 
			
		||||
    margin-top: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes highlight-flash {
 | 
			
		||||
  0% { background-color: rgba(37, 99, 235, 0.1); }
 | 
			
		||||
  100% { background-color: transparent; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-content {
 | 
			
		||||
  line-height: 1.7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-content h3 {
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  border-bottom: 2px solid var(--color-primary);
 | 
			
		||||
  padding-bottom: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-content p {
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-content ul, .kb-content ol {
 | 
			
		||||
  margin-left: 1.5rem;
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-content code {
 | 
			
		||||
  background-color: var(--color-bg-secondary);
 | 
			
		||||
  padding: 0.125rem 0.25rem;
 | 
			
		||||
  border-radius: 0.25rem;
 | 
			
		||||
  font-family: 'Monaco', 'Courier New', monospace;
 | 
			
		||||
  font-size: 0.875em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-content pre {
 | 
			
		||||
  background-color: var(--color-bg-secondary);
 | 
			
		||||
  padding: 1rem;
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
  overflow-x: auto;
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.kb-content blockquote {
 | 
			
		||||
  border-left: 4px solid var(--color-primary);
 | 
			
		||||
  padding-left: 1rem;
 | 
			
		||||
  margin: 1rem 0;
 | 
			
		||||
  font-style: italic;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fade-in {
 | 
			
		||||
  animation: fadeIn 0.3s ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user