Fix some issues

This commit is contained in:
2026-03-26 13:18:09 -04:00
parent ae6a44199f
commit 169820240a
25 changed files with 319 additions and 38 deletions
+1
View File
@@ -18,6 +18,7 @@
</div>
<div class="card-body">
<form method="POST" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<!-- ── Identity ─────────────────────────────────────────────────── -->
<div style="font-size:11px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin-bottom:14px;">
+1
View File
@@ -10,6 +10,7 @@
<div class="card-header"><i class="bi bi-person-gear me-2"></i>Edit: {{ user.full_name }}</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Full Name</label>
+1
View File
@@ -43,6 +43,7 @@
<form method="POST" enctype="multipart/form-data" id="kb-form"
data-article-id="{{ article.id if article else '' }}"
data-atts-url="{{ url_for('admin.kb_get_attachments', article_id=article.id) if article else '' }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="mb-3">
<label class="form-label">Title *</label>
<input type="text" class="form-control" name="title" required
+2
View File
@@ -37,6 +37,7 @@
<a href="{{ url_for('admin.kb_edit', article_id=art.id) }}" class="btn btn-secondary btn-sm" title="Edit"><i class="bi bi-pencil"></i></a>
<!-- Publish / Unpublish quick toggle -->
<form method="POST" action="{{ url_for('admin.kb_toggle_publish', article_id=art.id) }}" style="margin:0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% if art.is_published %}
<button type="submit" class="btn btn-sm"
style="background:rgba(251,191,36,.1);border:1px solid rgba(251,191,36,.3);color:var(--warning);"
@@ -52,6 +53,7 @@
{% endif %}
</form>
<form method="POST" action="{{ url_for('admin.kb_delete', article_id=art.id) }}" onsubmit="return confirm('Delete this article?');" style="margin:0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm" style="background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.2);color:var(--danger);" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
+1 -1
View File
@@ -61,7 +61,7 @@
{% if u.id != current_user.id and u.is_active %}
<form method="POST" action="{{ url_for('admin.delete_user', user_id=u.id) }}"
onsubmit="return confirm('Deactivate {{ u.full_name }}?');">
<button type="submit" class="btn btn-sm" style="background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.2);color:var(--danger);">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <button type="submit" class="btn btn-sm" style="background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.2);color:var(--danger);">
<i class="bi bi-person-dash"></i>
</button>
</form>
+1
View File
@@ -53,6 +53,7 @@
{% endwith %}
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label>Email Address</label>
<input type="email" name="email" required placeholder="you@company.com" autofocus/>
+1
View File
@@ -9,6 +9,7 @@
<div class="card-header"><i class="bi bi-person-circle me-2"></i>Account Settings</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<!-- Avatar placeholder -->
<div class="text-center mb-4">
<div style="width:80px;height:80px;border-radius:50%;background:var(--accent2);display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:700;color:#fff;margin:0 auto 10px;">
+1
View File
@@ -45,6 +45,7 @@
{% endwith %}
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="row">
<div class="form-group">
<label>Full Name *</label>
+25 -6
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<meta name="csrf-token" content="{{ csrf_token() }}"/>
<title>{% block title %}IT Helpdesk{% endblock %} — TechDesk</title>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
@@ -438,6 +439,27 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script>
// ── CSRF helper ───────────────────────────────────────────────────────────────
// All state-changing fetch() calls must include the CSRF token header.
// Use csrfPost(url, body) instead of raw fetch(..., {method:'POST'}) to ensure
// the token from the <meta> tag is sent automatically.
const _csrfToken = () => document.querySelector('meta[name="csrf-token"]')?.content || '';
async function csrfPost(url, body = null) {
const opts = {
method : 'POST',
headers: { 'X-CSRFToken': _csrfToken() },
};
if (body !== null) {
if (typeof body === 'object' && !(body instanceof FormData)) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(body);
} else {
opts.body = body;
}
}
return fetch(url, opts);
}
// ── WebSocket ────────────────────────────────────────────────────────────────
{% if current_user.is_authenticated %}
const socket = io({
@@ -506,7 +528,7 @@ function _bindNotifClick(el) {
const link = el.dataset.link;
// Mark as read in DB
if(el.classList.contains('unread')){
try { await fetch(`/api/notifications/${id}/read`, { method: 'POST' }); } catch(_){}
try { await csrfPost(`/api/notifications/${id}/read`); } catch(_){}
el.classList.remove('unread');
const dot = el.querySelector('.notif-dot');
if(dot) dot.remove();
@@ -567,7 +589,7 @@ function prependNotif(n){
}
async function markAllRead(){
try { await fetch('/api/notifications/mark-all-read', { method: 'POST' }); } catch(_){}
try { await csrfPost('/api/notifications/mark-all-read'); } catch(_){}
updateBadge(0);
document.querySelectorAll('.notif-item.unread').forEach(el => {
el.classList.remove('unread');
@@ -601,10 +623,7 @@ async function sendChat(){
chatHistory.push({role:'user',content:msg});
const typing = appendTyping();
try{
const r = await fetch('/chatbot/message',{
method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:msg,history:chatHistory})
});
const r = await csrfPost('/chatbot/message', {message:msg,history:chatHistory});
const d = await r.json();
typing.remove();
const reply = d.reply || 'Sorry, I encountered an error.';
+1
View File
@@ -11,6 +11,7 @@
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="row g-3">
<div class="col-12">
<label class="form-label">Issue Title *</label>
+3
View File
@@ -88,6 +88,7 @@
{% if current_user.is_it_staff or comment.author_id == current_user.id %}
<form method="POST" action="{{ url_for('tickets.delete_comment', comment_id=comment.id) }}"
onsubmit="return confirm('Delete this comment?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm" style="background:none;border:none;color:var(--muted);padding:2px 6px;"
title="Delete comment">
<i class="bi bi-trash"></i>
@@ -122,6 +123,7 @@
<div class="card-header"><i class="bi bi-chat-plus me-2"></i>Add Comment</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="mb-3">
<textarea class="form-control" name="body" rows="4" required
placeholder="Add your update, follow-up, or response here…"></textarea>
@@ -157,6 +159,7 @@
<div class="card-header"><i class="bi bi-pencil-square me-2"></i>Update Ticket</div>
<div class="card-body">
<form method="POST" action="{{ url_for('tickets.update_ticket', ticket_id=ticket.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="mb-3">
<label class="form-label">Status</label>
<select class="form-select" name="status">
+1
View File
@@ -9,6 +9,7 @@
<div class="card-header d-flex align-items-center justify-content-between">
<span><i class="bi bi-bell me-2"></i>All Notifications</span>
<form method="POST" action="{{ url_for('tickets.mark_notifications_read') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-secondary btn-sm">
<i class="bi bi-check2-all me-1"></i>Mark All Read
</button>