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 %}