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;
+ }