overcuriousity 5ecbabea90 some cleanup
2025-08-17 17:20:54 +02:00

693 lines
26 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
//src/pages/index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
import ToolCard from '../components/ToolCard.astro';
import ToolFilters from '../components/ToolFilters.astro';
import ToolMatrix from '../components/ToolMatrix.astro';
import AIQueryInterface from '../components/AIQueryInterface.astro';
import TargetedScenarios from '../components/TargetedScenarios.astro';
import { getToolsData } from '../utils/dataService.js';
import { withAPIAuth, getAuthRequirementForContext } from '../utils/auth.js';
const data = await getToolsData();
const tools = data.tools;
const phases = data.phases;
const aiAuthRequired = getAuthRequirementForContext('ai');
let aiAuthContext: { authenticated: boolean; userId: string; session?: any; authRequired: boolean; } | null = null;
if (aiAuthRequired) {
aiAuthContext = await withAPIAuth(Astro.request, 'ai');
}
---
<BaseLayout title="~/">
<section class="approach-hero">
<div class="approach-content">
<h1>ForensicPathways</h1>
<p class="hero-tagline">Das richtige Werkzeug zur richtigen Zeit</p>
<p class="hero-subtitle">
Systematische digitale Forensik nach bewährter NIST SP 800-86 Methodik.<br>
Wählen Sie Ihren Ansatz für die Werkzeugauswahl:
</p>
<div class="ai-hero-spotlight">
<div class="ai-spotlight-content">
<div class="ai-spotlight-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
<circle cx="12" cy="12" r="2"/>
</svg>
</div>
<div class="ai-spotlight-text">
<h3>Forensic AI-Beratung</h3>
<p>Analyse des Untersuchungsszenarios mit Empfehlungen zum Vorgehen</p>
</div>
</div>
{aiAuthRequired && !aiAuthContext?.authenticated ? (
<div class="ai-auth-required">
<button id="ai-login-btn" class="btn btn-accent btn-lg">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
<polyline points="10 17 15 12 10 7"/>
<line x1="15" y1="12" x2="3" y2="12"/>
</svg>
Anmelden für KI-Beratung
</button>
<p style="margin-top: 0.75rem; font-size: 0.875rem; color: var(--color-text-secondary); text-align: center;">
Authentifizierung erforderlich für KI-Features
</p>
</div>
) : (
<button id="ai-query-btn" class="btn btn-accent btn-lg ai-primary-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
</svg>
KI-Beratung starten
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-2">
<line x1="7" y1="17" x2="17" y2="7"/>
<polyline points="7,7 17,7 17,17"/>
</svg>
</button>
)}
<div class="ai-features-mini">
<span class="badge badge-secondary">Workflow-Empfehlungen</span>
<span class="badge badge-secondary">Transparenz</span>
<span class="badge badge-secondary">Sofortige Analyse</span>
</div>
</div>
<div class="approach-selector">
<div class="approach-card methodology" onclick="selectApproach('methodology')">
<div class="approach-header">
<div class="approach-icon">🔍</div>
<h3>Vollständige Ermittlung</h3>
</div>
<p class="approach-description">
Systematisches Vorgehen durch alle vier NIST-Phasen einer forensischen Untersuchung
</p>
<ul class="approach-features">
<li>Methodische Schritt-für-Schritt Anleitung</li>
<li>Vollständige Dokumentationskette</li>
<li>Rechtssichere Beweisführung</li>
<li>Ideal für komplexe Fälle</li>
</ul>
</div>
<div class="approach-card targeted" onclick="selectApproach('targeted')">
<div class="approach-header">
<div class="approach-icon">🎯</div>
<h3>Gezieltes Problem lösen</h3>
</div>
<p class="approach-description">
Direkter Zugang zu spezifischen Tools und Methoden für bekannte Anforderungen
</p>
<ul class="approach-features">
<li>Schnelle Tool-Suche</li>
<li>Spezifische Problemlösungen</li>
<li>Erfahrene Anwender</li>
<li>Effizient für Einzelaufgaben</li>
</ul>
</div>
</div>
<div class="approach-actions">
<p class="approach-info">
<span class="info-icon"></span>
Die lila gekennzeichneten Einträge sind über das Single-Sign-On der CC24-Cloud direkt zugänglich.
Teilnehmer der Seminargruppe CC24-w1 (oder andere Berechtigte) können die gehostete Infrastruktur nutzen.
<a href="/about#support">Kontakt bei Problemen</a>
</p>
<div class="quick-actions">
<a href="/about" class="btn btn-secondary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 16v-4"></path>
<path d="M12 8h.01"></path>
</svg>
Infos, SSO & Zugang
</a>
<a href="/contribute" class="btn" style="background-color: var(--color-warning); color: white; border-color: var(--color-warning);" data-contribute-button="new">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="8.5" cy="7" r="4"/>
<line x1="20" y1="8" x2="20" y2="14"/>
<line x1="23" y1="11" x2="17" y2="11"/>
</svg>
Beitragen
</a>
<button onclick="window.scrollToElementById('filters-section')" class="btn btn-secondary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</svg>
Direkt zur Suche
</a>
</div>
</div>
</div>
</section>
<!-- NIST Methodology Section -->
<section id="methodology-section" class="methodology-section">
<div class="methodology-header">
<h3>NIST SP 800-86 Forensische Methodik</h3>
<p class="methodology-subtitle">
Wählen Sie eine Phase aus dem bewährten Vier-Phasen-Modell
</p>
</div>
<div class="nist-workflow">
{phases.map((phase: any, index: number) => {
const phaseTools = tools.filter((tool: any) =>
tool.phases && tool.phases.includes(phase.id)
);
return (
<div class={`phase-card phase-${phase.id}`} onclick={`selectPhase('${phase.id}')`}>
<div class="phase-number">{index + 1}</div>
<div class="phase-title">{phase.name}</div>
<p class="phase-description">
{phase.description}
</p>
<span class="tool-count">{phaseTools.length} Tools</span>
</div>
);
})}
</div>
<div class="methodology-tip">
<p>
<strong>Tipp:</strong> Für komplexe Ermittlungen empfiehlt sich das sequenzielle Durchlaufen aller Phasen.
Jede Phase baut methodisch auf der vorherigen auf.
</p>
</div>
</section>
<TargetedScenarios />
<section id="filters-section" class="section">
<div class="content-center-lg">
<h3 style="color: var(--color-text); margin-bottom: 0.5rem;">Alle verfügbaren Werkzeuge durchsuchen</h3>
<p style="color: var(--color-text-secondary); margin: 0;">
Nutzen Sie die erweiterten Filter und Kategorien für eine detaillierte Suche
</p>
</div>
<ToolFilters data={data} />
</section>
{aiAuthRequired && !aiAuthContext?.authenticated ? (
<section id="ai-interface" class="ai-interface hidden">
<div class="ai-query-section">
<div class="content-center-lg">
<div class="card" style="text-align: center; padding: 3rem; border-left: 4px solid var(--color-accent);">
<div style="margin-bottom: 2rem;">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.5" style="margin: 0 auto;">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
<circle cx="12" cy="12" r="2"/>
</svg>
</div>
<h2 style="margin-bottom: 1rem; color: var(--color-primary);">Anmeldung erforderlich</h2>
<p style="margin-bottom: 2rem; color: var(--color-text-secondary); line-height: 1.6;">
Für die Nutzung der KI-Beratung ist eine Authentifizierung erforderlich.
Melden Sie sich an, um personalisierten Workflow-Empfehlungen und Tool-Analysen zu erhalten.
</p>
<a href={`/api/auth/login?returnTo=${encodeURIComponent(Astro.url.toString())}`}
class="btn btn-accent btn-lg">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
<polyline points="10 17 15 12 10 7"/>
<line x1="15" y1="12" x2="3" y2="12"/>
</svg>
Anmelden
</a>
</div>
</div>
</div>
</section>
) : (
<AIQueryInterface />
)}
<section id="tools-grid" style="padding-bottom: 2rem;">
<div class="grid-auto-fit" id="tools-container">
{tools.map((tool: any) => (
<ToolCard tool={tool} />
))}
</div>
<div id="no-results" style="display: none; text-align: center; padding: 4rem 0;">
<p class="text-muted" style="font-size: 1.125rem;">No tools found matching your criteria.</p>
</div>
</section>
<ToolMatrix data={data} />
</BaseLayout>
<script define:vars={{ toolsData: data.tools, phases: data.phases, aiAuthRequired: aiAuthRequired, aiAuthenticated: aiAuthContext?.authenticated }}>
window.toolsData = toolsData;
window.selectApproach = function(approach) {
console.log(`Selected approach: ${approach}`);
const aiResults = document.getElementById('ai-results');
if (aiResults) aiResults.style.display = 'none';
document.querySelectorAll('.approach-card').forEach(card => {
card.classList.remove('selected');
});
const selectedCard = document.querySelector(`.approach-card.${approach}`);
if (selectedCard) selectedCard.classList.add('selected');
const methodologySection = document.getElementById('methodology-section');
const targetedSection = document.getElementById('targeted-section');
if (methodologySection) methodologySection.classList.remove('active');
if (targetedSection) targetedSection.classList.remove('active');
if (approach === 'methodology') {
if (methodologySection) {
methodologySection.classList.add('active');
window.scrollToElementById('methodology-section');
}
} else if (approach === 'targeted') {
if (targetedSection) {
targetedSection.classList.add('active');
window.scrollToElementById('targeted-section');
}
}
};
window.selectPhase = function(phase) {
console.log(`Selected NIST phase: ${phase}`);
document.querySelectorAll('.phase-card').forEach(card => {
card.classList.remove('active');
});
const selectedCard = document.querySelector(`.phase-card.phase-${phase}`);
if (selectedCard) {
selectedCard.classList.add('active');
}
const phaseSelect = document.getElementById('phase-select');
if (phaseSelect) {
phaseSelect.value = phase;
const changeEvent = new Event('change', { bubbles: true });
phaseSelect.dispatchEvent(changeEvent);
}
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
if (gridToggle && !gridToggle.classList.contains('active')) {
gridToggle.click();
}
setTimeout(() => {
window.scrollToElementById('tools-grid');
}, 200);
};
document.addEventListener('DOMContentLoaded', () => {
const toolsContainer = document.getElementById('tools-container');
const toolsGrid = document.getElementById('tools-grid');
const matrixContainer = document.getElementById('matrix-container');
const aiInterface = document.getElementById('ai-interface');
const filtersSection = document.getElementById('filters-section');
const noResults = document.getElementById('no-results');
const aiQueryBtn = document.getElementById('ai-query-btn');
const aiLoginBtn = document.getElementById('ai-login-btn');
if (!toolsContainer || !toolsGrid || !matrixContainer || !noResults || !aiInterface || !filtersSection) {
console.error('Required DOM elements not found');
return;
}
if (aiLoginBtn) {
aiLoginBtn.addEventListener('click', () => {
const currentUrl = encodeURIComponent(window.location.href);
window.location.href = `/api/auth/login?returnTo=${currentUrl}`;
});
}
if (aiQueryBtn) {
aiQueryBtn.addEventListener('click', () => {
aiQueryBtn.classList.add('activated');
setTimeout(() => aiQueryBtn.classList.remove('activated'), 400);
switchToView('ai');
window.dispatchEvent(new CustomEvent('viewChanged', { detail: 'ai' }));
if (window.scrollToElementById) {
window.scrollToElementById('ai-interface');
} else {
aiInterface.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
}
function switchToView(view) {
console.log('[VIEW] Switching to view:', view);
const toolsGrid = document.getElementById('tools-grid');
const matrixContainer = document.getElementById('matrix-container');
const aiInterface = document.getElementById('ai-interface');
const filtersSection = document.getElementById('filters-section');
const noResults = document.getElementById('no-results');
const methodologySection = document.getElementById('methodology-section');
const targetedSection = document.getElementById('targeted-section');
if (toolsGrid) toolsGrid.style.display = 'none';
if (matrixContainer) {
matrixContainer.style.display = 'none';
matrixContainer.classList.add('hidden');
}
if (aiInterface) aiInterface.style.display = 'none';
if (noResults) noResults.style.display = 'none';
if (methodologySection) methodologySection.classList.remove('active');
if (targetedSection) targetedSection.classList.remove('active');
switch (view) {
case 'grid':
console.log('[VIEW] Showing grid view');
if (toolsGrid) toolsGrid.style.display = 'block';
if (filtersSection) filtersSection.style.display = 'block';
break;
case 'matrix':
console.log('[VIEW] Showing matrix view');
if (matrixContainer) {
matrixContainer.style.display = 'block';
matrixContainer.classList.remove('hidden');
}
if (filtersSection) filtersSection.style.display = 'block';
break;
case 'ai':
console.log('[VIEW] Showing AI view');
if (aiAuthRequired && !aiAuthenticated) {
console.log('[AUTH] AI access denied, redirecting to login');
const currentUrl = encodeURIComponent(window.location.href);
window.location.href = `/api/auth/login?returnTo=${currentUrl}`;
return;
}
if (aiInterface) aiInterface.style.display = 'block';
if (filtersSection) {
filtersSection.style.display = 'block';
const filterSections = filtersSection.querySelectorAll('.filter-section');
filterSections.forEach((section, index) => {
if (index === filterSections.length - 1) {
section.style.display = 'block';
} else {
section.style.display = 'none';
}
});
}
break;
default:
console.warn('[VIEW] Unknown view:', view);
}
if (view !== 'ai' && filtersSection) {
const filterSections = filtersSection.querySelectorAll('.filter-section');
filterSections.forEach(section => {
section.style.display = 'block';
});
}
}
window.navigateToGrid = function(toolName) {
console.log('Navigating to grid for tool:', toolName);
switchToView('grid');
setTimeout(() => {
if (window.clearAllFilters) {
window.clearAllFilters();
}
setTimeout(() => {
const toolCards = document.querySelectorAll('.tool-card');
let targetCard = null;
toolCards.forEach(card => {
const cardTitle = card.querySelector('h3');
if (cardTitle) {
const titleText = cardTitle.textContent?.replace(/[^\w\s\-\.]/g, '').trim();
if (titleText === toolName) {
targetCard = card;
}
}
});
if (targetCard) {
console.log('Found target card, scrolling...');
window.scrollToElement(targetCard, { block: 'center' });
targetCard.style.animation = 'highlight-flash 2s ease-out';
setTimeout(() => {
if (targetCard) {
targetCard.style.animation = '';
}
}, 2000);
} else {
console.warn('Tool card not found in grid:', toolName);
window.scrollToElementById('tools-grid');
}
}, 300);
}, 200);
};
window.navigateToMatrix = function(toolName) {
console.log('Navigating to matrix for tool:', toolName);
switchToView('matrix');
setTimeout(() => {
const toolChips = document.querySelectorAll('.tool-chip');
let firstMatch = null;
let matchCount = 0;
toolChips.forEach(chip => {
const chipText = chip.textContent?.replace(/📖/g, '').replace(/[^\w\s\-\.]/g, '').trim();
if (chipText === toolName) {
chip.style.animation = 'highlight-flash 2s ease-out';
matchCount++;
if (!firstMatch) {
firstMatch = chip;
}
setTimeout(() => {
chip.style.animation = '';
}, 8000);
}
});
if (firstMatch) {
console.log(`Found ${matchCount} occurrences of tool, highlighting all and scrolling to first`);
window.scrollToElement(firstMatch);
} else {
console.warn('Tool chip not found in matrix:', toolName);
window.scrollToElementById('matrix-container');
}
}, 500);
};
function handleSharedURL() {
const urlParams = new URLSearchParams(window.location.search);
const toolParam = urlParams.get('tool');
const viewParam = urlParams.get('view');
const modalParam = urlParams.get('modal');
if (!toolParam) {
if (viewParam === 'ai') {
switchToView('ai');
}
return;
}
if (!window.findToolByIdentifier) {
console.error('[SHARE] findToolByIdentifier not available, retrying...');
setTimeout(() => handleSharedURL(), 200);
return;
}
const tool = window.findToolByIdentifier(window.toolsData, toolParam);
if (!tool) {
return;
}
const cleanUrl = window.location.protocol + "//" + window.location.host + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
setTimeout(() => {
switch (viewParam) {
case 'grid':
window.navigateToGrid(tool.name);
break;
case 'matrix':
window.navigateToMatrix(tool.name);
break;
case 'modal':
if (modalParam === 'secondary') {
window.showToolDetails(tool.name, 'secondary');
} else {
window.showToolDetails(tool.name, 'primary');
}
break;
default:
window.navigateToGrid(tool.name);
}
}, 300);
}
window.addEventListener('toolsFiltered', (event) => {
const { tools: filtered, semanticSearch } = event.detail;
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
if (currentView === 'matrix' || currentView === 'ai') {
return;
}
const allToolCards = document.querySelectorAll('.tool-card');
const filteredNames = new Set(filtered.map(tool => tool.name.toLowerCase()));
const toolsContainer = document.getElementById('tools-container');
let visibleCount = 0;
if (semanticSearch && filtered.length > 0) {
console.log('[SEMANTIC] Reordering tools by semantic similarity');
const orderedCards = [];
const remainingCards = [];
filtered.forEach(tool => {
const toolName = tool.name.toLowerCase();
const matchingCard = Array.from(allToolCards).find(card =>
card.getAttribute('data-tool-name') === toolName
);
if (matchingCard) {
matchingCard.style.display = 'block';
orderedCards.push(matchingCard);
visibleCount++;
if (tool._semanticSimilarity) {
matchingCard.setAttribute('data-semantic-similarity', tool._semanticSimilarity.toFixed(3));
matchingCard.setAttribute('data-semantic-rank', tool._semanticRank || '');
const header = matchingCard.querySelector('.tool-card-header h3');
if (header && tool._semanticRank <= 3) {
const existingIndicator = header.querySelector('.semantic-rank-indicator');
if (existingIndicator) {
existingIndicator.remove();
}
const indicator = document.createElement('span');
indicator.className = 'semantic-rank-indicator';
indicator.style.cssText = `
display: inline-block;
width: 6px;
height: 6px;
background-color: var(--color-accent);
border-radius: 50%;
margin-left: 0.5rem;
opacity: ${1 - (tool._semanticRank - 1) * 0.3};
`;
indicator.title = `Semantische Relevanz: ${tool._semanticSimilarity.toFixed(3)}`;
header.appendChild(indicator);
}
}
}
});
allToolCards.forEach(card => {
const toolName = card.getAttribute('data-tool-name');
if (!filteredNames.has(toolName)) {
card.style.display = 'none';
remainingCards.push(card);
}
});
const allCards = [...orderedCards, ...remainingCards];
allCards.forEach(card => {
toolsContainer.appendChild(card);
});
} else {
allToolCards.forEach(card => {
const toolName = card.getAttribute('data-tool-name');
card.removeAttribute('data-semantic-similarity');
card.removeAttribute('data-semantic-rank');
const semanticIndicator = card.querySelector('.semantic-rank-indicator');
if (semanticIndicator) {
semanticIndicator.remove();
}
if (filteredNames.has(toolName)) {
card.style.display = 'block';
visibleCount++;
} else {
card.style.display = 'none';
}
});
if (!semanticSearch) {
const originalOrder = Array.from(allToolCards).sort((a, b) => {
const aIndex = Array.from(allToolCards).indexOf(a);
const bIndex = Array.from(allToolCards).indexOf(b);
return aIndex - bIndex;
});
originalOrder.forEach(card => {
toolsContainer.appendChild(card);
});
}
}
if (visibleCount === 0) {
noResults.style.display = 'block';
} else {
noResults.style.display = 'none';
}
if (semanticSearch) {
console.log(`[SEMANTIC] Displayed ${visibleCount} tools in semantic order`);
}
});
window.addEventListener('viewChanged', (event) => {
const view = event.detail;
if (!event.triggeredByButton) {
switchToView(view);
}
});
window.switchToAIView = () => switchToView('ai');
window.switchToView = switchToView;
setTimeout(() => {
handleSharedURL();
}, 1000);
});
</script>
</BaseLayout>