data model overhaul
This commit is contained in:
@@ -169,6 +169,12 @@
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.tag.domain-agnostic {
|
||||
background: #e8f5e8;
|
||||
color: #27ae60;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.skill-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
@@ -182,8 +188,7 @@
|
||||
.skill-beginner { background: #d5f4e6; color: #27ae60; }
|
||||
.skill-intermediate { background: #ffeaa7; color: #e17055; }
|
||||
.skill-advanced { background: #fab1a0; color: #d63031; }
|
||||
.skill-expert { background: #fab1a0; color: #3a1b1b38; }
|
||||
|
||||
.skill-expert { background: #2c3e50; color: #ffffff; }
|
||||
|
||||
.form-section {
|
||||
background: #f8f9fa;
|
||||
@@ -395,8 +400,7 @@
|
||||
.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, #241513, #422825); }
|
||||
|
||||
.skill-fill.expert { background: linear-gradient(90deg, #2c3e50, #34495e); }
|
||||
|
||||
.skill-count {
|
||||
min-width: 30px;
|
||||
@@ -404,6 +408,24 @@
|
||||
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>
|
||||
@@ -444,6 +466,10 @@
|
||||
<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>
|
||||
@@ -470,7 +496,7 @@
|
||||
|
||||
<!-- 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()" />
|
||||
<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>
|
||||
|
||||
@@ -478,6 +504,7 @@
|
||||
<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">
|
||||
@@ -525,6 +552,7 @@
|
||||
<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">
|
||||
@@ -559,6 +587,10 @@
|
||||
<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>
|
||||
|
||||
@@ -572,6 +604,11 @@
|
||||
<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">
|
||||
@@ -604,6 +641,7 @@
|
||||
<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>
|
||||
|
||||
@@ -616,12 +654,13 @@
|
||||
|
||||
<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>
|
||||
<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('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>
|
||||
@@ -639,6 +678,7 @@
|
||||
<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>
|
||||
|
||||
@@ -646,6 +686,11 @@
|
||||
<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>
|
||||
|
||||
@@ -679,12 +724,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
alert('Please select a file');
|
||||
showMessage('Please select a file', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -694,82 +748,40 @@
|
||||
yamlData = jsyaml.load(e.target.result);
|
||||
console.log('Loaded YAML data:', yamlData);
|
||||
updateUI();
|
||||
alert('File loaded successfully!');
|
||||
showMessage('File loaded successfully!');
|
||||
} catch (error) {
|
||||
alert('Error parsing YAML: ' + error.message);
|
||||
showMessage('Error parsing YAML: ' + error.message, 'error');
|
||||
}
|
||||
};
|
||||
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"`;
|
||||
|
||||
// This would load from your existing YAML data
|
||||
// For brevity, I'll just show the structure
|
||||
try {
|
||||
yamlData = jsyaml.load(sampleYaml);
|
||||
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();
|
||||
alert('Sample data loaded successfully!');
|
||||
showMessage('Sample data loaded successfully!');
|
||||
} catch (error) {
|
||||
alert('Error loading sample data: ' + error.message);
|
||||
showMessage('Error loading sample data: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,9 +794,10 @@ phases:
|
||||
// Update statistics
|
||||
updateStats();
|
||||
|
||||
// Update domain and phase checkboxes
|
||||
// Update checkboxes
|
||||
updateDomainCheckboxes();
|
||||
updatePhaseCheckboxes();
|
||||
updateDomainAgnosticCheckboxes();
|
||||
|
||||
// Render tools grid
|
||||
renderToolsGrid();
|
||||
@@ -797,6 +810,7 @@ phases:
|
||||
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;
|
||||
@@ -804,10 +818,8 @@ phases:
|
||||
const knowledgebaseTools = tools.filter(tool => tool.knowledgebase === true).length;
|
||||
document.getElementById('knowledgebaseCount').textContent = knowledgebaseTools;
|
||||
|
||||
// Update tag analytics
|
||||
// Update analytics
|
||||
updateTagAnalytics();
|
||||
|
||||
// Update skill distribution
|
||||
updateSkillDistribution();
|
||||
}
|
||||
|
||||
@@ -816,7 +828,7 @@ phases:
|
||||
|
||||
if (yamlData && yamlData.tools) {
|
||||
yamlData.tools.forEach(tool => {
|
||||
if (tool.tags) {
|
||||
if (tool.tags && Array.isArray(tool.tags)) {
|
||||
tool.tags.forEach(tag => {
|
||||
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
||||
});
|
||||
@@ -831,7 +843,6 @@ phases:
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort tags by count (descending)
|
||||
const sortedTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]);
|
||||
|
||||
container.innerHTML = sortedTags.map(([tag, count]) => `
|
||||
@@ -848,7 +859,9 @@ phases:
|
||||
if (yamlData && yamlData.tools) {
|
||||
yamlData.tools.forEach(tool => {
|
||||
const skill = tool.skillLevel || 'intermediate';
|
||||
skillCounts[skill] = (skillCounts[skill] || 0) + 1;
|
||||
if (skillCounts.hasOwnProperty(skill)) {
|
||||
skillCounts[skill]++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -875,11 +888,8 @@ phases:
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -918,6 +928,23 @@ phases:
|
||||
}
|
||||
}
|
||||
|
||||
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 = '';
|
||||
@@ -952,6 +979,12 @@ phases:
|
||||
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;">
|
||||
@@ -959,7 +992,7 @@ phases:
|
||||
${knowledgebaseIndicator}
|
||||
</div>
|
||||
<p>${tool.description}</p>
|
||||
<div style="margin: 10px 0;">${tags}</div>
|
||||
<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>
|
||||
@@ -995,6 +1028,12 @@ phases:
|
||||
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');
|
||||
@@ -1004,7 +1043,6 @@ phases:
|
||||
cards.forEach((card, index) => {
|
||||
const tool = yamlData.tools[index];
|
||||
|
||||
// Search in multiple fields
|
||||
const searchableText = [
|
||||
tool.name || '',
|
||||
tool.description || '',
|
||||
@@ -1012,6 +1050,7 @@ phases:
|
||||
...(tool.domains || []),
|
||||
...(tool.phases || []),
|
||||
...(tool.platforms || []),
|
||||
...(tool['domain-agnostic-software'] || []),
|
||||
tool.skillLevel || '',
|
||||
tool.license || '',
|
||||
tool.accessType || '',
|
||||
@@ -1030,15 +1069,13 @@ phases:
|
||||
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
|
||||
// Populate form fields
|
||||
document.getElementById('toolName').value = tool.name || '';
|
||||
document.getElementById('description').value = tool.description || '';
|
||||
document.getElementById('skillLevel').value = tool.skillLevel || '';
|
||||
@@ -1049,31 +1086,23 @@ phases:
|
||||
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 checkboxes
|
||||
setCheckboxValues('#platformsCheckbox input', tool.platforms || []);
|
||||
setCheckboxValues('#domainsCheckbox input', tool.domains || []);
|
||||
setCheckboxValues('#phasesCheckbox input', tool.phases || []);
|
||||
setCheckboxValues('#domainAgnosticCheckbox input', tool['domain-agnostic-software'] || []);
|
||||
|
||||
// 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 setCheckboxValues(selector, values) {
|
||||
document.querySelectorAll(selector).forEach(checkbox => {
|
||||
checkbox.checked = values.includes(checkbox.value);
|
||||
});
|
||||
}
|
||||
|
||||
function populateTags(tags) {
|
||||
const container = document.getElementById('tagContainer');
|
||||
// Clear existing tags except input
|
||||
container.querySelectorAll('.removable-tag').forEach(tag => tag.remove());
|
||||
|
||||
tags.forEach(tag => {
|
||||
@@ -1112,47 +1141,58 @@ phases:
|
||||
}
|
||||
|
||||
function saveTool() {
|
||||
if (!yamlData) {
|
||||
yamlData = { tools: [], domains: [], phases: [] };
|
||||
}
|
||||
if (!yamlData.tools) {
|
||||
yamlData.tools = [];
|
||||
}
|
||||
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'),
|
||||
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()
|
||||
};
|
||||
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()
|
||||
};
|
||||
|
||||
// Add statusUrl if provided
|
||||
const statusUrl = document.getElementById('statusUrl').value;
|
||||
if (statusUrl) {
|
||||
tool.statusUrl = statusUrl;
|
||||
// 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');
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1188,7 +1228,7 @@ phases:
|
||||
clearForm();
|
||||
updateStats();
|
||||
renderToolsGrid();
|
||||
alert('Tool deleted successfully!');
|
||||
showMessage('Tool deleted successfully!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1223,7 +1263,7 @@ phases:
|
||||
|
||||
function bulkUpdateSkillLevel() {
|
||||
if (selectedTools.size === 0) {
|
||||
alert('No tools selected');
|
||||
showMessage('No tools selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1232,66 +1272,83 @@ phases:
|
||||
selectedTools.forEach(index => {
|
||||
yamlData.tools[index].skillLevel = skillLevel;
|
||||
});
|
||||
alert(`Updated skill level for ${selectedTools.size} tools`);
|
||||
showMessage(`Updated skill level for ${selectedTools.size} tools`);
|
||||
renderBulkGrid();
|
||||
}
|
||||
}
|
||||
|
||||
function bulkUpdateDomains() {
|
||||
if (selectedTools.size === 0) {
|
||||
alert('No tools selected');
|
||||
showMessage('No tools selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const domains = prompt('Enter domains (comma-separated):');
|
||||
if (domains) {
|
||||
const domainList = domains.split(',').map(d => d.trim());
|
||||
const domainList = domains.split(',').map(d => d.trim()).filter(d => d);
|
||||
selectedTools.forEach(index => {
|
||||
yamlData.tools[index].domains = domainList;
|
||||
});
|
||||
alert(`Updated domains for ${selectedTools.size} tools`);
|
||||
showMessage(`Updated domains for ${selectedTools.size} tools`);
|
||||
renderBulkGrid();
|
||||
}
|
||||
}
|
||||
|
||||
function bulkUpdatePhases() {
|
||||
if (selectedTools.size === 0) {
|
||||
alert('No tools selected');
|
||||
showMessage('No tools selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const phases = prompt('Enter phases (comma-separated):');
|
||||
if (phases) {
|
||||
const phaseList = phases.split(',').map(p => p.trim());
|
||||
const phaseList = phases.split(',').map(p => p.trim()).filter(p => p);
|
||||
selectedTools.forEach(index => {
|
||||
yamlData.tools[index].phases = phaseList;
|
||||
});
|
||||
alert(`Updated phases for ${selectedTools.size} tools`);
|
||||
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) {
|
||||
alert('No tools selected');
|
||||
showMessage('No tools selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = prompt('Enter tags (comma-separated):');
|
||||
if (tags) {
|
||||
const tagList = tags.split(',').map(t => t.trim());
|
||||
const tagList = tags.split(',').map(t => t.trim()).filter(t => t);
|
||||
selectedTools.forEach(index => {
|
||||
yamlData.tools[index].tags = tagList;
|
||||
});
|
||||
alert(`Updated tags for ${selectedTools.size} tools`);
|
||||
updateStats(); // Refresh tag analytics
|
||||
showMessage(`Updated tags for ${selectedTools.size} tools`);
|
||||
updateStats();
|
||||
renderBulkGrid();
|
||||
}
|
||||
}
|
||||
|
||||
function bulkSetKnowledgebase(value) {
|
||||
if (selectedTools.size === 0) {
|
||||
alert('No tools selected');
|
||||
showMessage('No tools selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1307,26 +1364,27 @@ phases:
|
||||
});
|
||||
|
||||
const actionCompleted = value ? 'marked as knowledgebase' : 'removed knowledgebase flag from';
|
||||
alert(`Successfully ${actionCompleted} ${selectedTools.size} tools`);
|
||||
showMessage(`Successfully ${actionCompleted} ${selectedTools.size} tools`);
|
||||
|
||||
updateStats(); // Refresh knowledgebase count
|
||||
updateStats();
|
||||
renderBulkGrid();
|
||||
}
|
||||
|
||||
function bulkClearField(fieldName) {
|
||||
if (selectedTools.size === 0) {
|
||||
alert('No tools selected');
|
||||
showMessage('No tools selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldDisplayName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
||||
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]) {
|
||||
if (fieldName === 'tags' || fieldName === 'domains' || fieldName === 'phases' || fieldName === 'platforms') {
|
||||
const arrayFields = ['tags', 'domains', 'phases', 'platforms', 'domain-agnostic-software'];
|
||||
if (arrayFields.includes(fieldName)) {
|
||||
yamlData.tools[index][fieldName] = [];
|
||||
} else {
|
||||
yamlData.tools[index][fieldName] = '';
|
||||
@@ -1334,10 +1392,10 @@ phases:
|
||||
}
|
||||
});
|
||||
|
||||
alert(`Cleared ${fieldDisplayName} for ${selectedTools.size} tools`);
|
||||
showMessage(`Cleared ${fieldDisplayName} for ${selectedTools.size} tools`);
|
||||
|
||||
if (fieldName === 'tags') {
|
||||
updateStats(); // Refresh tag analytics if tags were cleared
|
||||
updateStats();
|
||||
}
|
||||
|
||||
renderBulkGrid();
|
||||
@@ -1345,7 +1403,7 @@ phases:
|
||||
|
||||
function bulkDelete() {
|
||||
if (selectedTools.size === 0) {
|
||||
alert('No tools selected');
|
||||
showMessage('No tools selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1357,13 +1415,45 @@ phases:
|
||||
selectedTools.clear();
|
||||
updateStats();
|
||||
renderBulkGrid();
|
||||
alert('Tools deleted successfully!');
|
||||
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) {
|
||||
alert('No data to preview');
|
||||
showMessage('No data to preview', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1374,7 +1464,7 @@ phases:
|
||||
|
||||
function exportYAML() {
|
||||
if (!yamlData) {
|
||||
alert('No data to export');
|
||||
showMessage('No data to export', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1389,6 +1479,8 @@ phases:
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
showMessage('YAML file exported successfully!');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user