1487 lines
56 KiB
HTML
1487 lines
56 KiB
HTML
<!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">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, 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="self-hosted">Self-Hosted</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 === 'self-hosted').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> |