308 lines
9.9 KiB
JavaScript
308 lines
9.9 KiB
JavaScript
// admin.js
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Проверка наличия токена перед загрузкой
|
|
const token = localStorage.getItem('admin_token');
|
|
if (!token) {
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
|
|
fetchAddresses();
|
|
fetchNamedLists();
|
|
|
|
const addForm = document.getElementById('addAddressForm');
|
|
addForm.addEventListener('submit', handleAddAddress);
|
|
|
|
const addListForm = document.getElementById('addListForm');
|
|
addListForm.addEventListener('submit', handleAddList);
|
|
|
|
const filterInput = document.getElementById('tableFilter');
|
|
filterInput.addEventListener('input', handleFilter);
|
|
|
|
const profileForm = document.getElementById('profileForm');
|
|
profileForm.addEventListener('submit', handleProfileUpdate);
|
|
});
|
|
|
|
// Хелпер для авторизованных запросов
|
|
async function authorizedFetch(url, options = {}) {
|
|
const token = localStorage.getItem('admin_token');
|
|
|
|
const defaultHeaders = {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
const config = {
|
|
...options,
|
|
headers: {
|
|
...defaultHeaders,
|
|
...options.headers
|
|
}
|
|
};
|
|
|
|
const response = await fetch(url, config);
|
|
|
|
if (response.status === 401) {
|
|
localStorage.removeItem('admin_token');
|
|
window.location.href = '/login';
|
|
return null;
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
async function fetchNamedLists() {
|
|
try {
|
|
const response = await authorizedFetch('/api/v1/named-lists');
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
renderListsTable(data.data);
|
|
populateListDropdown(data.data);
|
|
}
|
|
} catch (err) {
|
|
console.error('Ошибка при загрузке списков:', err);
|
|
}
|
|
}
|
|
|
|
function renderListsTable(lists) {
|
|
const tbody = document.getElementById('listsTableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
lists.forEach(list => {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${list.id}</td>
|
|
<td><strong>${list.name}</strong></td>
|
|
<td>
|
|
<button class="btn btn-danger btn-sm" onclick="deleteNamedList(${list.id})" title="Удалить список">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function populateListDropdown(lists) {
|
|
const select = document.getElementById('inputList');
|
|
const currentValue = select.value;
|
|
|
|
select.innerHTML = '';
|
|
lists.forEach(list => {
|
|
const option = document.createElement('option');
|
|
option.value = list.name;
|
|
option.textContent = list.name;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
if (currentValue && lists.some(l => l.name === currentValue)) {
|
|
select.value = currentValue;
|
|
}
|
|
}
|
|
|
|
async function handleAddList(e) {
|
|
e.preventDefault();
|
|
const name = document.getElementById('newListName').value;
|
|
|
|
try {
|
|
const response = await authorizedFetch('/api/v1/named-lists', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ name })
|
|
});
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showToast('Список создан', 'success');
|
|
document.getElementById('addListForm').reset();
|
|
fetchNamedLists();
|
|
} else {
|
|
showToast('Ошибка: ' + data.error, 'danger');
|
|
}
|
|
} catch (err) {
|
|
showToast('Сетевая ошибка', 'danger');
|
|
}
|
|
}
|
|
|
|
async function deleteNamedList(id) {
|
|
if (!confirm('Удалить этот список? Это возможно только если в нем нет адресов.')) return;
|
|
|
|
try {
|
|
const response = await authorizedFetch(`/api/v1/named-lists/${id}`, { method: 'DELETE' });
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showToast('Список удален', 'success');
|
|
fetchNamedLists();
|
|
} else {
|
|
showToast('Ошибка: ' + data.error, 'danger');
|
|
}
|
|
} catch (err) {
|
|
showToast('Сетевая ошибка', 'danger');
|
|
}
|
|
}
|
|
|
|
async function fetchAddresses() {
|
|
try {
|
|
const response = await authorizedFetch('/api/v1/addresses');
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
renderTable(data.data);
|
|
updateStats(data.data);
|
|
}
|
|
} catch (err) {
|
|
showToast('Ошибка при загрузке данных', 'danger');
|
|
}
|
|
}
|
|
|
|
function renderTable(addresses) {
|
|
const tbody = document.getElementById('addressesTableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
addresses.forEach(item => {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${item.id}</td>
|
|
<td><strong>${item.ip}</strong></td>
|
|
<td><span class="badge" style="background: #eee;">${item.list_name}</span></td>
|
|
<td>${item.comment || '-'}</td>
|
|
<td>
|
|
<span class="badge ${item.enabled ? 'badge-success' : 'badge-danger'}">
|
|
${item.enabled ? 'Активен' : 'Отключен'}
|
|
</span>
|
|
</td>
|
|
<td class="actions-cell">
|
|
<button class="btn btn-primary btn-sm" onclick="toggleAddress(${item.id}, ${item.enabled})" title="${item.enabled ? 'Отключить' : 'Включить'}">
|
|
<i class="fas fa-power-off"></i>
|
|
</button>
|
|
<button class="btn btn-danger btn-sm" onclick="deleteAddress(${item.id})" title="Удалить">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function updateStats(addresses) {
|
|
document.getElementById('statTotal').textContent = addresses.length;
|
|
document.getElementById('statActive').textContent = addresses.filter(a => a.enabled).length;
|
|
document.getElementById('statInactive').textContent = addresses.filter(a => !a.enabled).length;
|
|
}
|
|
|
|
async function handleAddAddress(e) {
|
|
e.preventDefault();
|
|
const address = document.getElementById('inputAddress').value;
|
|
const list = document.getElementById('inputList').value;
|
|
const comment = document.getElementById('inputComment').value;
|
|
|
|
try {
|
|
const response = await authorizedFetch('/api/v1/addresses', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ address, list, comment })
|
|
});
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showToast('Адрес успешно добавлен', 'success');
|
|
document.getElementById('addAddressForm').reset();
|
|
fetchAddresses();
|
|
} else {
|
|
showToast('Ошибка: ' + data.error, 'danger');
|
|
}
|
|
} catch (err) {
|
|
showToast('Сетевая ошибка', 'danger');
|
|
}
|
|
}
|
|
|
|
async function toggleAddress(id, currentEnabled) {
|
|
const action = currentEnabled ? 'disable' : 'enable';
|
|
try {
|
|
const response = await authorizedFetch(`/api/v1/addresses/${id}/${action}`, { method: 'PUT' });
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showToast(`Статус успешно изменен`, 'success');
|
|
fetchAddresses();
|
|
} else {
|
|
showToast('Ошибка: ' + data.error, 'danger');
|
|
}
|
|
} catch (err) {
|
|
showToast('Сетевая ошибка', 'danger');
|
|
}
|
|
}
|
|
|
|
async function deleteAddress(id) {
|
|
if (!confirm('Вы уверены, что хотите удалить этот адрес?')) return;
|
|
try {
|
|
const response = await authorizedFetch(`/api/v1/addresses/${id}`, { method: 'DELETE' });
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showToast('Запись удалена', 'success');
|
|
fetchAddresses();
|
|
} else {
|
|
showToast('Ошибка при удалении', 'danger');
|
|
}
|
|
} catch (err) {
|
|
showToast('Сетевая ошибка', 'danger');
|
|
}
|
|
}
|
|
|
|
async function handleProfileUpdate(e) {
|
|
e.preventDefault();
|
|
const username = document.getElementById('profileUsername').value;
|
|
const password = document.getElementById('profilePassword').value;
|
|
|
|
try {
|
|
const response = await authorizedFetch('/api/v1/admin/profile', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
if (!response) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showToast('Профиль успешно обновлен. Если вы сменили имя, потребуется перелогиниться.', 'success');
|
|
document.getElementById('profilePassword').value = '';
|
|
} else {
|
|
showToast('Ошибка: ' + data.error, 'danger');
|
|
}
|
|
} catch (err) {
|
|
showToast('Сетевая ошибка', 'danger');
|
|
}
|
|
}
|
|
|
|
function handleFilter(e) {
|
|
const text = e.target.value.toLowerCase();
|
|
const rows = document.querySelectorAll('#addressesTableBody tr');
|
|
rows.forEach(row => {
|
|
row.style.display = row.textContent.toLowerCase().includes(text) ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function showToast(message, type) {
|
|
const toast = document.getElementById('toast');
|
|
toast.textContent = message;
|
|
toast.style.backgroundColor = type === 'success' ? '#2ecc71' : '#e74c3c';
|
|
toast.style.display = 'block';
|
|
setTimeout(() => {
|
|
toast.style.display = 'none';
|
|
}, 3000);
|
|
}
|
|
|
|
// Добавим функцию выхода
|
|
function logout() {
|
|
localStorage.removeItem('admin_token');
|
|
window.location.href = '/login';
|
|
}
|