Files
mikrotik-proxy-api/static/js/admin.js
T

307 lines
9.9 KiB
JavaScript
Raw Normal View History

2026-09-20 19:47:02 +03:00
// 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';
}