Files
Photography/assets/js/admin.js
Kroonk 3ad76e841d
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 30s
Rework Photography admin dashboard
2026-05-20 19:13:51 +02:00

442 lines
17 KiB
JavaScript

$(document).ready(function() {
var currentAdmin = null;
var users = [];
var folders = [];
var assignedFolders = [];
var selectedUserId = "";
$.get('/api/auth/me')
.done(function(user) {
if (user.role !== 'admin') {
alert('Kein Zugriff!');
window.location.href = '/';
return;
}
currentAdmin = user;
$('#admin-user-label').text(user.username || 'Admin');
refreshDashboard();
$('body').removeClass('is-preload');
})
.fail(function() { window.location.href = '/'; });
function showView(name) {
$('.admin-view').removeClass('active').filter('[data-view="' + name + '"]').addClass('active');
$('.nav-item').removeClass('active').filter('[data-view="' + name + '"]').addClass('active');
window.scrollTo({ top: 0, behavior: 'smooth' });
}
$('.admin-nav').on('click', '.nav-item', function() {
showView($(this).data('view'));
});
$('#logout-btn').click(function(e) {
e.preventDefault();
$.post('/api/auth/logout').done(function() { window.location.href = '/'; });
});
$('#refresh-dashboard').click(refreshDashboard);
function refreshDashboard() {
$.when(loadUsers(), loadFolders()).done(function() {
renderStats();
renderUsers();
renderUserSelect();
renderFolders();
if (selectedUserId) {
loadAssignments(selectedUserId);
} else {
renderSelectedUserSummary(null);
}
});
}
function loadUsers() {
return $.get('/api/admin/users').done(function(data) {
users = data || [];
}).fail(function(xhr) {
$('#users-tbody').html(
'<tr><td colspan="6" style="color:#e44d26;">Fehler beim Laden: HTTP ' +
xhr.status + '</td></tr>'
);
});
}
function loadFolders() {
return $.get('/api/admin/folders').done(function(data) {
folders = data || [];
}).fail(function() {
$('#folders-overview').html('<p style="color:#e44d26;">Ordner konnten nicht geladen werden.</p>');
});
}
function roleCounts() {
return users.reduce(function(acc, user) {
var role = user.role || 'client';
acc[role] = (acc[role] || 0) + 1;
return acc;
}, { admin: 0, user: 0, client: 0 });
}
function totalAssignments() {
return users.reduce(function(total, user) {
return total + ((user.folders && user.folders.length) || 0);
}, 0);
}
function totalImages() {
return folders.reduce(function(total, folder) {
return total + (folder.image_count || 0);
}, 0);
}
function renderStats() {
var roles = roleCounts();
var expiredClients = users.filter(function(user) { return user.expired; }).length;
var unassignedUsers = users.filter(function(user) {
return (user.role || 'client') !== 'admin' && (!user.folders || !user.folders.length);
}).length;
var unsharedFolders = folders.filter(function(folder) {
return !folder.assigned_users || !folder.assigned_users.length;
}).length;
$('#stat-users').text(users.length);
$('#stat-users-sub').text(roles.admin + ' Admins, ' + roles.user + ' User');
$('#stat-folders').text(folders.length);
$('#stat-folders-sub').text(totalImages() + ' Bilder in privaten Ordnern');
$('#stat-access').text(totalAssignments());
$('#stat-access-sub').text(unassignedUsers + ' Nutzer ohne Ordner');
$('#stat-clients').text(roles.client);
$('#stat-clients-sub').text(expiredClients + ' abgelaufen');
renderAttentionList(unassignedUsers, unsharedFolders, expiredClients);
renderRoleList(roles);
}
function appendSignal($target, label, value, muted) {
var $item = $('<div></div>').addClass('signal-item');
$('<strong></strong>').text(label).appendTo($item);
$('<span></span>').toggleClass('muted', !!muted).text(value).appendTo($item);
$target.append($item);
}
function renderAttentionList(unassignedUsers, unsharedFolders, expiredClients) {
var $list = $('#attention-list').empty();
appendSignal($list, 'Nutzer ohne Ordner', unassignedUsers, unassignedUsers === 0);
appendSignal($list, 'Ordner ohne Freigabe', unsharedFolders, unsharedFolders === 0);
appendSignal($list, 'Abgelaufene Clients', expiredClients, expiredClients === 0);
appendSignal($list, 'Gesamtbilder privat', totalImages(), totalImages() === 0);
}
function renderRoleList(roles) {
var $list = $('#role-list').empty();
appendSignal($list, 'Admins', roles.admin || 0);
appendSignal($list, 'Permanente User', roles.user || 0);
appendSignal($list, 'Clients', roles.client || 0);
appendSignal($list, 'Freigaben', totalAssignments());
}
function getRoleBadge(role, expired) {
if (expired) return $('<span></span>').addClass('badge badge-expired').text('abgelaufen');
return $('<span></span>').addClass('badge badge-' + (role || 'client')).text(role || 'client');
}
function renderUsers() {
var $tbody = $('#users-tbody').empty();
if (!users.length) {
$tbody.html('<tr><td colspan="6" style="text-align:center;">Keine Nutzer gefunden</td></tr>');
return;
}
users.forEach(function(user) {
var role = user.role || 'client';
var created = user.created_at ? new Date(user.created_at).toLocaleDateString('de-DE') : '-';
var status = created;
if (role === 'client' && user.expires_at) {
status = (user.expired ? 'Abgelaufen: ' : 'Bis: ') +
new Date(user.expires_at).toLocaleDateString('de-DE');
}
var $row = $('<tr></tr>');
var $userCell = $('<td></td>').text(user.username + ' ').append(getRoleBadge(role, user.expired));
var $foldersCell = $('<td></td>');
if (user.folders && user.folders.length) {
user.folders.forEach(function(folder) {
$('<span></span>').addClass('badge folder-badge').text(folder).appendTo($foldersCell);
});
} else {
$foldersCell.append($('<span></span>').addClass('muted').text('Keine privaten Ordner'));
}
var $actions = $('<td></td>');
$('<button type="button" class="btn-small user-access">Zugriff</button>')
.data('user-id', user.id)
.appendTo($actions);
$('<button type="button" class="btn-small user-password">PW</button>')
.data('user-id', user.id)
.data('username', user.username)
.appendTo($actions);
$('<button type="button" class="btn-small btn-danger user-delete">Loeschen</button>')
.data('user-id', user.id)
.data('username', user.username)
.appendTo($actions);
$row.append($('<td></td>').text(user.id));
$row.append($userCell);
$row.append($('<td></td>').text(role));
$row.append($('<td></td>').text(status));
$row.append($foldersCell);
$row.append($actions);
$tbody.append($row);
});
}
function renderUserSelect() {
var $select = $('#user-select').html('<option value="">Nutzer waehlen...</option>');
users.forEach(function(user) {
if ((user.role || 'client') === 'admin') return;
$('<option></option>')
.val(user.id)
.text(user.username + ' (' + (user.role || 'client') + ')')
.appendTo($select);
});
if (selectedUserId) $select.val(selectedUserId);
}
function renderFolders() {
var $wrap = $('#folders-overview').empty();
if (!folders.length) {
$wrap.html('<p class="muted">Keine Unterordner im Bilderpfad gefunden.</p>');
return;
}
folders.forEach(function(folder) {
var $card = $('<div></div>').addClass('folder-card');
$('<h3></h3>').text(folder.name).appendTo($card);
var $meta = $('<div></div>').addClass('folder-meta').appendTo($card);
$('<span></span>').addClass('folder-pill').text((folder.image_count || 0) + ' Bilder').appendTo($meta);
$('<span></span>').addClass('folder-pill').text('privat').appendTo($meta);
$('<span></span>').addClass('folder-pill').text((folder.assigned_users || []).length + ' Freigaben').appendTo($meta);
var $assigned = $('<div></div>').appendTo($card);
if (folder.assigned_users && folder.assigned_users.length) {
folder.assigned_users.forEach(function(user) {
$('<span></span>').addClass('badge folder-badge').text(user.username).appendTo($assigned);
});
} else {
$assigned.append($('<span></span>').addClass('muted').text('Noch niemandem freigegeben'));
}
var $actions = $('<div></div>').addClass('folder-actions').appendTo($card);
$('<button type="button" class="btn-small folder-preview-btn">Ansehen</button>')
.data('folder', folder.name)
.appendTo($actions);
$('<button type="button" class="btn-small folder-assign-btn">Freigaben</button>')
.data('folder', folder.name)
.appendTo($actions);
$wrap.append($card);
});
}
function renderAccessList() {
var $list = $('#folder-access-list').empty();
if (!folders.length) {
$list.html('<p class="muted">Keine Unterordner gefunden.</p>');
return;
}
folders.forEach(function(folder) {
var checked = assignedFolders.indexOf(folder.name) !== -1;
var $label = $('<label></label>').addClass('access-item');
$('<input type="checkbox" />')
.val(folder.name)
.prop('checked', checked)
.appendTo($label);
$('<span></span>').text(folder.name + ' (' + (folder.image_count || 0) + ')').appendTo($label);
$list.append($label);
});
}
function findUser(userId) {
var id = String(userId || '');
return users.find(function(user) { return String(user.id) === id; }) || null;
}
function renderSelectedUserSummary(user) {
var $summary = $('#selected-user-summary').empty();
if (!user) {
appendSignal($summary, 'Auswahl', 'Kein Nutzer', true);
return;
}
appendSignal($summary, 'Rolle', user.role || 'client');
appendSignal($summary, 'Ordner', (assignedFolders.length || 0) + ' freigegeben');
if (user.expires_at) {
appendSignal($summary, 'Client bis', new Date(user.expires_at).toLocaleDateString('de-DE'), !!user.expired);
}
}
function loadAssignments(userId) {
selectedUserId = String(userId || "");
if (!selectedUserId) {
$('#folder-table-wrap').hide();
$('#folder-access-empty').show();
assignedFolders = [];
renderSelectedUserSummary(null);
return;
}
$('#user-select').val(selectedUserId);
$.get('/api/admin/assign?user_id=' + encodeURIComponent(selectedUserId)).done(function(data) {
assignedFolders = data || [];
renderAccessList();
renderSelectedUserSummary(findUser(selectedUserId));
$('#folder-access-empty').hide();
$('#folder-table-wrap').show();
}).fail(function() {
assignedFolders = [];
renderAccessList();
renderSelectedUserSummary(findUser(selectedUserId));
$('#folder-access-empty').hide();
$('#folder-table-wrap').show();
});
}
function saveAssignments() {
if (!selectedUserId) return;
assignedFolders = [];
$('#folder-access-list input:checked').each(function() {
assignedFolders.push($(this).val());
});
$.ajax({
url: '/api/admin/assign',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ user_id: selectedUserId, folders: assignedFolders })
}).done(function() {
$('#assign-status').text('Gespeichert').show().delay(1200).fadeOut();
refreshDashboard();
}).fail(function(xhr) {
alert((xhr.responseJSON && xhr.responseJSON.error) || 'Speichern fehlgeschlagen');
});
}
$('#users-tbody').on('click', '.user-access', function() {
loadAssignments($(this).data('user-id'));
showView('access');
});
$('#user-select').change(function() {
loadAssignments($(this).val());
});
$('#folder-access-list').on('change', 'input[type="checkbox"]', saveAssignments);
$('#folders-overview').on('click', '.folder-assign-btn', function() {
var folder = $(this).data('folder');
showView('access');
if (!selectedUserId) {
alert('Bitte zuerst einen Nutzer in den Freigaben waehlen.');
return;
}
var $checkbox = $('#folder-access-list input').filter(function() {
return $(this).val() === folder;
});
if ($checkbox.length) {
$checkbox.prop('checked', !$checkbox.prop('checked')).trigger('change');
}
});
$('#folders-overview').on('click', '.folder-preview-btn', function() {
var folder = $(this).data('folder');
showView('preview');
$('#folder-preview-title').text('Ordnerinhalt: ' + folder);
$('#folder-preview-note').text('Lade Bilder...');
$('#folder-preview').empty();
$.get('/api/admin/folders/' + encodeURIComponent(folder) + '/images').done(function(images) {
$('#folder-preview-note').text((images || []).length + ' Bilder im Ordner');
var $preview = $('#folder-preview').empty();
if (!images || !images.length) {
$preview.html('<p class="muted">Dieser Ordner ist leer.</p>');
return;
}
images.forEach(function(img) {
var $link = $('<a></a>').attr({ href: img.full_url, target: '_blank', rel: 'noopener' });
$('<img />').attr({ src: img.thumb_url, alt: img.name, title: img.name }).appendTo($link);
$preview.append($link);
});
}).fail(function(xhr) {
$('#folder-preview-note').text((xhr.responseJSON && xhr.responseJSON.error) || 'Ordner konnte nicht geladen werden.');
});
});
$('#create-user-form').submit(function(e) {
e.preventDefault();
var username = $('#new-username').val().trim();
var password = $('#new-password').val();
var role = $('#new-role').val();
if (!username || !password) {
alert('Bitte Username und Passwort eingeben.');
return;
}
$.ajax({
url: '/api/admin/users',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ username: username, password: password, role: role })
}).done(function() {
$('#new-username, #new-password').val('');
refreshDashboard();
}).fail(function(xhr) {
var msg = (xhr.responseJSON && xhr.responseJSON.error) || 'Unbekannter Fehler';
alert('Fehler beim Erstellen: ' + msg);
});
});
$('#users-tbody').on('click', '.user-delete', function() {
var id = $(this).data('user-id');
var name = $(this).data('username');
if (!confirm('Nutzer "' + name + '" wirklich loeschen?')) return;
$.ajax({ url: '/api/admin/users/' + id, type: 'DELETE' })
.done(function() {
if (String(selectedUserId) === String(id)) {
selectedUserId = "";
assignedFolders = [];
}
refreshDashboard();
})
.fail(function(err) { alert(err.responseJSON ? err.responseJSON.error : 'Fehler'); });
});
$('#users-tbody').on('click', '.user-password', function() {
var id = $(this).data('user-id');
var name = $(this).data('username');
var newPw = prompt('Neues Passwort fuer "' + name + '":');
if (!newPw) return;
$.ajax({
url: '/api/admin/users/' + id + '/password',
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify({ new_password: newPw })
}).done(function() {
alert('Passwort geaendert.');
}).fail(function(err) {
alert(err.responseJSON ? err.responseJSON.error : 'Fehler');
});
});
$('#admin-pw-form').submit(function(e) {
e.preventDefault();
$.post('/api/auth/change-password', {
old_password: $('#admin-pw-old').val(),
new_password: $('#admin-pw-new').val()
}).done(function() {
alert('Passwort geaendert.');
$('#admin-pw-old, #admin-pw-new').val('');
}).fail(function(xhr) {
alert(xhr.responseJSON ? xhr.responseJSON.error : 'Fehler');
});
});
});