diff --git a/server/static/app.js b/server/static/app.js index d0cc991..1ed7f2d 100644 --- a/server/static/app.js +++ b/server/static/app.js @@ -12,7 +12,7 @@ document.addEventListener('DOMContentLoaded', () => { if (logout) { logout.addEventListener('click', async (e) => { e.preventDefault(); - await fetch('/api/auth/logout', { method: 'POST' }); + await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }); window.location.href = '/login'; }); } diff --git a/server/templates/admin_settings.html b/server/templates/admin_settings.html index a153729..f5609f3 100644 --- a/server/templates/admin_settings.html +++ b/server/templates/admin_settings.html @@ -55,6 +55,7 @@ document.getElementById('settings-form').addEventListener('submit', async (e) => const res = await fetch('/api/settings', { method: 'POST', headers: {'Content-Type': 'application/json'}, + credentials: 'include', body: JSON.stringify(body), }); if (res.ok) { diff --git a/server/templates/admin_users.html b/server/templates/admin_users.html index ede199c..86bf256 100644 --- a/server/templates/admin_users.html +++ b/server/templates/admin_users.html @@ -69,6 +69,7 @@ document.getElementById('create-user-form').addEventListener('submit', async (e) const res = await fetch('/api/auth/users', { method: 'POST', headers: {'Content-Type': 'application/json'}, + credentials: 'include', body: JSON.stringify({ username: document.getElementById('new-username').value, password: document.getElementById('new-password').value, @@ -91,6 +92,7 @@ document.querySelectorAll('.change-role').forEach(btn => { const res = await fetch(`/api/auth/users/${btn.dataset.id}`, { method: 'PATCH', headers: {'Content-Type': 'application/json'}, + credentials: 'include', body: JSON.stringify({role: newRole}), }); if (res.ok) location.reload(); @@ -105,6 +107,7 @@ document.querySelectorAll('.reset-password').forEach(btn => { const res = await fetch(`/api/auth/users/${btn.dataset.id}`, { method: 'PATCH', headers: {'Content-Type': 'application/json'}, + credentials: 'include', body: JSON.stringify({password}), }); if (res.ok) alert('Пароль изменён'); @@ -115,7 +118,7 @@ document.querySelectorAll('.reset-password').forEach(btn => { document.querySelectorAll('.delete-user').forEach(btn => { btn.addEventListener('click', async () => { if (!confirm(`Удалить пользователя ${btn.dataset.username}?`)) return; - const res = await fetch(`/api/auth/users/${btn.dataset.id}`, {method: 'DELETE'}); + const res = await fetch(`/api/auth/users/${btn.dataset.id}`, {method: 'DELETE', credentials: 'include'}); if (res.ok) location.reload(); else alert('Ошибка удаления'); }); diff --git a/server/templates/dashboard.html b/server/templates/dashboard.html index 8601dbc..6a0979f 100644 --- a/server/templates/dashboard.html +++ b/server/templates/dashboard.html @@ -6,9 +6,9 @@

Компьютеры

- + Уведомления {% if unread_count %}{{ unread_count }}{% endif %} @@ -107,18 +107,45 @@ function currentQuery() { async function deleteComputer(id, hostname) { if (!confirm(`Удалить ПК ${hostname}?`)) return; - const res = await fetch(`/api/computers/${id}`, {method: 'DELETE'}); + const res = await fetch(`/api/computers/${id}`, {method: 'DELETE', credentials: 'include'}); if (res.ok) location.reload(); + else if (res.status === 401) location.href = '/login'; else alert('Ошибка удаления'); } +async function exportCsv() { + const res = await fetch('/api/computers/export/csv?' + currentQuery(), {credentials: 'include'}); + if (!res.ok) { + if (res.status === 401) location.href = '/login'; + else alert('Ошибка экспорта'); + return; + } + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + const disposition = res.headers.get('content-disposition'); + let filename = 'computers.csv'; + if (disposition) { + const match = disposition.match(/filename="?([^";]+)"?/); + if (match) filename = match[1]; + } + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); +} + +document.getElementById('export-csv-btn').addEventListener('click', exportCsv); + document.querySelectorAll('.delete-pc').forEach(btn => { btn.addEventListener('click', () => deleteComputer(btn.dataset.id, btn.dataset.hostname)); }); async function loadDashboard() { try { - const res = await fetch('/api/computers?' + currentQuery()); + const res = await fetch('/api/computers?' + currentQuery(), {credentials: 'include'}); if (!res.ok) return location.reload(); const data = await res.json(); const tbody = document.querySelector('#computers-table tbody'); diff --git a/server/templates/login.html b/server/templates/login.html index 92e8c1f..b68f28f 100644 --- a/server/templates/login.html +++ b/server/templates/login.html @@ -57,7 +57,7 @@ document.getElementById('login-form').addEventListener('submit', async (e) => { form.append('username', document.getElementById('username').value); form.append('password', document.getElementById('password').value); try { - const res = await fetch('/api/auth/login', { method: 'POST', body: form }); + const res = await fetch('/api/auth/login', { method: 'POST', body: form, credentials: 'include' }); if (res.ok) { window.location.href = '/'; } else { @@ -79,6 +79,7 @@ document.getElementById('register-form').addEventListener('submit', async (e) => const res = await fetch('/api/auth/register', { method: 'POST', headers: {'Content-Type': 'application/json'}, + credentials: 'include', body: JSON.stringify({ username: document.getElementById('reg-username').value, password: document.getElementById('reg-password').value, diff --git a/server/templates/notifications.html b/server/templates/notifications.html index 667ccfd..a963025 100644 --- a/server/templates/notifications.html +++ b/server/templates/notifications.html @@ -36,7 +36,7 @@ {% block scripts %}