This commit is contained in:
2026-05-01 03:50:25 +03:00
parent 43597be020
commit bc7b5bfe37
10 changed files with 549 additions and 185 deletions

View File

@@ -1078,6 +1078,7 @@ async function resumeManga(url) {
// ── Users management (admin only) ─────────────
let _userModalEditId = null;
const _userDataCache = {};
function showUsersSection() {
if(isAdmin()) {
@@ -1103,6 +1104,7 @@ function renderUsers(users) {
el.innerHTML = '<div class="text-xs text-gray-500">Нет пользователей</div>';
return;
}
users.forEach(u => { _userDataCache[u.id] = u; });
const roleColors = {admin: 'color:#fbbf24;background:#292103', user: 'color:#86efac;background:#032911'};
el.innerHTML = users.map(u => `
<div class="flex items-center justify-between px-3 py-2 rounded-lg" style="background:#1e293b">
@@ -1114,13 +1116,25 @@ function renderUsers(users) {
${u.is_env_admin ? '<span class="text-xs text-gray-500" title="Системный администратор — пароль задаётся через AUTH_PASSWORD">🔒</span>' : ''}
</div>
<div class="flex gap-2">
<button onclick="openEditUserModal(${u.id}, '${escHtml(u.username)}', '${u.role}', ${!!u.is_env_admin})"
<button data-action="edit-user" data-id="${u.id}"
class="text-xs px-2 py-1 rounded text-gray-400 hover:text-white" style="background:#334155">✏️</button>
${!u.is_env_admin && u.id !== state.currentUser?.id ? `<button onclick="confirmDeleteUser(${u.id}, '${escHtml(u.username)}')"
${!u.is_env_admin && u.id !== state.currentUser?.id ? `<button data-action="delete-user" data-id="${u.id}"
class="text-xs px-2 py-1 rounded text-red-400 hover:text-red-300" style="background:#2d1111">✕</button>` : ''}
</div>
</div>
`).join('');
el.querySelectorAll('[data-action="edit-user"]').forEach(btn => {
btn.addEventListener('click', () => {
const u = _userDataCache[+btn.dataset.id];
if(u) openEditUserModal(u.id, u.username, u.role, !!u.is_env_admin);
});
});
el.querySelectorAll('[data-action="delete-user"]').forEach(btn => {
btn.addEventListener('click', () => {
const u = _userDataCache[+btn.dataset.id];
if(u) confirmDeleteUser(u.id, u.username);
});
});
}
function openAddUserModal() {
@@ -1523,23 +1537,18 @@ async function refreshMetaModal(url) {
}
}
async function forceRedownload(url) {
async function forceRedownload(url, closeModalAfter = false) {
if(!confirm('Скачать заново ВСЕ главы? Уже скачанные файлы будут перезаписаны.')) return;
const r = await fetch('/api/mangas/force_redownload?url='+encodeURIComponent(url), {method:'POST'});
if(r.ok && state.mangas[url]) {
state.mangas[url].status = 'queued';
updateMangaRow(url);
}
if(closeModalAfter) closeModal();
}
async function forceRedownloadModal(url) {
if(!confirm('Скачать заново ВСЕ главы? Уже скачанные файлы будут перезаписаны.')) return;
const r = await fetch('/api/mangas/force_redownload?url='+encodeURIComponent(url), {method:'POST'});
if(r.ok && state.mangas[url]) {
state.mangas[url].status = 'queued';
updateMangaRow(url);
}
closeModal();
return forceRedownload(url, true);
}
async function openDetail(url, initialTab = 'overview') {
@@ -2280,8 +2289,8 @@ async function saveRenameFolder() {
headers: {'Content-Type':'application/json'},
body: JSON.stringify({url: _renameFolderUrl, folder_name}),
});
if(!r.ok) throw new Error((await r.json()).detail || await r.text());
const data = await r.json();
if(!r.ok) throw new Error(data.detail || 'Ошибка сервера');
if(state.mangas[_renameFolderUrl]) {
state.mangas[_renameFolderUrl].folder_name = data.folder_name;
}