forensic-pathways/dfir_yaml_editor.html
2025-07-15 22:50:21 +02:00

1387 lines
50 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DFIR Tools YAML Editor</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
color: white;
padding: 25px;
text-align: center;
}
.header h1 {
font-size: 2.2em;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
font-size: 1.1em;
}
.tabs {
background: #f8f9fa;
display: flex;
border-bottom: 2px solid #e9ecef;
}
.tab {
padding: 15px 25px;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
font-weight: 500;
}
.tab:hover {
background: #e9ecef;
}
.tab.active {
background: white;
border-bottom-color: #3498db;
color: #3498db;
}
.tab-content {
display: none;
padding: 30px;
min-height: 600px;
}
.tab-content.active {
display: block;
}
.file-input-section {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
border: 2px dashed #dee2e6;
text-align: center;
}
.file-input-section input {
margin: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.btn {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
margin: 5px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 15px rgba(52, 152, 219, 0.3);
}
.btn-danger {
background: linear-gradient(135deg, #e74c3c, #c0392b);
}
.btn-success {
background: linear-gradient(135deg, #27ae60, #229954);
}
.btn-warning {
background: linear-gradient(135deg, #f39c12, #e67e22);
}
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
margin-top: 20px;
}
.tool-card {
background: white;
border: 1px solid #e9ecef;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.tool-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 15px rgba(0,0,0,0.15);
}
.tool-card h3 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 1.3em;
}
.tool-card p {
color: #7f8c8d;
margin-bottom: 15px;
font-size: 0.9em;
line-height: 1.4;
}
.tag {
display: inline-block;
background: #ecf0f1;
color: #34495e;
padding: 4px 8px;
border-radius: 15px;
font-size: 0.8em;
margin: 2px;
}
.skill-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8em;
font-weight: bold;
margin: 5px 0;
}
.skill-beginner { background: #d5f4e6; color: #27ae60; }
.skill-intermediate { background: #ffeaa7; color: #e17055; }
.skill-advanced { background: #fab1a0; color: #d63031; }
.form-section {
background: #f8f9fa;
padding: 25px;
border-radius: 10px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2c3e50;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 12px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s ease;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
.checkbox-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-top: 10px;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 8px;
}
.search-bar {
width: 100%;
padding: 15px;
border: 2px solid #e9ecef;
border-radius: 10px;
font-size: 16px;
margin-bottom: 20px;
}
.bulk-operations {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.stat-number {
font-size: 2em;
font-weight: bold;
color: #3498db;
}
.stat-label {
color: #7f8c8d;
margin-top: 5px;
}
.hidden {
display: none !important;
}
.tag-input-container {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-bottom: 10px;
padding: 10px;
border: 2px solid #e9ecef;
border-radius: 8px;
min-height: 50px;
align-items: center;
}
.tag-input {
border: none;
outline: none;
padding: 5px;
flex: 1;
min-width: 100px;
}
.removable-tag {
background: #3498db;
color: white;
padding: 4px 8px;
border-radius: 15px;
font-size: 0.8em;
display: flex;
align-items: center;
gap: 5px;
}
.remove-tag {
cursor: pointer;
background: rgba(255,255,255,0.3);
border-radius: 50%;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.export-section {
background: #e8f5e8;
border: 1px solid #c3e6c3;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
}
.tag-stat {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin: 5px 0;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #3498db;
}
.tag-stat:hover {
background: #e9ecef;
cursor: pointer;
}
.tag-name {
font-weight: 500;
color: #2c3e50;
}
.tag-count {
background: #3498db;
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.85em;
font-weight: bold;
}
.skill-bar {
display: flex;
align-items: center;
margin: 10px 0;
gap: 15px;
}
.skill-label {
width: 100px;
font-weight: 500;
text-transform: capitalize;
}
.skill-progress {
flex: 1;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.skill-fill {
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
}
.skill-fill.beginner { background: linear-gradient(90deg, #27ae60, #2ecc71); }
.skill-fill.intermediate { background: linear-gradient(90deg, #f39c12, #e67e22); }
.skill-fill.advanced { background: linear-gradient(90deg, #e74c3c, #c0392b); }
.skill-count {
min-width: 30px;
text-align: right;
font-weight: bold;
color: #2c3e50;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔧 DFIR Tools YAML Editor</h1>
<p>Comprehensive editor for Digital Forensics and Incident Response tools database</p>
</div>
<div class="tabs">
<div class="tab active" onclick="showTab('overview')">📊 Overview</div>
<div class="tab" onclick="showTab('tools')">🛠️ Tools</div>
<div class="tab" onclick="showTab('editor')">✏️ Editor</div>
<div class="tab" onclick="showTab('bulk')">📋 Bulk Edit</div>
<div class="tab" onclick="showTab('export')">💾 Export</div>
</div>
<!-- Overview Tab -->
<div id="overview" class="tab-content active">
<div class="file-input-section">
<h3>📁 Load YAML File</h3>
<input type="file" id="fileInput" accept=".yaml,.yml" />
<button class="btn" onclick="loadFile()">Load File</button>
<button class="btn btn-success" onclick="loadSampleData()">Load Sample Data</button>
</div>
<div id="statsSection" class="hidden">
<div class="stats">
<div class="stat-card">
<div class="stat-number" id="totalTools">0</div>
<div class="stat-label">Total Tools</div>
</div>
<div class="stat-card">
<div class="stat-number" id="totalDomains">0</div>
<div class="stat-label">Domains</div>
</div>
<div class="stat-card">
<div class="stat-number" id="totalPhases">0</div>
<div class="stat-label">Phases</div>
</div>
<div class="stat-card">
<div class="stat-number" id="selfHostedCount">0</div>
<div class="stat-label">Self-Hosted</div>
</div>
<div class="stat-card">
<div class="stat-number" id="knowledgebaseCount">0</div>
<div class="stat-label">Knowledgebase</div>
</div>
</div>
<div class="form-section">
<h3>🏷️ Tag Analytics</h3>
<div id="tagAnalytics" style="max-height: 400px; overflow-y: auto;">
<p>Loading tag statistics...</p>
</div>
</div>
<div class="form-section">
<h3>📊 Skill Level Distribution</h3>
<div id="skillDistribution"></div>
</div>
</div>
</div>
<!-- Tools Tab -->
<div id="tools" class="tab-content">
<input type="text" class="search-bar" id="searchBar" placeholder="🔍 Search tools by name, description, tags, domains, or 'knowledgebase'..." onkeyup="filterTools()" />
<div class="tools-grid" id="toolsGrid"></div>
</div>
<!-- Editor Tab -->
<div id="editor" class="tab-content">
<div class="form-section">
<h3 id="editorTitle">Add New Tool</h3>
<form id="toolForm">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="form-group">
<label for="toolName">Tool Name *</label>
<input type="text" id="toolName" required />
</div>
<div class="form-group">
<label for="skillLevel">Skill Level *</label>
<select id="skillLevel" required>
<option value="">Select Level</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
</div>
</div>
<div class="form-group">
<label for="description">Description *</label>
<textarea id="description" rows="3" required></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="form-group">
<label for="url">URL</label>
<input type="url" id="url" />
</div>
<div class="form-group">
<label for="projectUrl">Project URL</label>
<input type="url" id="projectUrl" />
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;">
<div class="form-group">
<label for="license">License</label>
<input type="text" id="license" />
</div>
<div class="form-group">
<label for="accessType">Access Type</label>
<select id="accessType">
<option value="">Select Type</option>
<option value="download">Download</option>
<option value="self-hosted">Self-Hosted</option>
<option value="commercial">Commercial</option>
</select>
</div>
<div class="form-group">
<label for="statusUrl">Status URL</label>
<input type="url" id="statusUrl" />
</div>
</div>
<div class="form-group">
<div class="checkbox-item">
<input type="checkbox" id="knowledgebase" />
<label for="knowledgebase">📚 Knowledgebase Tool</label>
</div>
</div>
<div class="form-group">
<label>Platforms</label>
<div class="checkbox-group" id="platformsCheckbox">
<div class="checkbox-item">
<input type="checkbox" id="platform-windows" value="Windows">
<label for="platform-windows">Windows</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="platform-linux" value="Linux">
<label for="platform-linux">Linux</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="platform-macos" value="macOS">
<label for="platform-macos">macOS</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="platform-web" value="Web">
<label for="platform-web">Web</label>
</div>
</div>
</div>
<div class="form-group">
<label>Domains</label>
<div class="checkbox-group" id="domainsCheckbox"></div>
</div>
<div class="form-group">
<label>Phases</label>
<div class="checkbox-group" id="phasesCheckbox"></div>
</div>
<div class="form-group">
<label for="tagsInput">Tags</label>
<div class="tag-input-container" id="tagContainer">
<input type="text" class="tag-input" id="tagsInput" placeholder="Type and press Enter to add tags..." onkeypress="addTagOnEnter(event)" />
</div>
</div>
<div style="text-align: center; margin-top: 30px;">
<button type="button" class="btn btn-success" onclick="saveTool()">💾 Save Tool</button>
<button type="button" class="btn btn-warning" onclick="clearForm()">🗑️ Clear Form</button>
<button type="button" class="btn btn-danger" id="deleteBtn" onclick="deleteTool()" style="display: none;">🗑️ Delete Tool</button>
</div>
</form>
</div>
</div>
<!-- Bulk Edit Tab -->
<div id="bulk" class="tab-content">
<div class="bulk-operations">
<h3>🔄 Bulk Operations</h3>
<p>Select multiple tools to perform bulk operations</p>
<div style="margin: 20px 0;">
<button class="btn" onclick="selectAllTools()">Select All</button>
<button class="btn" onclick="clearSelection()">Clear Selection</button>
<span id="selectionCount" style="margin-left: 20px; font-weight: bold;">0 selected</span>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px;">
<button class="btn btn-warning" onclick="bulkUpdateSkillLevel()">Update Skill Level</button>
<button class="btn btn-warning" onclick="bulkUpdateDomains()">Update Domains</button>
<button class="btn btn-warning" onclick="bulkUpdatePhases()">Update Phases</button>
<button class="btn btn-warning" onclick="bulkUpdateTags()">Update Tags</button>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;">
<button class="btn" onclick="bulkSetKnowledgebase(true)">📚 Set as Knowledgebase</button>
<button class="btn" onclick="bulkSetKnowledgebase(false)">📖 Remove Knowledgebase</button>
<button class="btn" onclick="bulkClearField('tags')">🗑️ Clear All Tags</button>
<button class="btn" onclick="bulkClearField('domains')">🗑️ Clear All Domains</button>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;">
<button class="btn" onclick="bulkClearField('phases')">🗑️ Clear All Phases</button>
<button class="btn" onclick="bulkClearField('platforms')">🗑️ Clear All Platforms</button>
<button class="btn" onclick="bulkClearField('url')">🗑️ Clear All URLs</button>
<button class="btn" onclick="bulkClearField('projectUrl')">🗑️ Clear Project URLs</button>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;">
<button class="btn" onclick="bulkClearField('statusUrl')">🗑️ Clear Status URLs</button>
<button class="btn btn-danger" onclick="bulkDelete()">🗑️ Delete Selected</button>
</div>
</div>
<div class="tools-grid" id="bulkToolsGrid"></div>
</div>
<!-- Export Tab -->
<div id="export" class="tab-content">
<div class="export-section">
<h3>💾 Export Options</h3>
<p>Download your edited YAML file</p>
<div style="margin: 20px 0;">
<button class="btn btn-success" onclick="exportYAML()">📥 Download YAML</button>
<button class="btn" onclick="previewYAML()">👁️ Preview YAML</button>
</div>
</div>
<div id="yamlPreview" class="form-section hidden">
<h3>YAML Preview</h3>
<textarea readonly style="width: 100%; height: 400px; font-family: monospace; font-size: 12px;" id="yamlPreviewText"></textarea>
</div>
</div>
</div>
<script>
let yamlData = null;
let currentEditingIndex = -1;
let selectedTools = new Set();
function showTab(tabName) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Remove active class from all tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected tab content
document.getElementById(tabName).classList.add('active');
// Add active class to clicked tab
event.target.classList.add('active');
// Refresh content based on tab
if (tabName === 'tools') {
renderToolsGrid();
} else if (tabName === 'bulk') {
renderBulkGrid();
}
}
function loadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert('Please select a file');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
yamlData = jsyaml.load(e.target.result);
console.log('Loaded YAML data:', yamlData);
updateUI();
alert('File loaded successfully!');
} catch (error) {
alert('Error parsing YAML: ' + error.message);
}
};
reader.readAsText(file);
}
function loadSampleData() {
// Load the provided sample data
const sampleYaml = `tools:
- name: "Autopsy"
description: "Open-Source digitale Forensik-Plattform mit grafischer Benutzeroberfläche für Festplatten- und Dateisystemanalyse"
domains:
- "storage-file-system"
- "application-code"
phases:
- "Auswertung"
- "Analyse"
platforms: ["Windows", "Linux", "macOS"]
skillLevel: "intermediate"
accessType: "download"
url: "https://www.autopsy.com/"
projectUrl: ""
license: "Apache 2.0"
tags: ["disk", "recovery", "timeline", "opensource"]
- name: "Volatility 3"
description: "Fortgeschrittenes Memory-Forensik-Framework für Incident Response und Malware-Analyse mit Plugin-Architektur"
domains:
- "memory-runtime"
phases:
- "Auswertung"
- "Analyse"
platforms: ["Windows", "Linux", "macOS"]
skillLevel: "advanced"
accessType: "download"
url: "https://www.volatilityfoundation.org/"
projectUrl: ""
license: "VSL"
tags: ["memory", "malware", "runtime", "plugins"]
domains:
- id: "storage-file-system"
name: "Storage & File System Artifacts"
- id: "memory-runtime"
name: "Memory & Runtime Artifacts"
- id: "network-communication"
name: "Network & Communication Artifacts"
- id: "application-code"
name: "Application & Code Artifacts"
- id: "multimedia-content"
name: "Multimedia & Content Artifacts"
- id: "transaction-financial"
name: "Transaction & Financial Artifacts"
- id: "platform-infrastructure"
name: "Platform & Infrastructure Artifacts"
phases:
- id: "data-collection"
name: "Datensammlung"
- id: "Auswertung"
name: "Auswertung"
- id: "Analyse"
name: "Analyse"
- id: "Bericht & Präsentation"
name: "Bericht & Präsentation"
- id: "collaboration"
name: "Übergreifend & Kollaboration"`;
try {
yamlData = jsyaml.load(sampleYaml);
updateUI();
alert('Sample data loaded successfully!');
} catch (error) {
alert('Error loading sample data: ' + error.message);
}
}
function updateUI() {
if (!yamlData) return;
// Show stats section
document.getElementById('statsSection').classList.remove('hidden');
// Update statistics
updateStats();
// Update domain and phase checkboxes
updateDomainCheckboxes();
updatePhaseCheckboxes();
// Render tools grid
renderToolsGrid();
}
function updateStats() {
if (!yamlData || !yamlData.tools) return;
const tools = yamlData.tools;
document.getElementById('totalTools').textContent = tools.length;
document.getElementById('totalDomains').textContent = yamlData.domains ? yamlData.domains.length : 0;
document.getElementById('totalPhases').textContent = yamlData.phases ? yamlData.phases.length : 0;
const selfHosted = tools.filter(tool => tool.accessType === 'self-hosted').length;
document.getElementById('selfHostedCount').textContent = selfHosted;
const knowledgebaseTools = tools.filter(tool => tool.knowledgebase === true).length;
document.getElementById('knowledgebaseCount').textContent = knowledgebaseTools;
// Update tag analytics
updateTagAnalytics();
// Update skill distribution
updateSkillDistribution();
}
function updateTagAnalytics() {
const tagCounts = {};
if (yamlData && yamlData.tools) {
yamlData.tools.forEach(tool => {
if (tool.tags) {
tool.tags.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
}
});
}
const container = document.getElementById('tagAnalytics');
if (Object.keys(tagCounts).length === 0) {
container.innerHTML = '<p style="color: #7f8c8d; font-style: italic;">No tags found</p>';
return;
}
// Sort tags by count (descending)
const sortedTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]);
container.innerHTML = sortedTags.map(([tag, count]) => `
<div class="tag-stat" onclick="searchForTag('${tag}')">
<span class="tag-name">${tag}</span>
<span class="tag-count">${count}</span>
</div>
`).join('');
}
function updateSkillDistribution() {
const skillCounts = { beginner: 0, intermediate: 0, advanced: 0 };
if (yamlData && yamlData.tools) {
yamlData.tools.forEach(tool => {
const skill = tool.skillLevel || 'intermediate';
skillCounts[skill] = (skillCounts[skill] || 0) + 1;
});
}
const total = Object.values(skillCounts).reduce((sum, count) => sum + count, 0);
const container = document.getElementById('skillDistribution');
if (total === 0) {
container.innerHTML = '<p style="color: #7f8c8d; font-style: italic;">No skill data available</p>';
return;
}
container.innerHTML = Object.entries(skillCounts).map(([skill, count]) => {
const percentage = total > 0 ? (count / total) * 100 : 0;
return `
<div class="skill-bar">
<div class="skill-label">${skill}</div>
<div class="skill-progress">
<div class="skill-fill ${skill}" style="width: ${percentage}%"></div>
</div>
<div class="skill-count">${count}</div>
</div>
`;
}).join('');
}
function searchForTag(tag) {
// Switch to tools tab
showTab('tools');
document.querySelector('[onclick="showTab(\'tools\')"]').classList.add('active');
// Set search bar value and filter
document.getElementById('searchBar').value = tag;
filterTools();
}
function updateDomainCheckboxes() {
const container = document.getElementById('domainsCheckbox');
container.innerHTML = '';
if (yamlData.domains) {
yamlData.domains.forEach(domain => {
const div = document.createElement('div');
div.className = 'checkbox-item';
div.innerHTML = `
<input type="checkbox" id="domain-${domain.id}" value="${domain.id}">
<label for="domain-${domain.id}">${domain.name}</label>
`;
container.appendChild(div);
});
}
}
function updatePhaseCheckboxes() {
const container = document.getElementById('phasesCheckbox');
container.innerHTML = '';
if (yamlData.phases) {
yamlData.phases.forEach(phase => {
const div = document.createElement('div');
div.className = 'checkbox-item';
div.innerHTML = `
<input type="checkbox" id="phase-${phase.id}" value="${phase.id}">
<label for="phase-${phase.id}">${phase.name}</label>
`;
container.appendChild(div);
});
}
}
function renderToolsGrid() {
const container = document.getElementById('toolsGrid');
container.innerHTML = '';
if (!yamlData || !yamlData.tools) return;
yamlData.tools.forEach((tool, index) => {
const card = createToolCard(tool, index);
container.appendChild(card);
});
}
function renderBulkGrid() {
const container = document.getElementById('bulkToolsGrid');
container.innerHTML = '';
if (!yamlData || !yamlData.tools) return;
yamlData.tools.forEach((tool, index) => {
const card = createBulkToolCard(tool, index);
container.appendChild(card);
});
updateSelectionCount();
}
function createToolCard(tool, index) {
const card = document.createElement('div');
card.className = 'tool-card';
const skillClass = `skill-${tool.skillLevel || 'intermediate'}`;
const tags = (tool.tags || []).map(tag => `<span class="tag">${tag}</span>`).join('');
const knowledgebaseIndicator = tool.knowledgebase ? '<span class="tag" style="background: #e8f5e8; color: #27ae60; font-weight: bold;">📚 Knowledgebase</span>' : '';
card.innerHTML = `
<h3>${tool.name}</h3>
<div style="margin: 5px 0;">
<div class="skill-badge ${skillClass}">${tool.skillLevel || 'intermediate'}</div>
${knowledgebaseIndicator}
</div>
<p>${tool.description}</p>
<div style="margin: 10px 0;">${tags}</div>
<div style="margin-top: 15px;">
<button class="btn" onclick="editTool(${index})">✏️ Edit</button>
<button class="btn btn-danger" onclick="confirmDelete(${index})">🗑️ Delete</button>
</div>
`;
return card;
}
function createBulkToolCard(tool, index) {
const card = document.createElement('div');
card.className = 'tool-card';
const skillClass = `skill-${tool.skillLevel || 'intermediate'}`;
const isSelected = selectedTools.has(index);
const knowledgebaseIndicator = tool.knowledgebase ? '<span class="tag" style="background: #e8f5e8; color: #27ae60; font-weight: bold; margin-left: 10px;">📚 KB</span>' : '';
card.innerHTML = `
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">
<input type="checkbox" ${isSelected ? 'checked' : ''} onchange="toggleToolSelection(${index})" />
<h3 style="margin: 0;">${tool.name}</h3>
${knowledgebaseIndicator}
</div>
<div class="skill-badge ${skillClass}">${tool.skillLevel || 'intermediate'}</div>
<p>${tool.description}</p>
`;
if (isSelected) {
card.style.border = '2px solid #3498db';
card.style.backgroundColor = '#f8f9ff';
}
return card;
}
function filterTools() {
const searchTerm = document.getElementById('searchBar').value.toLowerCase();
const cards = document.querySelectorAll('#toolsGrid .tool-card');
if (!yamlData || !yamlData.tools) return;
cards.forEach((card, index) => {
const tool = yamlData.tools[index];
// Search in multiple fields
const searchableText = [
tool.name || '',
tool.description || '',
...(tool.tags || []),
...(tool.domains || []),
...(tool.phases || []),
...(tool.platforms || []),
tool.skillLevel || '',
tool.license || '',
tool.accessType || '',
tool.knowledgebase ? 'knowledgebase' : ''
].join(' ').toLowerCase();
if (searchableText.includes(searchTerm)) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
}
function editTool(index) {
currentEditingIndex = index;
const tool = yamlData.tools[index];
// Switch to editor tab
showTab('editor');
document.querySelector('[onclick="showTab(\'editor\')"]').classList.add('active');
// Update form title
document.getElementById('editorTitle').textContent = `Edit Tool: ${tool.name}`;
document.getElementById('deleteBtn').style.display = 'inline-block';
// Populate form
document.getElementById('toolName').value = tool.name || '';
document.getElementById('description').value = tool.description || '';
document.getElementById('skillLevel').value = tool.skillLevel || '';
document.getElementById('url').value = tool.url || '';
document.getElementById('projectUrl').value = tool.projectUrl || '';
document.getElementById('license').value = tool.license || '';
document.getElementById('accessType').value = tool.accessType || '';
document.getElementById('statusUrl').value = tool.statusUrl || '';
document.getElementById('knowledgebase').checked = tool.knowledgebase || false;
// Set platforms
const platforms = tool.platforms || [];
document.querySelectorAll('#platformsCheckbox input').forEach(checkbox => {
checkbox.checked = platforms.includes(checkbox.value);
});
// Set domains
const domains = tool.domains || [];
document.querySelectorAll('#domainsCheckbox input').forEach(checkbox => {
checkbox.checked = domains.includes(checkbox.value);
});
// Set phases
const phases = tool.phases || [];
document.querySelectorAll('#phasesCheckbox input').forEach(checkbox => {
checkbox.checked = phases.includes(checkbox.value);
});
// Set tags
populateTags(tool.tags || []);
}
function populateTags(tags) {
const container = document.getElementById('tagContainer');
// Clear existing tags except input
container.querySelectorAll('.removable-tag').forEach(tag => tag.remove());
tags.forEach(tag => {
addTag(tag);
});
}
function addTagOnEnter(event) {
if (event.key === 'Enter') {
event.preventDefault();
const input = event.target;
const tag = input.value.trim();
if (tag) {
addTag(tag);
input.value = '';
}
}
}
function addTag(tagText) {
const container = document.getElementById('tagContainer');
const input = container.querySelector('.tag-input');
const tagElement = document.createElement('span');
tagElement.className = 'removable-tag';
tagElement.innerHTML = `
${tagText}
<span class="remove-tag" onclick="removeTag(this)">×</span>
`;
container.insertBefore(tagElement, input);
}
function removeTag(element) {
element.parentElement.remove();
}
function saveTool() {
if (!yamlData) {
yamlData = { tools: [], domains: [], phases: [] };
}
if (!yamlData.tools) {
yamlData.tools = [];
}
const tool = {
name: document.getElementById('toolName').value,
description: document.getElementById('description').value,
domains: getCheckedValues('#domainsCheckbox input:checked'),
phases: getCheckedValues('#phasesCheckbox input:checked'),
platforms: getCheckedValues('#platformsCheckbox input:checked'),
skillLevel: document.getElementById('skillLevel').value,
accessType: document.getElementById('accessType').value,
url: document.getElementById('url').value,
projectUrl: document.getElementById('projectUrl').value,
license: document.getElementById('license').value,
knowledgebase: document.getElementById('knowledgebase').checked,
tags: getTags()
};
// Add statusUrl if provided
const statusUrl = document.getElementById('statusUrl').value;
if (statusUrl) {
tool.statusUrl = statusUrl;
}
if (currentEditingIndex >= 0) {
// Update existing tool
yamlData.tools[currentEditingIndex] = tool;
alert('Tool updated successfully!');
} else {
// Add new tool
yamlData.tools.push(tool);
alert('Tool added successfully!');
}
clearForm();
updateStats();
renderToolsGrid();
}
function getCheckedValues(selector) {
return Array.from(document.querySelectorAll(selector)).map(checkbox => checkbox.value);
}
function getTags() {
const tags = [];
document.querySelectorAll('#tagContainer .removable-tag').forEach(tagElement => {
const text = tagElement.textContent.replace('×', '').trim();
if (text) tags.push(text);
});
return tags;
}
function clearForm() {
document.getElementById('toolForm').reset();
document.getElementById('tagContainer').querySelectorAll('.removable-tag').forEach(tag => tag.remove());
document.getElementById('editorTitle').textContent = 'Add New Tool';
document.getElementById('deleteBtn').style.display = 'none';
currentEditingIndex = -1;
}
function confirmDelete(index) {
if (confirm(`Are you sure you want to delete "${yamlData.tools[index].name}"?`)) {
deleteTool(index);
}
}
function deleteTool(index = currentEditingIndex) {
if (index >= 0 && yamlData && yamlData.tools) {
yamlData.tools.splice(index, 1);
clearForm();
updateStats();
renderToolsGrid();
alert('Tool deleted successfully!');
}
}
// Bulk operations
function toggleToolSelection(index) {
if (selectedTools.has(index)) {
selectedTools.delete(index);
} else {
selectedTools.add(index);
}
updateSelectionCount();
renderBulkGrid();
}
function selectAllTools() {
if (yamlData && yamlData.tools) {
yamlData.tools.forEach((_, index) => selectedTools.add(index));
updateSelectionCount();
renderBulkGrid();
}
}
function clearSelection() {
selectedTools.clear();
updateSelectionCount();
renderBulkGrid();
}
function updateSelectionCount() {
document.getElementById('selectionCount').textContent = `${selectedTools.size} selected`;
}
function bulkUpdateSkillLevel() {
if (selectedTools.size === 0) {
alert('No tools selected');
return;
}
const skillLevel = prompt('Enter skill level (beginner/intermediate/advanced):');
if (skillLevel && ['beginner', 'intermediate', 'advanced'].includes(skillLevel)) {
selectedTools.forEach(index => {
yamlData.tools[index].skillLevel = skillLevel;
});
alert(`Updated skill level for ${selectedTools.size} tools`);
renderBulkGrid();
}
}
function bulkUpdateDomains() {
if (selectedTools.size === 0) {
alert('No tools selected');
return;
}
const domains = prompt('Enter domains (comma-separated):');
if (domains) {
const domainList = domains.split(',').map(d => d.trim());
selectedTools.forEach(index => {
yamlData.tools[index].domains = domainList;
});
alert(`Updated domains for ${selectedTools.size} tools`);
renderBulkGrid();
}
}
function bulkUpdatePhases() {
if (selectedTools.size === 0) {
alert('No tools selected');
return;
}
const phases = prompt('Enter phases (comma-separated):');
if (phases) {
const phaseList = phases.split(',').map(p => p.trim());
selectedTools.forEach(index => {
yamlData.tools[index].phases = phaseList;
});
alert(`Updated phases for ${selectedTools.size} tools`);
renderBulkGrid();
}
}
function bulkUpdateTags() {
if (selectedTools.size === 0) {
alert('No tools selected');
return;
}
const tags = prompt('Enter tags (comma-separated):');
if (tags) {
const tagList = tags.split(',').map(t => t.trim());
selectedTools.forEach(index => {
yamlData.tools[index].tags = tagList;
});
alert(`Updated tags for ${selectedTools.size} tools`);
updateStats(); // Refresh tag analytics
renderBulkGrid();
}
}
function bulkSetKnowledgebase(value) {
if (selectedTools.size === 0) {
alert('No tools selected');
return;
}
const action = value ? 'set as knowledgebase' : 'remove knowledgebase flag from';
if (!confirm(`Are you sure you want to ${action} ${selectedTools.size} selected tools?`)) {
return;
}
selectedTools.forEach(index => {
if (yamlData.tools[index]) {
yamlData.tools[index].knowledgebase = value;
}
});
const actionCompleted = value ? 'marked as knowledgebase' : 'removed knowledgebase flag from';
alert(`Successfully ${actionCompleted} ${selectedTools.size} tools`);
updateStats(); // Refresh knowledgebase count
renderBulkGrid();
}
function bulkClearField(fieldName) {
if (selectedTools.size === 0) {
alert('No tools selected');
return;
}
const fieldDisplayName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
if (!confirm(`Are you sure you want to clear ${fieldDisplayName} for ${selectedTools.size} selected tools?`)) {
return;
}
selectedTools.forEach(index => {
if (yamlData.tools[index]) {
if (fieldName === 'tags' || fieldName === 'domains' || fieldName === 'phases' || fieldName === 'platforms') {
yamlData.tools[index][fieldName] = [];
} else {
yamlData.tools[index][fieldName] = '';
}
}
});
alert(`Cleared ${fieldDisplayName} for ${selectedTools.size} tools`);
if (fieldName === 'tags') {
updateStats(); // Refresh tag analytics if tags were cleared
}
renderBulkGrid();
}
function bulkDelete() {
if (selectedTools.size === 0) {
alert('No tools selected');
return;
}
if (confirm(`Are you sure you want to delete ${selectedTools.size} tools?`)) {
const indices = Array.from(selectedTools).sort((a, b) => b - a);
indices.forEach(index => {
yamlData.tools.splice(index, 1);
});
selectedTools.clear();
updateStats();
renderBulkGrid();
alert('Tools deleted successfully!');
}
}
function previewYAML() {
if (!yamlData) {
alert('No data to preview');
return;
}
const yamlString = jsyaml.dump(yamlData, { indent: 2 });
document.getElementById('yamlPreviewText').value = yamlString;
document.getElementById('yamlPreview').classList.remove('hidden');
}
function exportYAML() {
if (!yamlData) {
alert('No data to export');
return;
}
const yamlString = jsyaml.dump(yamlData, { indent: 2 });
const blob = new Blob([yamlString], { type: 'text/yaml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'dfir-tools.yaml';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
</script>
</body>
</html>