/
kev
/
Nextcloud_Users-mod
Обзор
Документация
Войти
/
kev
/
Nextcloud_Users-mod
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/admin.js
536 строк
23 KB
Evgeny Konovalov
first_commit
01 мар 2026, 19:48
01 мар 2026, 19:48
b6c8230
Код
Авторство
О чём код?
(function() { 'use strict'; var app = document.getElementById('users-mod-app'); if (!app) return; var listUrl = app.getAttribute('data-list-url') || ''; var groupsUrl = app.getAttribute('data-groups-url') || ''; var listEl = document.getElementById('users-mod-list'); var listEmpty = document.getElementById('users-mod-list-empty'); var searchEl = document.getElementById('users-mod-search'); var newBtn = document.getElementById('users-mod-new'); var placeholder = document.getElementById('users-mod-placeholder'); var formEl = document.getElementById('users-mod-form'); var groupFormEl = document.getElementById('users-mod-group-form'); var formId = document.getElementById('users-mod-id'); var deleteBtn = document.getElementById('users-mod-delete'); var groupsContainer = document.getElementById('users-mod-groups'); var groupsListEl = document.getElementById('users-mod-groups-list'); var groupsEmptyEl = document.getElementById('users-mod-groups-empty'); var groupSearchEl = document.getElementById('users-mod-group-search'); var newGroupBtn = document.getElementById('users-mod-new-group'); var groupIdEl = document.getElementById('users-mod-group-id'); var groupMembersEl = document.getElementById('users-mod-group-members'); var groupAddUserSelect = document.getElementById('users-mod-group-add-user'); var groupAddBtn = document.getElementById('users-mod-group-add-btn'); var groupDeleteBtn = document.getElementById('users-mod-group-delete'); var newGroupFormEl = document.getElementById('users-mod-new-group-form'); var createGroupFormEl = document.getElementById('users-mod-create-group-form'); var newGroupIdEl = document.getElementById('users-mod-new-group-id'); var userIdInput = null; var currentGroupId = null; var cachedGroups = []; function api(url, options) { options = options || {}; options.headers = options.headers || {}; if (options.body) { options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json'; } var token = (typeof OC !== 'undefined' && OC.requestToken != null) ? String(OC.requestToken) : ''; if (token) { options.headers['requesttoken'] = token; } if (options.body && typeof options.body === 'object' && !(options.body instanceof FormData)) { options.body = JSON.stringify(options.body); } var requestUrl = (url != null && url !== '') ? String(url) : ''; if (!requestUrl) { return Promise.reject(new Error('API URL is missing')); } return fetch(requestUrl, options).then(function(r) { if (!r.ok) { return r.text().then(function(text) { var msg = String(r.status); try { var j = JSON.parse(text); if (j && (j.message || j.error)) msg = j.message || j.error; } catch (e) { if (text) msg = text; } throw new Error(msg); }); } var ct = r.headers.get('Content-Type') || ''; if (ct.indexOf('application/json') !== -1) { return r.json(); } return r.text(); }); } function unwrap(data) { if (Array.isArray(data)) return data; if (data && data.data) return data.data; if (data && data.ocs && data.ocs.data) return data.ocs.data; return []; } function listUsers(search) { var url = listUrl; if (search) url += '?search=' + encodeURIComponent(search); return api(url); } function getUser(id) { return api(listUrl + '/' + encodeURIComponent(id)); } function createUser(data) { return api(listUrl, { method: 'POST', body: data }); } function updateUser(id, data) { return api(listUrl + '/' + encodeURIComponent(id), { method: 'PUT', body: data }); } function deleteUser(id) { return api(listUrl + '/' + encodeURIComponent(id), { method: 'DELETE' }); } function loadGroups() { return api(groupsUrl).then(unwrap); } function getGroup(gid) { return api(groupsUrl + '/' + encodeURIComponent(gid)); } function createGroup(gid) { return api(groupsUrl, { method: 'POST', body: { groupid: gid } }); } function deleteGroup(gid) { return api(groupsUrl + '/' + encodeURIComponent(gid), { method: 'DELETE' }); } function groupAddUser(gid, uid) { return api(groupsUrl + '/' + encodeURIComponent(gid) + '/users', { method: 'POST', body: { userid: uid } }); } function groupRemoveUser(gid, uid) { return api(groupsUrl + '/' + encodeURIComponent(gid) + '/users/' + encodeURIComponent(uid), { method: 'DELETE' }); } function escapeHtml(s) { var div = document.createElement('div'); div.textContent = s; return div.innerHTML; } function renderUserList(users) { listEl.innerHTML = ''; if (!users || users.length === 0) { listEmpty.classList.remove('hidden'); return; } listEmpty.classList.add('hidden'); users.forEach(function(u) { var li = document.createElement('li'); li.dataset.userId = u.id; li.innerHTML = '<span class="displayname">' + escapeHtml(u.displayname || u.id || '') + '</span>'; li.addEventListener('click', function() { selectUser(u.id); }); listEl.appendChild(li); }); } function renderGroupsList(groups) { if (Array.isArray(groups)) cachedGroups = groups; groups = cachedGroups; var filter = (groupSearchEl && groupSearchEl.value) ? groupSearchEl.value.trim().toLowerCase() : ''; groupsListEl.innerHTML = ''; var filtered = (groups || []).filter(function(g) { return !filter || String(g).toLowerCase().indexOf(filter) !== -1; }); if (filtered.length === 0) { groupsEmptyEl.classList.remove('hidden'); return; } groupsEmptyEl.classList.add('hidden'); filtered.forEach(function(gid) { var li = document.createElement('li'); li.dataset.groupId = gid; li.innerHTML = '<span class="gid">' + escapeHtml(gid) + '</span>'; li.addEventListener('click', function() { selectGroup(gid); }); groupsListEl.appendChild(li); }); } function setActiveUser(id) { [].forEach.call(listEl.querySelectorAll('li'), function(li) { li.classList.toggle('active', li.dataset.userId === id); }); [].forEach.call(groupsListEl.querySelectorAll('li'), function(li) { li.classList.remove('active'); }); } function setActiveGroup(id) { [].forEach.call(groupsListEl.querySelectorAll('li'), function(li) { li.classList.toggle('active', li.dataset.groupId === id); }); [].forEach.call(listEl.querySelectorAll('li'), function(li) { li.classList.remove('active'); }); } function showPlaceholder() { placeholder.classList.remove('hidden'); formEl.classList.add('hidden'); groupFormEl.classList.add('hidden'); if (newGroupFormEl) newGroupFormEl.classList.add('hidden'); currentGroupId = null; } function showNewGroupForm() { placeholder.classList.add('hidden'); formEl.classList.add('hidden'); groupFormEl.classList.add('hidden'); if (newGroupFormEl) { newGroupFormEl.classList.remove('hidden'); if (newGroupIdEl) { newGroupIdEl.value = ''; newGroupIdEl.focus(); } } } function fillUserForm(user) { var isNew = !user || !user.id; formId.value = user ? user.id : ''; document.getElementById('users-mod-displayname').value = user ? (user.displayname || '') : ''; document.getElementById('users-mod-email').value = user ? (user.email || '') : ''; document.getElementById('users-mod-phone').value = user ? (user.phone || '') : ''; document.getElementById('users-mod-address').value = user ? (user.address || '') : ''; document.getElementById('users-mod-website').value = user ? (user.website || '') : ''; document.getElementById('users-mod-twitter').value = user ? (user.twitter || '') : ''; document.getElementById('users-mod-quota').value = user ? (user.quota || '') : 'none'; document.getElementById('users-mod-enabled').checked = user ? (user.enabled !== false) : true; document.getElementById('users-mod-password').value = ''; var pwdConfirm = document.getElementById('users-mod-password-confirm'); if (pwdConfirm) pwdConfirm.value = ''; if (userIdInput) userIdInput.remove(); if (isNew) { userIdInput = document.createElement('div'); userIdInput.className = 'users-mod-form-grid'; userIdInput.innerHTML = '<label for="users-mod-userid">ID пользователя (логин)</label><input type="text" id="users-mod-userid" name="userid" required />'; var firstGrid = formEl.querySelector('.users-mod-form-grid'); formEl.insertBefore(userIdInput, firstGrid); } var userGroups = []; if (user && user.groups !== undefined && user.groups !== null) { if (Array.isArray(user.groups)) { userGroups = user.groups.slice(); } else if (typeof user.groups === 'object') { userGroups = Object.keys(user.groups).map(function(k) { return user.groups[k]; }); } } renderGroupsCheckboxes(userGroups); placeholder.classList.add('hidden'); groupFormEl.classList.add('hidden'); if (newGroupFormEl) newGroupFormEl.classList.add('hidden'); formEl.classList.remove('hidden'); deleteBtn.style.display = isNew ? 'none' : ''; } function fillGroupForm(group) { currentGroupId = group ? group.id : null; groupIdEl.value = group ? group.id : ''; groupMembersEl.innerHTML = ''; var members = (group && group.members) ? group.members : []; members.forEach(function(uid) { var li = document.createElement('li'); li.innerHTML = '<span>' + escapeHtml(uid) + '</span><button type="button" class="button remove-member">Удалить</button>'; li.querySelector('button').addEventListener('click', function() { groupRemoveUser(currentGroupId, uid).then(function() { refreshGroupForm(); }).catch(function() { if (OC.Notification) OC.Notification.showTemporary('Ошибка'); }); }); groupMembersEl.appendChild(li); }); refreshGroupAddSelect(members); placeholder.classList.add('hidden'); formEl.classList.add('hidden'); if (newGroupFormEl) newGroupFormEl.classList.add('hidden'); groupFormEl.classList.remove('hidden'); } function refreshGroupForm() { if (!currentGroupId) return; getGroup(currentGroupId).then(function(g) { fillGroupForm(g); setActiveGroup(currentGroupId); }); } function refreshGroupAddSelect(members) { members = members || []; groupAddUserSelect.innerHTML = ''; var opt = document.createElement('option'); opt.value = ''; opt.textContent = '— Выберите пользователя —'; groupAddUserSelect.appendChild(opt); listUsers('').then(function(users) { (users || []).forEach(function(u) { if (members.indexOf(u.id) !== -1) return; var o = document.createElement('option'); o.value = u.id; o.textContent = (u.displayname || u.id) + ' (' + u.id + ')'; groupAddUserSelect.appendChild(o); }); }).catch(function() {}); } function renderGroupsCheckboxes(selectedIds) { selectedIds = Array.isArray(selectedIds) ? selectedIds : []; var selectedSet = {}; selectedIds.forEach(function(s) { selectedSet[String(s)] = true; }); groupsContainer.innerHTML = ''; groupsContainer.classList.add('loading'); groupsContainer.textContent = 'Загрузка…'; loadGroups().then(function(data) { var ids = Array.isArray(data) ? data : (data && data.data ? data.data : []); if (!Array.isArray(ids)) ids = []; groupsContainer.classList.remove('loading'); groupsContainer.innerHTML = ''; ids.forEach(function(gid) { var gidStr = String(gid); var label = document.createElement('label'); var cb = document.createElement('input'); cb.type = 'checkbox'; cb.name = 'groups[]'; cb.value = gidStr; cb.checked = !!selectedSet[gidStr]; label.appendChild(cb); label.appendChild(document.createTextNode(gidStr)); groupsContainer.appendChild(label); }); if (ids.length === 0) { groupsContainer.appendChild(document.createTextNode('Нет групп')); } }).catch(function() { groupsContainer.classList.remove('loading'); groupsContainer.textContent = 'Не удалось загрузить группы'; }); } function getFormData() { var data = { displayname: document.getElementById('users-mod-displayname').value.trim(), email: document.getElementById('users-mod-email').value.trim(), phone: document.getElementById('users-mod-phone').value.trim(), address: document.getElementById('users-mod-address').value.trim(), website: document.getElementById('users-mod-website').value.trim(), twitter: document.getElementById('users-mod-twitter').value.trim(), quota: document.getElementById('users-mod-quota').value.trim() || 'none', enabled: document.getElementById('users-mod-enabled').checked, groups: [].map.call(groupsContainer.querySelectorAll('input:checked'), function(c) { return c.value; }) }; var pwd = document.getElementById('users-mod-password').value; var pwdConfirmEl = document.getElementById('users-mod-password-confirm'); var pwdConfirm = pwdConfirmEl ? pwdConfirmEl.value : ''; if (pwd && pwdConfirm && pwd === pwdConfirm) { data.password = pwd; } return data; } function selectUser(id) { setActiveUser(id); getUser(id).then(function(user) { fillUserForm(user); }).catch(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Не удалось загрузить пользователя'); }); } function selectGroup(gid) { setActiveGroup(gid); getGroup(gid).then(function(group) { fillGroupForm(group); }).catch(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Не удалось загрузить группу'); }); } var searchTimeout; searchEl.addEventListener('input', function() { clearTimeout(searchTimeout); searchTimeout = setTimeout(function() { listUsers(searchEl.value.trim()).then(renderUserList).catch(function() { renderUserList([]); }); }, 300); }); if (groupSearchEl) { groupSearchEl.addEventListener('input', function() { loadGroups().then(renderGroupsList).catch(function() { renderGroupsList([]); }); }); } newBtn.addEventListener('click', function() { setActiveUser(null); setActiveGroup(null); fillUserForm(null); }); newGroupBtn.addEventListener('click', function() { setActiveUser(null); setActiveGroup(null); showNewGroupForm(); }); if (createGroupFormEl) { createGroupFormEl.addEventListener('submit', function(e) { e.preventDefault(); var name = newGroupIdEl ? newGroupIdEl.value.trim() : ''; if (!name) { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Введите ID группы'); return; } createGroup(name).then(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Группа создана'); loadGroups().then(function(ids) { renderGroupsList(ids); setActiveUser(null); setActiveGroup(name); getGroup(name).then(fillGroupForm); }); }).catch(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Ошибка создания группы'); }); }); } groupAddBtn.addEventListener('click', function() { var uid = groupAddUserSelect.value; if (!uid || !currentGroupId) return; groupAddUser(currentGroupId, uid).then(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Пользователь добавлен'); refreshGroupForm(); loadGroups().then(renderGroupsList); }).catch(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Ошибка'); }); }); groupDeleteBtn.addEventListener('click', function() { if (!currentGroupId) return; if (!confirm('Удалить группу «' + currentGroupId + '»?')) return; deleteGroup(currentGroupId).then(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Группа удалена'); currentGroupId = null; showPlaceholder(); formEl.classList.add('hidden'); groupFormEl.classList.add('hidden'); placeholder.classList.remove('hidden'); loadGroups().then(renderGroupsList); }).catch(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Ошибка удаления'); }); }); formEl.addEventListener('submit', function(e) { e.preventDefault(); var id = formId.value.trim(); var pwd = document.getElementById('users-mod-password').value; var pwdConfirmEl = document.getElementById('users-mod-password-confirm'); var pwdConfirm = pwdConfirmEl ? pwdConfirmEl.value : ''; if (pwd || pwdConfirm) { if (pwd !== pwdConfirm) { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Пароль и подтверждение не совпадают'); return; } } var data = getFormData(); var promise; if (!id) { var useridEl = document.getElementById('users-mod-userid'); var userid = useridEl ? useridEl.value.trim() : ''; if (!userid) { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Введите ID пользователя (логин)'); return; } if (!pwd || !pwdConfirm) { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Укажите пароль и подтверждение для нового пользователя'); return; } if (pwd !== pwdConfirm) { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Пароль и подтверждение не совпадают'); return; } data.userid = userid; promise = createUser(data); } else { promise = updateUser(id, data); } promise.then(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Сохранено'); listUsers(searchEl.value.trim()).then(renderUserList); if (!id) { setActiveUser(data.userid); getUser(data.userid).then(fillUserForm); } }).catch(function(err) { var msg = (err && err.message) ? err.message : 'Ошибка сохранения'; if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary(msg); }); }); deleteBtn.addEventListener('click', function() { var id = formId.value.trim(); if (!id) return; if (!confirm('Удалить пользователя «' + id + '»? Это действие необратимо.')) return; deleteUser(id).then(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Пользователь удалён'); showPlaceholder(); listUsers(searchEl.value.trim()).then(renderUserList); }).catch(function() { if (typeof OC !== 'undefined' && OC.Notification) OC.Notification.showTemporary('Ошибка удаления'); }); }); function initLists() { listUsers('').then(renderUserList).catch(function() { renderUserList([]); }); loadGroups() .then(function(data) { var list = unwrap(data); renderGroupsList(Array.isArray(list) ? list : []); }) .catch(function() { renderGroupsList([]); }); } /* Первая загрузка: отложить, чтобы OC.requestToken был готов (группы видны с первого открытия) */ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { setTimeout(initLists, 50); }); } else { setTimeout(initLists, 50); } /* Повторная загрузка групп при фокусе на поиске групп (если при первом открытии не подтянулись) */ if (groupSearchEl) { groupSearchEl.addEventListener('focus', function() { if (cachedGroups.length === 0) { loadGroups().then(function(data) { var list = unwrap(data); renderGroupsList(Array.isArray(list) ? list : []); }); } }); } })();