Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2): - Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc. - Add RateLimitMiddleware for API rate limiting - Add security event logging to AuditLogModel - Add ResponseHelper for standardized API responses - Update config.php with security constants Database (Phase 3): - Add migration 014 for additional indexes - Add migration 015 for ticket dependencies - Add migration 016 for ticket attachments - Add migration 017 for recurring tickets - Add migration 018 for custom fields Features (Phase 4-5): - Add ticket dependencies with DependencyModel and API - Add duplicate detection with check_duplicates API - Add file attachments with AttachmentModel and upload/download APIs - Add @mentions with autocomplete and highlighting - Add quick actions on dashboard rows Collaboration (Phase 5): - Add mention extraction in CommentModel - Add mention autocomplete dropdown in ticket.js - Add mention highlighting CSS styles Admin & Export (Phase 6): - Add StatsModel for dashboard widgets - Add dashboard stats cards (open, critical, unassigned, etc.) - Add CSV/JSON export via export_tickets API - Add rich text editor toolbar in markdown.js - Add RecurringTicketModel with cron job - Add CustomFieldModel for per-category fields - Add admin views: RecurringTickets, CustomFields, Workflow, Templates, AuditLog, UserActivity - Add admin APIs: manage_workflows, manage_templates, manage_recurring, custom_fields, get_users - Add admin routes in index.php Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,13 @@
|
||||
<label for="title">Ticket Title *</label>
|
||||
<input type="text" id="title" name="title" class="editable" required placeholder="Enter a descriptive title for this ticket">
|
||||
</div>
|
||||
<!-- Duplicate Warning Area -->
|
||||
<div id="duplicateWarning" style="display: none; margin-top: 1rem; padding: 1rem; border: 2px solid var(--terminal-amber); background: rgba(241, 196, 15, 0.1);">
|
||||
<div style="color: var(--terminal-amber); font-weight: bold; margin-bottom: 0.5rem;">
|
||||
Possible Duplicates Found
|
||||
</div>
|
||||
<div id="duplicatesList"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -187,5 +194,63 @@
|
||||
</form>
|
||||
</div>
|
||||
<!-- END OUTER FRAME -->
|
||||
|
||||
<script>
|
||||
// Duplicate detection with debounce
|
||||
let duplicateCheckTimeout = null;
|
||||
|
||||
document.getElementById('title').addEventListener('input', function() {
|
||||
clearTimeout(duplicateCheckTimeout);
|
||||
const title = this.value.trim();
|
||||
|
||||
if (title.length < 5) {
|
||||
document.getElementById('duplicateWarning').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce: wait 500ms after user stops typing
|
||||
duplicateCheckTimeout = setTimeout(() => {
|
||||
checkForDuplicates(title);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
function checkForDuplicates(title) {
|
||||
fetch('/api/check_duplicates.php?title=' + encodeURIComponent(title))
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const warningDiv = document.getElementById('duplicateWarning');
|
||||
const listDiv = document.getElementById('duplicatesList');
|
||||
|
||||
if (data.success && data.duplicates && data.duplicates.length > 0) {
|
||||
let html = '<ul style="margin: 0; padding-left: 1.5rem; color: var(--terminal-green);">';
|
||||
data.duplicates.forEach(dup => {
|
||||
html += `<li style="margin-bottom: 0.5rem;">
|
||||
<a href="/ticket/${escapeHtml(dup.ticket_id)}" target="_blank" style="color: var(--terminal-green);">
|
||||
#${escapeHtml(dup.ticket_id)}
|
||||
</a>
|
||||
- ${escapeHtml(dup.title)}
|
||||
<span style="color: var(--terminal-amber);">(${dup.similarity}% match, ${escapeHtml(dup.status)})</span>
|
||||
</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
html += '<p style="margin-top: 0.5rem; font-size: 0.85rem; color: var(--terminal-green-dim);">Consider checking these tickets before creating a new one.</p>';
|
||||
|
||||
listDiv.innerHTML = html;
|
||||
warningDiv.style.display = 'block';
|
||||
} else {
|
||||
warningDiv.style.display = 'none';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error checking duplicates:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user