forensic-pathways/dfir_yaml_editor.html
2025-07-19 17:53:17 +02:00

1487 lines
56 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;
}
.tag.domain-agnostic {
background: #e8f5e8;
color: #27ae60;
font-weight: bold;
}
.skill-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8em;
font-weight: bold;
margin: 5px 0;
}
.skill-novice { background: #d5f4e6; color: #158bc2; }
.skill-beginner { background: #d5f4e6; color: #27ae60; }
.skill-intermediate { background: #ffeaa7; color: #e17055; }
.skill-advanced { background: #fab1a0; color: #d63031; }
.skill-expert { background: #2c3e50; color: #ffffff; }
.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.novice { background: linear-gradient(90deg, #77b7d3, #2e92cc); }
.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-fill.expert { background: linear-gradient(90deg, #2c3e50, #34495e); }
.skill-count {
min-width: 30px;
text-align: right;
font-weight: bold;
color: #2c3e50;
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 12px;
border-radius: 8px;
margin: 10px 0;
border: 1px solid #f5c6cb;
}
.success-message {
background: #d4edda;
color: #155724;
padding: 12px;
border-radius: 8px;
margin: 10px 0;
border: 1px solid #c3e6cb;
}
</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="totalDomainAgnostic">0</div>
<div class="stat-label">Domain-Agnostic</div>
</div>
<div class="stat-card">
<div class="stat-number" id="selfHostedCount">0</div>
<div class="stat-label">CC24-Server</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, phases, or domain-agnostic categories..." 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>
<div id="messageArea"></div>
<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="novice">Novice</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
<option value="expert">Expert</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="server-based">CC24-Server</option>
<option value="commercial">Commercial</option>
<option value="OS">Operating System</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 class="checkbox-item">
<input type="checkbox" id="platform-os" value="OS">
<label for="platform-os">Operating System</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>Domain-Agnostic Software Categories</label>
<div class="checkbox-group" id="domainAgnosticCheckbox"></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="bulkUpdateDomainAgnostic()">Update Domain-Agnostic</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('domain-agnostic-software')">🗑️ Clear Domain-Agnostic</button>
<button class="btn" onclick="bulkClearField('platforms')">🗑️ Clear All Platforms</button>
<button class="btn" onclick="bulkClearField('url')">🗑️ Clear All 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('projectUrl')">🗑️ Clear Project URLs</button>
<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>
<button class="btn btn-warning" onclick="validateYAML()">✅ Validate Structure</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 id="validationResults" class="form-section hidden">
<h3>Validation Results</h3>
<div id="validationContent"></div>
</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 showMessage(message, type = 'success') {
const messageArea = document.getElementById('messageArea');
const className = type === 'error' ? 'error-message' : 'success-message';
messageArea.innerHTML = `<div class="${className}">${message}</div>`;
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}
function loadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
showMessage('Please select a file', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
yamlData = jsyaml.load(e.target.result);
console.log('Loaded YAML data:', yamlData);
updateUI();
showMessage('File loaded successfully!');
} catch (error) {
showMessage('Error parsing YAML: ' + error.message, 'error');
}
};
reader.readAsText(file);
}
function loadSampleData() {
// This would load from your existing YAML data
// For brevity, I'll just show the structure
try {
const sampleData = {
tools: [], // Your existing tools
domains: [], // Your existing domains
phases: [], // Your existing phases
"domain-agnostic-software": [
{
id: "collaboration-general",
name: "Übergreifend & Kollaboration",
description: "Cross-cutting tools and collaboration platforms"
},
{
id: "specific-os",
name: "Betriebssysteme",
description: "Operating Systems which focus on forensics"
}
]
};
yamlData = sampleData;
updateUI();
showMessage('Sample data loaded successfully!');
} catch (error) {
showMessage('Error loading sample data: ' + error.message, 'error');
}
}
function updateUI() {
if (!yamlData) return;
// Show stats section
document.getElementById('statsSection').classList.remove('hidden');
// Update statistics
updateStats();
// Update checkboxes
updateDomainCheckboxes();
updatePhaseCheckboxes();
updateDomainAgnosticCheckboxes();
// 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;
document.getElementById('totalDomainAgnostic').textContent = yamlData['domain-agnostic-software'] ? yamlData['domain-agnostic-software'].length : 0;
const selfHosted = tools.filter(tool => tool.accessType === 'server-based').length;
document.getElementById('selfHostedCount').textContent = selfHosted;
const knowledgebaseTools = tools.filter(tool => tool.knowledgebase === true).length;
document.getElementById('knowledgebaseCount').textContent = knowledgebaseTools;
// Update analytics
updateTagAnalytics();
updateSkillDistribution();
}
function updateTagAnalytics() {
const tagCounts = {};
if (yamlData && yamlData.tools) {
yamlData.tools.forEach(tool => {
if (tool.tags && Array.isArray(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;
}
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 = { novice: 0, beginner: 0, intermediate: 0, advanced: 0, expert: 0 };
if (yamlData && yamlData.tools) {
yamlData.tools.forEach(tool => {
const skill = tool.skillLevel || 'intermediate';
if (skillCounts.hasOwnProperty(skill)) {
skillCounts[skill]++;
}
});
}
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) {
showTab('tools');
document.querySelector('[onclick="showTab(\'tools\')"]').classList.add('active');
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 updateDomainAgnosticCheckboxes() {
const container = document.getElementById('domainAgnosticCheckbox');
container.innerHTML = '';
if (yamlData['domain-agnostic-software']) {
yamlData['domain-agnostic-software'].forEach(category => {
const div = document.createElement('div');
div.className = 'checkbox-item';
div.innerHTML = `
<input type="checkbox" id="domain-agnostic-${category.id}" value="${category.id}">
<label for="domain-agnostic-${category.id}">${category.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>' : '';
// Add domain-agnostic indicators
const domainAgnosticTags = (tool['domain-agnostic-software'] || []).map(cat => {
const categoryName = getDomainAgnosticName(cat);
return `<span class="tag domain-agnostic">🔧 ${categoryName}</span>`;
}).join('');
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}${domainAgnosticTags}</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 getDomainAgnosticName(id) {
if (!yamlData['domain-agnostic-software']) return id;
const category = yamlData['domain-agnostic-software'].find(cat => cat.id === id);
return category ? category.name : id;
}
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];
const searchableText = [
tool.name || '',
tool.description || '',
...(tool.tags || []),
...(tool.domains || []),
...(tool.phases || []),
...(tool.platforms || []),
...(tool['domain-agnostic-software'] || []),
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];
showTab('editor');
document.querySelector('[onclick="showTab(\'editor\')"]').classList.add('active');
document.getElementById('editorTitle').textContent = `Edit Tool: ${tool.name}`;
document.getElementById('deleteBtn').style.display = 'inline-block';
// Populate form fields
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 checkboxes
setCheckboxValues('#platformsCheckbox input', tool.platforms || []);
setCheckboxValues('#domainsCheckbox input', tool.domains || []);
setCheckboxValues('#phasesCheckbox input', tool.phases || []);
setCheckboxValues('#domainAgnosticCheckbox input', tool['domain-agnostic-software'] || []);
populateTags(tool.tags || []);
}
function setCheckboxValues(selector, values) {
document.querySelectorAll(selector).forEach(checkbox => {
checkbox.checked = values.includes(checkbox.value);
});
}
function populateTags(tags) {
const container = document.getElementById('tagContainer');
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() {
try {
if (!yamlData) {
yamlData = { tools: [], domains: [], phases: [], 'domain-agnostic-software': [] };
}
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'),
'domain-agnostic-software': getCheckedValues('#domainAgnosticCheckbox 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()
};
// Clean up empty arrays and null values
Object.keys(tool).forEach(key => {
if (Array.isArray(tool[key]) && tool[key].length === 0) {
delete tool[key];
} else if (tool[key] === '' || tool[key] === null) {
delete tool[key];
}
});
const statusUrl = document.getElementById('statusUrl').value;
if (statusUrl) {
tool.statusUrl = statusUrl;
}
if (currentEditingIndex >= 0) {
yamlData.tools[currentEditingIndex] = tool;
showMessage('Tool updated successfully!');
} else {
yamlData.tools.push(tool);
showMessage('Tool added successfully!');
}
clearForm();
updateStats();
renderToolsGrid();
} catch (error) {
showMessage('Error saving tool: ' + error.message, 'error');
}
}
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();
showMessage('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) {
showMessage('No tools selected', 'error');
return;
}
const skillLevel = prompt('Enter skill level (novice/beginner/intermediate/advanced/expert):');
if (skillLevel && ['novice','beginner', 'intermediate', 'advanced', 'expert'].includes(skillLevel)) {
selectedTools.forEach(index => {
yamlData.tools[index].skillLevel = skillLevel;
});
showMessage(`Updated skill level for ${selectedTools.size} tools`);
renderBulkGrid();
}
}
function bulkUpdateDomains() {
if (selectedTools.size === 0) {
showMessage('No tools selected', 'error');
return;
}
const domains = prompt('Enter domains (comma-separated):');
if (domains) {
const domainList = domains.split(',').map(d => d.trim()).filter(d => d);
selectedTools.forEach(index => {
yamlData.tools[index].domains = domainList;
});
showMessage(`Updated domains for ${selectedTools.size} tools`);
renderBulkGrid();
}
}
function bulkUpdatePhases() {
if (selectedTools.size === 0) {
showMessage('No tools selected', 'error');
return;
}
const phases = prompt('Enter phases (comma-separated):');
if (phases) {
const phaseList = phases.split(',').map(p => p.trim()).filter(p => p);
selectedTools.forEach(index => {
yamlData.tools[index].phases = phaseList;
});
showMessage(`Updated phases for ${selectedTools.size} tools`);
renderBulkGrid();
}
}
function bulkUpdateDomainAgnostic() {
if (selectedTools.size === 0) {
showMessage('No tools selected', 'error');
return;
}
const categories = prompt('Enter domain-agnostic categories (comma-separated):');
if (categories) {
const categoryList = categories.split(',').map(c => c.trim()).filter(c => c);
selectedTools.forEach(index => {
yamlData.tools[index]['domain-agnostic-software'] = categoryList;
});
showMessage(`Updated domain-agnostic categories for ${selectedTools.size} tools`);
renderBulkGrid();
}
}
function bulkUpdateTags() {
if (selectedTools.size === 0) {
showMessage('No tools selected', 'error');
return;
}
const tags = prompt('Enter tags (comma-separated):');
if (tags) {
const tagList = tags.split(',').map(t => t.trim()).filter(t => t);
selectedTools.forEach(index => {
yamlData.tools[index].tags = tagList;
});
showMessage(`Updated tags for ${selectedTools.size} tools`);
updateStats();
renderBulkGrid();
}
}
function bulkSetKnowledgebase(value) {
if (selectedTools.size === 0) {
showMessage('No tools selected', 'error');
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';
showMessage(`Successfully ${actionCompleted} ${selectedTools.size} tools`);
updateStats();
renderBulkGrid();
}
function bulkClearField(fieldName) {
if (selectedTools.size === 0) {
showMessage('No tools selected', 'error');
return;
}
const fieldDisplayName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace('-', ' ');
if (!confirm(`Are you sure you want to clear ${fieldDisplayName} for ${selectedTools.size} selected tools?`)) {
return;
}
selectedTools.forEach(index => {
if (yamlData.tools[index]) {
const arrayFields = ['tags', 'domains', 'phases', 'platforms', 'domain-agnostic-software'];
if (arrayFields.includes(fieldName)) {
yamlData.tools[index][fieldName] = [];
} else {
yamlData.tools[index][fieldName] = '';
}
}
});
showMessage(`Cleared ${fieldDisplayName} for ${selectedTools.size} tools`);
if (fieldName === 'tags') {
updateStats();
}
renderBulkGrid();
}
function bulkDelete() {
if (selectedTools.size === 0) {
showMessage('No tools selected', 'error');
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();
showMessage('Tools deleted successfully!');
}
}
function validateYAML() {
if (!yamlData) {
showMessage('No data to validate', 'error');
return;
}
const validationResults = [];
// Check required sections
if (!yamlData.tools) validationResults.push('❌ Missing tools section');
if (!yamlData.domains) validationResults.push('❌ Missing domains section');
if (!yamlData.phases) validationResults.push('❌ Missing phases section');
// Validate tools
if (yamlData.tools) {
yamlData.tools.forEach((tool, index) => {
if (!tool.name) validationResults.push(`❌ Tool ${index + 1}: Missing name`);
if (!tool.description) validationResults.push(`❌ Tool ${index + 1}: Missing description`);
if (!tool.skillLevel) validationResults.push(`❌ Tool ${index + 1}: Missing skillLevel`);
});
}
const container = document.getElementById('validationContent');
if (validationResults.length === 0) {
container.innerHTML = '<div class="success-message">✅ YAML structure is valid!</div>';
} else {
container.innerHTML = '<div class="error-message">' + validationResults.join('<br>') + '</div>';
}
document.getElementById('validationResults').classList.remove('hidden');
}
function previewYAML() {
if (!yamlData) {
showMessage('No data to preview', 'error');
return;
}
const yamlString = jsyaml.dump(yamlData, { indent: 2 });
document.getElementById('yamlPreviewText').value = yamlString;
document.getElementById('yamlPreview').classList.remove('hidden');
}
function exportYAML() {
if (!yamlData) {
showMessage('No data to export', 'error');
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);
showMessage('YAML file exported successfully!');
}
</script>
</body>
</html>