From 438ba6ba9b656f5c2968f09171c974eabbb70674 Mon Sep 17 00:00:00 2001 From: kalugin66 Date: Sat, 7 Feb 2026 14:41:57 +0500 Subject: [PATCH] doc --- public/doc.html | 877 +++++++++++++++++++++++++++++++++++++++++ public/doc.js | 1000 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1877 insertions(+) create mode 100644 public/doc.html create mode 100644 public/doc.js diff --git a/public/doc.html b/public/doc.html new file mode 100644 index 0000000..f7b28c7 --- /dev/null +++ b/public/doc.html @@ -0,0 +1,877 @@ + + + + + + Управление внешними идентификаторами + + + + +
+
+
+
+

Внешние идентификаторы

+

Управление идентификаторами пользователей в сторонних системах

+
+ +
+
+ +
+
+ Группы идентификаторов +
+
+ Идентификаторы пользователей +
+
+ Статистика +
+
+ + +
+
+ +
+ + +
+ + +
+ +
+ + + + + + + + + + + + + + + + +
IDНазваниеТип сервисаОписаниеСтатусСозданоОбновленоДействия
+
+ + +
+ + +
+
+ +
+ + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + +
IDПользовательТип сервисаВнешний IDЛогин LDAPГруппа LDAPГруппаМетаданныеСтатусСозданоДействия
+
+ + +
+ + +
+
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + +
Тип сервисаВсего идентификаторовАктивныхУникальных пользователейДоля
+
+
+
+ + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/public/doc.js b/public/doc.js new file mode 100644 index 0000000..2c28934 --- /dev/null +++ b/public/doc.js @@ -0,0 +1,1000 @@ + // Глобальные переменные + let currentUser = null; + let isAdmin = false; + let currentTab = 'groups'; + let groups = []; + let idusers = []; + let allUsers = []; + let currentPage = { + groups: 1, + idusers: 1 + }; + const itemsPerPage = 20; + let deleteCallback = null; + let deleteParams = null; + + // Инициализация при загрузке страницы + document.addEventListener('DOMContentLoaded', function() { + checkAuth(); + setupEventListeners(); + }); + + // Проверка аутентификации + async function checkAuth() { + try { + const response = await fetch('/api/user'); + if (!response.ok) { + window.location.href = '/'; + return; + } + + const data = await response.json(); + if (data.user) { + currentUser = data.user; + isAdmin = currentUser.role === 'admin'; + + document.getElementById('userName').textContent = currentUser.name; + document.getElementById('userRole').textContent = `Роль: ${currentUser.role}`; + + // Загружаем начальные данные + loadGroups(); + loadAllUsers(); + loadIdUsers(); + loadStats(); + } else { + window.location.href = '/'; + } + } catch (error) { + console.error('Ошибка проверки аутентификации:', error); + window.location.href = '/'; + } + } + + // Настройка обработчиков событий + function setupEventListeners() { + // Переключение вкладок + document.querySelectorAll('.tab').forEach(tab => { + tab.addEventListener('click', function() { + const tabId = this.getAttribute('data-tab'); + switchTab(tabId); + }); + }); + + // Поиск в таблице групп + document.getElementById('groupSearch').addEventListener('input', debounce(function() { + filterGroups(); + }, 300)); + + // Фильтры групп + document.getElementById('groupServiceTypeFilter').addEventListener('change', filterGroups); + document.getElementById('groupStatusFilter').addEventListener('change', filterGroups); + + // Поиск в таблице идентификаторов + document.getElementById('iduserSearch').addEventListener('input', debounce(function() { + filterIdUsers(); + }, 300)); + + // Фильтры идентификаторов + document.getElementById('iduserServiceTypeFilter').addEventListener('change', filterIdUsers); + document.getElementById('iduserGroupFilter').addEventListener('change', filterIdUsers); + document.getElementById('iduserStatusFilter').addEventListener('change', filterIdUsers); + } + + // Дебаунс для поиска + function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + // Переключение вкладок + function switchTab(tabId) { + currentTab = tabId; + + // Обновляем активные вкладки + document.querySelectorAll('.tab').forEach(tab => { + tab.classList.remove('active'); + if (tab.getAttribute('data-tab') === tabId) { + tab.classList.add('active'); + } + }); + + // Показываем активный контент + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + if (content.id === `tab-${tabId}`) { + content.classList.add('active'); + } + }); + } + + // Загрузка всех пользователей + async function loadAllUsers() { + try { + const response = await fetch('/api/users'); + if (!response.ok) throw new Error('Ошибка загрузки пользователей'); + + allUsers = await response.json(); + + // Заполняем select в форме + const select = document.getElementById('iduserUserId'); + select.innerHTML = ''; + + allUsers.forEach(user => { + const option = document.createElement('option'); + option.value = user.id; + option.textContent = `${user.name} (${user.login})`; + select.appendChild(option); + }); + } catch (error) { + console.error('Ошибка загрузки пользователей:', error); + } + } + + // Загрузка групп + async function loadGroups() { + try { + const tableBody = document.getElementById('groupsTableBody'); + tableBody.innerHTML = ' Загрузка групп...'; + + const response = await fetch('/api2/groups'); + if (!response.ok) throw new Error('Ошибка загрузки групп'); + + groups = await response.json(); + + // Заполняем фильтр групп для идентификаторов + const groupFilter = document.getElementById('iduserGroupFilter'); + const currentValue = groupFilter.value; + groupFilter.innerHTML = ''; + + groups.forEach(group => { + const option = document.createElement('option'); + option.value = group.id; + option.textContent = group.name; + groupFilter.appendChild(option); + }); + + if (currentValue) { + groupFilter.value = currentValue; + } + + renderGroups(); + } catch (error) { + console.error('Ошибка загрузки групп:', error); + document.getElementById('groupsTableBody').innerHTML = + 'Ошибка загрузки групп: ' + error.message + ''; + } + } + + // Отображение групп + function renderGroups(filteredGroups = null) { + const groupsToRender = filteredGroups || groups; + const tableBody = document.getElementById('groupsTableBody'); + + if (groupsToRender.length === 0) { + tableBody.innerHTML = 'Нет данных для отображения'; + document.getElementById('groupsPagination').innerHTML = ''; + return; + } + + // Пагинация + const totalPages = Math.ceil(groupsToRender.length / itemsPerPage); + const startIndex = (currentPage.groups - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const pageGroups = groupsToRender.slice(startIndex, endIndex); + + // Очищаем таблицу + tableBody.innerHTML = ''; + + // Заполняем таблицу + pageGroups.forEach(group => { + const row = document.createElement('tr'); + + // Бейдж для типа сервиса + let serviceTypeBadge = ''; + switch(group.service_type) { + case 'sberbank': + serviceTypeBadge = 'Сбербанк'; + break; + case 'yandex': + serviceTypeBadge = 'Яндекс'; + break; + case 'ldap': + serviceTypeBadge = 'LDAP'; + break; + default: + serviceTypeBadge = 'Прочие'; + } + + row.innerHTML = ` + ${group.id} + ${group.name} + ${serviceTypeBadge} + ${group.description || '-'} + ${group.is_active ? 'Активна' : 'Неактивна'} + ${formatDate(group.created_at)} + ${formatDate(group.updated_at)} + + ${isAdmin ? ` + + + ` : ''} + + `; + + tableBody.appendChild(row); + }); + + // Пагинация + renderPagination('groups', totalPages); + } + + // Фильтрация групп + function filterGroups() { + const searchText = document.getElementById('groupSearch').value.toLowerCase(); + const serviceType = document.getElementById('groupServiceTypeFilter').value; + const statusFilter = document.getElementById('groupStatusFilter').value; + + const filtered = groups.filter(group => { + // Поиск по тексту + const matchesSearch = !searchText || + group.name.toLowerCase().includes(searchText) || + (group.description && group.description.toLowerCase().includes(searchText)); + + // Фильтр по типу сервиса + const matchesServiceType = !serviceType || group.service_type === serviceType; + + // Фильтр по статусу + const matchesStatus = !statusFilter || + (statusFilter === 'true' ? group.is_active : !group.is_active); + + return matchesSearch && matchesServiceType && matchesStatus; + }); + + currentPage.groups = 1; + renderGroups(filtered); + } + + // Загрузка идентификаторов пользователей + async function loadIdUsers() { + try { + const tableBody = document.getElementById('idusersTableBody'); + tableBody.innerHTML = ' Загрузка идентификаторов...'; + + const response = await fetch('/api2/idusers'); + if (!response.ok) throw new Error('Ошибка загрузки идентификаторов'); + + idusers = await response.json(); + renderIdUsers(); + } catch (error) { + console.error('Ошибка загрузки идентификаторов:', error); + document.getElementById('idusersTableBody').innerHTML = + 'Ошибка загрузки идентификаторов: ' + error.message + ''; + } + } + + // Отображение идентификаторов пользователей + function renderIdUsers(filteredIdUsers = null) { + const idusersToRender = filteredIdUsers || idusers; + const tableBody = document.getElementById('idusersTableBody'); + + if (idusersToRender.length === 0) { + tableBody.innerHTML = 'Нет данных для отображения'; + document.getElementById('idusersPagination').innerHTML = ''; + return; + } + + // Пагинация + const totalPages = Math.ceil(idusersToRender.length / itemsPerPage); + const startIndex = (currentPage.idusers - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const pageIdUsers = idusersToRender.slice(startIndex, endIndex); + + // Очищаем таблицу + tableBody.innerHTML = ''; + + // Заполняем таблицу + pageIdUsers.forEach(iduser => { + const row = document.createElement('tr'); + + // Бейдж для типа сервиса + let serviceTypeBadge = ''; + switch(iduser.service_type) { + case 'sberbank': + serviceTypeBadge = 'Сбербанк'; + break; + case 'yandex': + serviceTypeBadge = 'Яндекс'; + break; + case 'ldap': + serviceTypeBadge = 'LDAP'; + break; + default: + serviceTypeBadge = 'Прочие'; + } + + // Превью метаданных + let metadataPreview = '-'; + if (iduser.metadata && Object.keys(iduser.metadata).length > 0) { + metadataPreview = `
+ ${Object.keys(iduser.metadata).length} поле(й) +
`; + } + + row.innerHTML = ` + ${iduser.id} + + ${iduser.user_name || 'Неизвестно'}
+ ${iduser.user_login || '-'} + + ${serviceTypeBadge} + ${iduser.external_id} + ${iduser.login || '-'} + ${iduser.ldap_group || '-'} + ${iduser.group_name || '-'} + ${metadataPreview} + ${iduser.is_active ? 'Активен' : 'Неактивен'} + ${formatDate(iduser.created_at)} + + ${isAdmin ? ` + + + ` : ''} + + `; + + tableBody.appendChild(row); + }); + + // Пагинация + renderPagination('idusers', totalPages); + } + + // Фильтрация идентификаторов пользователей + function filterIdUsers() { + const searchText = document.getElementById('iduserSearch').value.toLowerCase(); + const serviceType = document.getElementById('iduserServiceTypeFilter').value; + const groupId = document.getElementById('iduserGroupFilter').value; + const statusFilter = document.getElementById('iduserStatusFilter').value; + + const filtered = idusers.filter(iduser => { + // Поиск по тексту + const matchesSearch = !searchText || + iduser.external_id.toLowerCase().includes(searchText) || + (iduser.login && iduser.login.toLowerCase().includes(searchText)) || + (iduser.user_name && iduser.user_name.toLowerCase().includes(searchText)) || + (iduser.user_login && iduser.user_login.toLowerCase().includes(searchText)); + + // Фильтр по типу сервиса + const matchesServiceType = !serviceType || iduser.service_type === serviceType; + + // Фильтр по группе + const matchesGroup = !groupId || iduser.group_id == groupId; + + // Фильтр по статусу + const matchesStatus = !statusFilter || + (statusFilter === 'true' ? iduser.is_active : !iduser.is_active); + + return matchesSearch && matchesServiceType && matchesGroup && matchesStatus; + }); + + currentPage.idusers = 1; + renderIdUsers(filtered); + } + + // Загрузка статистики + async function loadStats() { + try { + const statsGrid = document.getElementById('statsGrid'); + statsGrid.innerHTML = '
Загрузка статистики...
'; + + const response = await fetch('/api2/idusers/stats'); + if (!response.ok) throw new Error('Ошибка загрузки статистики'); + + const stats = await response.json(); + renderStats(stats); + } catch (error) { + console.error('Ошибка загрузки статистики:', error); + document.getElementById('statsGrid').innerHTML = + '
Ошибка загрузки статистики: ' + error.message + '
'; + } + } + + // Отображение статистики + function renderStats(stats) { + const statsGrid = document.getElementById('statsGrid'); + const tableBody = document.getElementById('statsTableBody'); + + // Статистические карточки + statsGrid.innerHTML = ` +
+
Всего идентификаторов
+
${stats.totals.total_identifiers || 0}
+
+
+
Уникальных пользователей
+
${stats.totals.total_users || 0}
+
+
+
Активных идентификаторов
+
${stats.totals.total_active || 0}
+
+
+
Обновлено
+
${formatDate(stats.timestamp, 'time')}
+
+ `; + + // Таблица по типам сервисов + tableBody.innerHTML = ''; + + if (stats.by_service_type && stats.by_service_type.length > 0) { + const total = stats.totals.total_identifiers || 1; + + stats.by_service_type.forEach(stat => { + const percentage = Math.round((stat.total_count / total) * 100); + + const row = document.createElement('tr'); + row.innerHTML = ` + + + ${getServiceTypeName(stat.service_type)} + + + ${stat.total_count} + ${stat.active_count} + ${stat.unique_users} + +
+
+
+ ${percentage}% + + `; + tableBody.appendChild(row); + }); + } else { + tableBody.innerHTML = 'Нет данных для отображения'; + } + } + + // Модальное окно для добавления группы + function showAddGroupModal() { + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + document.getElementById('groupModalTitle').textContent = 'Добавить группу'; + document.getElementById('groupId').value = ''; + document.getElementById('groupForm').reset(); + document.getElementById('groupIsActive').checked = true; + document.getElementById('groupMessage').innerHTML = ''; + + document.getElementById('groupModal').classList.add('active'); + } + + // Модальное окно для редактирования группы + async function editGroup(id) { + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + try { + const response = await fetch(`/api2/groups/${id}`); + if (!response.ok) throw new Error('Ошибка загрузки группы'); + + const group = await response.json(); + + document.getElementById('groupModalTitle').textContent = 'Редактировать группу'; + document.getElementById('groupId').value = group.id; + document.getElementById('groupName').value = group.name; + document.getElementById('groupDescription').value = group.description || ''; + document.getElementById('groupServiceType').value = group.service_type; + document.getElementById('groupIsActive').checked = group.is_active; + document.getElementById('groupMessage').innerHTML = ''; + + document.getElementById('groupModal').classList.add('active'); + } catch (error) { + console.error('Ошибка загрузки группы:', error); + alert('Ошибка загрузки группы: ' + error.message); + } + } + + // Закрытие модального окна группы + function closeGroupModal() { + document.getElementById('groupModal').classList.remove('active'); + } + + // Сохранение группы + async function saveGroup(event) { + event.preventDefault(); + + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + const groupId = document.getElementById('groupId').value; + const isEdit = !!groupId; + + const groupData = { + name: document.getElementById('groupName').value, + description: document.getElementById('groupDescription').value, + service_type: document.getElementById('groupServiceType').value, + is_active: document.getElementById('groupIsActive').checked + }; + + try { + const url = isEdit ? `/api2/groups/${groupId}` : '/api2/groups'; + const method = isEdit ? 'PUT' : 'POST'; + + const response = await fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(groupData) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || 'Ошибка сохранения'); + } + + const result = await response.json(); + + // Показываем сообщение об успехе + document.getElementById('groupMessage').innerHTML = ` +
+ + Группа успешно ${isEdit ? 'обновлена' : 'создана'}! +
+ `; + + // Обновляем список групп + setTimeout(() => { + closeGroupModal(); + loadGroups(); + }, 1500); + + } catch (error) { + console.error('Ошибка сохранения группы:', error); + document.getElementById('groupMessage').innerHTML = ` +
+ + Ошибка: ${error.message} +
+ `; + } + } + + // Подтверждение удаления группы + function confirmDeleteGroup(id) { + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + const group = groups.find(g => g.id == id); + if (!group) return; + + document.getElementById('confirmModalTitle').textContent = 'Удаление группы'; + document.getElementById('confirmMessage').textContent = `Вы уверены, что хотите удалить группу "${group.name}"?`; + + deleteCallback = deleteGroup; + deleteParams = { id }; + + document.getElementById('confirmModal').classList.add('active'); + } + + // Удаление группы + async function deleteGroup(params) { + try { + const response = await fetch(`/api2/groups/${params.id}`, { + method: 'DELETE' + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || 'Ошибка удаления'); + } + + const result = await response.json(); + + if (result.success) { + // Показываем сообщение об успехе + alert('Группа успешно удалена'); + + // Обновляем список групп + loadGroups(); + } else { + throw new Error(result.message || 'Ошибка удаления'); + } + + } catch (error) { + console.error('Ошибка удаления группы:', error); + alert('Ошибка удаления группы: ' + error.message); + } + } + + // Модальное окно для добавления идентификатора + function showAddIdUserModal() { + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + document.getElementById('iduserModalTitle').textContent = 'Добавить идентификатор'; + document.getElementById('iduserId').value = ''; + document.getElementById('iduserForm').reset(); + document.getElementById('iduserIsActive').checked = true; + document.getElementById('iduserMessage').innerHTML = ''; + + // Обновляем опции групп + updateGroupOptions(); + + document.getElementById('iduserModal').classList.add('active'); + } + + // Модальное окно для редактирования идентификатора + async function editIdUser(id) { + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + try { + const response = await fetch(`/api2/idusers/${id}`); + if (!response.ok) throw new Error('Ошибка загрузки идентификатора'); + + const iduser = await response.json(); + + document.getElementById('iduserModalTitle').textContent = 'Редактировать идентификатор'; + document.getElementById('iduserId').value = iduser.id; + document.getElementById('iduserUserId').value = iduser.user_id; + document.getElementById('iduserExternalId').value = iduser.external_id; + document.getElementById('iduserLogin').value = iduser.login || ''; + document.getElementById('iduserLdapGroup').value = iduser.ldap_group || ''; + document.getElementById('iduserServiceType').value = iduser.service_type; + document.getElementById('iduserIsActive').checked = iduser.is_active; + + // Устанавливаем метаданные + if (iduser.metadata && Object.keys(iduser.metadata).length > 0) { + document.getElementById('iduserMetadata').value = JSON.stringify(iduser.metadata, null, 2); + } else { + document.getElementById('iduserMetadata').value = ''; + } + + // Обновляем опции групп и выбираем текущую + updateGroupOptions().then(() => { + document.getElementById('iduserGroupId').value = iduser.group_id || ''; + }); + + document.getElementById('iduserMessage').innerHTML = ''; + + document.getElementById('iduserModal').classList.add('active'); + } catch (error) { + console.error('Ошибка загрузки идентификатора:', error); + alert('Ошибка загрузки идентификатора: ' + error.message); + } + } + + // Обновление опций групп в зависимости от типа сервиса + async function updateGroupOptions() { + const serviceType = document.getElementById('iduserServiceType').value; + const groupSelect = document.getElementById('iduserGroupId'); + + // Загружаем группы, если еще не загружены + if (groups.length === 0) { + await loadGroups(); + } + + // Фильтруем группы по типу сервиса + const filteredGroups = serviceType ? + groups.filter(g => g.service_type === serviceType && g.is_active) : + groups.filter(g => g.is_active); + + // Сохраняем текущее значение + const currentValue = groupSelect.value; + + // Обновляем опции + groupSelect.innerHTML = ''; + filteredGroups.forEach(group => { + const option = document.createElement('option'); + option.value = group.id; + option.textContent = group.name; + groupSelect.appendChild(option); + }); + + // Восстанавливаем значение, если оно есть в новых опциях + if (currentValue && filteredGroups.some(g => g.id == currentValue)) { + groupSelect.value = currentValue; + } + + return Promise.resolve(); + } + + // Закрытие модального окна идентификатора + function closeIdUserModal() { + document.getElementById('iduserModal').classList.remove('active'); + } + + // Сохранение идентификатора + async function saveIdUser(event) { + event.preventDefault(); + + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + const iduserId = document.getElementById('iduserId').value; + const isEdit = !!iduserId; + + // Парсим метаданные + let metadata = null; + const metadataText = document.getElementById('iduserMetadata').value.trim(); + if (metadataText) { + try { + metadata = JSON.parse(metadataText); + } catch (error) { + document.getElementById('iduserMessage').innerHTML = ` +
+ + Ошибка в формате JSON: ${error.message} +
+ `; + return; + } + } + + const iduserData = { + user_id: parseInt(document.getElementById('iduserUserId').value), + service_type: document.getElementById('iduserServiceType').value, + external_id: document.getElementById('iduserExternalId').value, + login: document.getElementById('iduserLogin').value || undefined, + ldap_group: document.getElementById('iduserLdapGroup').value || undefined, + group_id: document.getElementById('iduserGroupId').value || undefined, + metadata: metadata, + is_active: document.getElementById('iduserIsActive').checked + }; + + try { + const url = isEdit ? `/api2/idusers/${iduserId}` : '/api2/idusers'; + const method = isEdit ? 'PUT' : 'POST'; + + const response = await fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(iduserData) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || 'Ошибка сохранения'); + } + + const result = await response.json(); + + // Показываем сообщение об успехе + document.getElementById('iduserMessage').innerHTML = ` +
+ + Идентификатор успешно ${isEdit ? 'обновлен' : 'создан'}! +
+ `; + + // Обновляем список идентификаторов + setTimeout(() => { + closeIdUserModal(); + loadIdUsers(); + }, 1500); + + } catch (error) { + console.error('Ошибка сохранения идентификатора:', error); + document.getElementById('iduserMessage').innerHTML = ` +
+ + Ошибка: ${error.message} +
+ `; + } + } + + // Подтверждение удаления идентификатора + function confirmDeleteIdUser(id) { + if (!isAdmin) { + alert('Недостаточно прав'); + return; + } + + const iduser = idusers.find(i => i.id == id); + if (!iduser) return; + + document.getElementById('confirmModalTitle').textContent = 'Удаление идентификатора'; + document.getElementById('confirmMessage').textContent = `Вы уверены, что хотите удалить идентификатор "${iduser.external_id}" пользователя "${iduser.user_name}"?`; + + deleteCallback = deleteIdUser; + deleteParams = { id }; + + document.getElementById('confirmModal').classList.add('active'); + } + + // Удаление идентификатора + async function deleteIdUser(params) { + try { + const response = await fetch(`/api2/idusers/${params.id}`, { + method: 'DELETE' + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || 'Ошибка удаления'); + } + + const result = await response.json(); + + if (result.success) { + // Показываем сообщение об успехе + alert('Идентификатор успешно удален'); + + // Обновляем список идентификаторов + loadIdUsers(); + } else { + throw new Error(result.message || 'Ошибка удаления'); + } + + } catch (error) { + console.error('Ошибка удаления идентификатора:', error); + alert('Ошибка удаления идентификатора: ' + error.message); + } + } + + // Пагинация + function renderPagination(type, totalPages) { + const paginationDiv = document.getElementById(`${type}Pagination`); + + if (totalPages <= 1) { + paginationDiv.innerHTML = ''; + return; + } + + let html = ''; + + // Кнопка "Назад" + html += ``; + + // Номера страниц + for (let i = 1; i <= totalPages; i++) { + if (i === 1 || i === totalPages || (i >= currentPage[type] - 2 && i <= currentPage[type] + 2)) { + html += ``; + } else if (i === currentPage[type] - 3 || i === currentPage[type] + 3) { + html += `...`; + } + } + + // Кнопка "Вперед" + html += ``; + + // Информация о странице + html += ` + Страница ${currentPage[type]} из ${totalPages} + `; + + paginationDiv.innerHTML = html; + } + + // Изменение страницы + function changePage(type, page) { + currentPage[type] = page; + + if (type === 'groups') { + filterGroups(); + } else if (type === 'idusers') { + filterIdUsers(); + } + } + + // Подтверждение удаления + function confirmDelete() { + if (deleteCallback && deleteParams) { + deleteCallback(deleteParams); + } + closeConfirmModal(); + } + + // Закрытие модального окна подтверждения + function closeConfirmModal() { + document.getElementById('confirmModal').classList.remove('active'); + deleteCallback = null; + deleteParams = null; + } + + // Просмотр метаданных + function showMetadata(metadata) { + document.getElementById('metadataContent').textContent = metadata; + document.getElementById('metadataModal').classList.add('active'); + } + + // Закрытие модального окна метаданных + function closeMetadataModal() { + document.getElementById('metadataModal').classList.remove('active'); + } + + // Выход из системы + async function logout() { + try { + await fetch('/api/logout', { + method: 'POST' + }); + window.location.href = '/'; + } catch (error) { + console.error('Ошибка выхода:', error); + window.location.href = '/'; + } + } + + // Вспомогательные функции + function formatDate(dateString, type = 'date') { + if (!dateString) return '-'; + + const date = new Date(dateString); + + if (type === 'date') { + return date.toLocaleDateString('ru-RU', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + ' ' + date.toLocaleTimeString('ru-RU', { + hour: '2-digit', + minute: '2-digit' + }); + } else if (type === 'time') { + return date.toLocaleTimeString('ru-RU', { + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + } + + return date.toLocaleString('ru-RU'); + } + + function getServiceTypeName(type) { + switch(type) { + case 'sberbank': return 'Сбербанк'; + case 'yandex': return 'Яндекс'; + case 'ldap': return 'LDAP'; + default: return 'Прочие'; + } + } + + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + }