Files
Bentlybro 364ad3343b Escape HTML in rule modals and bulk action content
Added HTML escaping in rules.js to prevent XSS when displaying rule details and editing forms. Updated manual_review/index.html to use DOM manipulation instead of HTML string concatenation for selected content in the bulk action modal, improving security and robustness.
2025-10-07 22:49:40 +01:00

324 lines
14 KiB
JavaScript

// Rules page functionality
document.addEventListener('DOMContentLoaded', function() {
// Helper function to escape HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Store rule data in a hidden element to avoid HTML attribute issues
const ruleDataElement = document.getElementById('ruleDataStore');
const ruleDataStore = ruleDataElement ? JSON.parse(ruleDataElement.textContent) : {};
// Handle view rule button clicks
document.querySelectorAll('.view-rule-btn').forEach(function(button) {
button.addEventListener('click', function() {
const ruleId = this.getAttribute('data-rule-id');
const ruleName = this.getAttribute('data-rule-name');
const ruleType = this.getAttribute('data-rule-type');
// Get rule data from the store
const ruleData = ruleDataStore[ruleId] || {};
console.log('Rule data (view):', ruleData); // Debug log
const ruleAction = this.getAttribute('data-rule-action');
const ruleDescription = this.getAttribute('data-rule-description');
const rulePriority = this.getAttribute('data-rule-priority');
// Populate modal
document.getElementById('modalRuleName').textContent = ruleName;
document.getElementById('modalRuleType').innerHTML = `<span class="badge bg-info">${escapeHtml(ruleType)}</span>`;
document.getElementById('modalRuleDescription').textContent = ruleDescription || 'No description provided';
document.getElementById('modalRuleAction').innerHTML = `<span class="badge bg-primary">${escapeHtml(ruleAction)}</span>`;
document.getElementById('modalRulePriority').textContent = rulePriority;
// Format rule configuration based on type
let configHtml = '';
if (ruleType === 'keyword') {
const keywords = ruleData.keywords ? ruleData.keywords.map(k => escapeHtml(k)).join(', ') : 'None';
configHtml = `
<strong>Keywords:</strong> ${keywords}<br>
<strong>Case Sensitive:</strong> ${ruleData.case_sensitive ? 'Yes' : 'No'}
`;
} else if (ruleType === 'regex') {
configHtml = `
<strong>Pattern:</strong> <code>${escapeHtml(ruleData.pattern || 'None')}</code><br>
<strong>Flags:</strong> ${escapeHtml(ruleData.flags || 'None')}
`;
} else if (ruleType === 'ai_prompt') {
configHtml = `
<strong>Custom Prompt:</strong><br>
<pre class="mt-2">${escapeHtml(ruleData.prompt || 'None')}</pre>
`;
}
document.getElementById('modalRuleConfig').innerHTML = configHtml;
// Show modal
const modal = new bootstrap.Modal(document.getElementById('ruleDetailsModal'));
modal.show();
});
});
// Handle edit rule button clicks
document.querySelectorAll('.edit-rule-btn').forEach(function(button) {
button.addEventListener('click', function() {
const ruleId = this.getAttribute('data-rule-id');
const ruleName = this.getAttribute('data-rule-name');
const ruleType = this.getAttribute('data-rule-type');
// Get rule data from the store
const ruleData = ruleDataStore[ruleId] || {};
const ruleAction = this.getAttribute('data-rule-action');
const ruleDescription = this.getAttribute('data-rule-description');
const rulePriority = this.getAttribute('data-rule-priority');
// Populate edit form
document.getElementById('editRuleId').value = ruleId;
document.getElementById('editRuleName').value = ruleName;
document.getElementById('editRuleDescription').value = ruleDescription;
document.getElementById('editRuleAction').value = ruleAction;
document.getElementById('editRulePriority').value = rulePriority;
document.getElementById('editRuleType').value = ruleType;
document.getElementById('editRuleTypeDisplay').value = ruleType.charAt(0).toUpperCase() + ruleType.slice(1);
// Populate rule-specific configuration
let configHtml = '';
if (ruleType === 'keyword') {
const keywordsValue = ruleData.keywords ? (Array.isArray(ruleData.keywords) ? ruleData.keywords.join('\n') : ruleData.keywords) : '';
configHtml = `
<div class="card bg-light">
<div class="card-header">
<h6 class="mb-0">Keyword Configuration</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label for="editKeywords" class="form-label">Keywords *</label>
<textarea class="form-control" id="editKeywords" name="keywords" rows="3" placeholder="Enter keywords, one per line or comma-separated">${escapeHtml(keywordsValue)}</textarea>
<div class="form-text">Enter keywords to match. One per line or comma-separated.</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editCaseSensitive" name="case_sensitive" ${ruleData.case_sensitive ? 'checked' : ''}>
<label class="form-check-label" for="editCaseSensitive">
Case sensitive matching
</label>
</div>
</div>
</div>
`;
} else if (ruleType === 'regex') {
configHtml = `
<div class="card bg-light">
<div class="card-header">
<h6 class="mb-0">Regex Configuration</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label for="editRegexPattern" class="form-label">Pattern *</label>
<input type="text" class="form-control" id="editRegexPattern" name="pattern" value="${escapeHtml(ruleData.pattern || '')}" placeholder="Enter regex pattern">
<div class="form-text">Enter a valid regular expression pattern.</div>
</div>
</div>
</div>
`;
} else if (ruleType === 'ai_prompt') {
configHtml = `
<div class="card bg-light">
<div class="card-header">
<h6 class="mb-0">AI Prompt Configuration</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label for="editAiPrompt" class="form-label">Custom Prompt *</label>
<textarea class="form-control" id="editAiPrompt" name="prompt" rows="4" placeholder="Enter your custom prompt for AI analysis...">${escapeHtml(ruleData.prompt || '')}</textarea>
<div class="form-text">Describe what you want the AI to analyze and how it should respond.</div>
</div>
</div>
</div>
`;
}
document.getElementById('editRuleConfig').innerHTML = configHtml;
// Show modal
const modal = new bootstrap.Modal(document.getElementById('editRuleModal'));
modal.show();
});
});
// Handle edit from view button
const editFromViewBtn = document.getElementById('editFromViewBtn');
if (editFromViewBtn) {
editFromViewBtn.addEventListener('click', function() {
// Close view modal and open edit modal
bootstrap.Modal.getInstance(document.getElementById('ruleDetailsModal')).hide();
// Find the edit button for this rule and click it
const ruleId = document.querySelector('.view-rule-btn').getAttribute('data-rule-id');
const editBtn = document.querySelector(`[data-rule-id="${ruleId}"].edit-rule-btn`);
if (editBtn) {
editBtn.click();
}
});
}
// Handle toggle rule (activate/deactivate) button clicks
document.querySelectorAll('.toggle-rule-btn').forEach(function(button) {
button.addEventListener('click', function() {
const ruleId = this.getAttribute('data-rule-id');
const action = this.getAttribute('data-action');
const projectId = window.projectId || document.querySelector('[data-project-id]')?.getAttribute('data-project-id');
if (!projectId) {
console.error('Project ID not found');
return;
}
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
fetch(`/dashboard/projects/${projectId}/rules/${ruleId}/toggle`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({ action: action })
})
.then(response => {
if (!response.ok) {
return response.json().then(err => Promise.reject(err));
}
return response.json();
})
.then(data => {
if (data.success) {
location.reload(); // Refresh to show updated status
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error updating rule status');
});
});
});
// Handle delete rule button clicks
document.querySelectorAll('.delete-rule-btn').forEach(function(button) {
button.addEventListener('click', function() {
const ruleId = this.getAttribute('data-rule-id');
const ruleName = this.getAttribute('data-rule-name');
document.getElementById('deleteRuleName').textContent = ruleName;
document.getElementById('confirmDeleteBtn').setAttribute('data-rule-id', ruleId);
const modal = new bootstrap.Modal(document.getElementById('deleteRuleModal'));
modal.show();
});
});
// Handle confirm delete
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
if (confirmDeleteBtn) {
confirmDeleteBtn.addEventListener('click', function() {
const ruleId = this.getAttribute('data-rule-id');
const projectId = window.projectId || document.querySelector('[data-project-id]')?.getAttribute('data-project-id');
if (!projectId) {
console.error('Project ID not found');
return;
}
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
fetch(`/dashboard/projects/${projectId}/rules/${ruleId}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload(); // Refresh to show updated list
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error deleting rule');
});
});
}
// Handle edit rule form submission
const editRuleForm = document.getElementById('editRuleForm');
if (editRuleForm) {
editRuleForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const ruleId = formData.get('rule_id');
const projectId = window.projectId || document.querySelector('[data-project-id]')?.getAttribute('data-project-id');
if (!projectId) {
console.error('Project ID not found');
return;
}
// Build rule data based on type
const ruleType = formData.get('rule_type');
let ruleData = {};
if (ruleType === 'keyword') {
const keywords = formData.get('keywords').split(/[,\n]/).map(k => k.trim()).filter(k => k);
ruleData = {
keywords: keywords,
case_sensitive: formData.get('case_sensitive') === 'on'
};
} else if (ruleType === 'regex') {
ruleData = {
pattern: formData.get('pattern'),
flags: 0
};
} else if (ruleType === 'ai_prompt') {
ruleData = {
prompt: formData.get('prompt')
};
}
const updateData = {
name: formData.get('name'),
description: formData.get('description'),
action: formData.get('action'),
priority: parseInt(formData.get('priority')),
rule_data: ruleData
};
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
fetch(`/dashboard/projects/${projectId}/rules/${ruleId}/update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify(updateData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload(); // Refresh to show updated rule
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error updating rule');
});
});
}
});