Rework Photography admin dashboard
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 30s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 30s
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
$(document).ready(function() {
|
||||
var currentAdmin = null;
|
||||
var users = [];
|
||||
var folders = [];
|
||||
var assignedFolders = [];
|
||||
@@ -6,28 +7,46 @@ $(document).ready(function() {
|
||||
|
||||
$.get('/api/auth/me')
|
||||
.done(function(user) {
|
||||
if(user.role !== 'admin') {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -51,6 +70,72 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
|
||||
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');
|
||||
@@ -63,20 +148,20 @@ $(document).ready(function() {
|
||||
return;
|
||||
}
|
||||
|
||||
users.forEach(function(u) {
|
||||
var role = u.role || 'client';
|
||||
var created = u.created_at ? new Date(u.created_at).toLocaleDateString('de-DE') : '-';
|
||||
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' && u.expires_at) {
|
||||
status = (u.expired ? 'Abgelaufen: ' : 'Bis: ') +
|
||||
new Date(u.expires_at).toLocaleDateString('de-DE');
|
||||
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(u.username + ' ').append(getRoleBadge(role, u.expired));
|
||||
var $userCell = $('<td></td>').text(user.username + ' ').append(getRoleBadge(role, user.expired));
|
||||
var $foldersCell = $('<td></td>');
|
||||
if (u.folders && u.folders.length) {
|
||||
u.folders.forEach(function(folder) {
|
||||
if (user.folders && user.folders.length) {
|
||||
user.folders.forEach(function(folder) {
|
||||
$('<span></span>').addClass('badge folder-badge').text(folder).appendTo($foldersCell);
|
||||
});
|
||||
} else {
|
||||
@@ -85,18 +170,18 @@ $(document).ready(function() {
|
||||
|
||||
var $actions = $('<td></td>');
|
||||
$('<button type="button" class="btn-small user-access">Zugriff</button>')
|
||||
.data('user-id', u.id)
|
||||
.data('user-id', user.id)
|
||||
.appendTo($actions);
|
||||
$('<button type="button" class="btn-small user-password">PW</button>')
|
||||
.data('user-id', u.id)
|
||||
.data('username', u.username)
|
||||
.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', u.id)
|
||||
.data('username', u.username)
|
||||
.data('user-id', user.id)
|
||||
.data('username', user.username)
|
||||
.appendTo($actions);
|
||||
|
||||
$row.append($('<td></td>').text(u.id));
|
||||
$row.append($('<td></td>').text(user.id));
|
||||
$row.append($userCell);
|
||||
$row.append($('<td></td>').text(role));
|
||||
$row.append($('<td></td>').text(status));
|
||||
@@ -108,11 +193,11 @@ $(document).ready(function() {
|
||||
|
||||
function renderUserSelect() {
|
||||
var $select = $('#user-select').html('<option value="">Nutzer waehlen...</option>');
|
||||
users.forEach(function(u) {
|
||||
if ((u.role || 'client') === 'admin') return;
|
||||
users.forEach(function(user) {
|
||||
if ((user.role || 'client') === 'admin') return;
|
||||
$('<option></option>')
|
||||
.val(u.id)
|
||||
.text(u.username + ' (' + (u.role || 'client') + ')')
|
||||
.val(user.id)
|
||||
.text(user.username + ' (' + (user.role || 'client') + ')')
|
||||
.appendTo($select);
|
||||
});
|
||||
if (selectedUserId) $select.val(selectedUserId);
|
||||
@@ -174,11 +259,31 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -186,10 +291,14 @@ $(document).ready(function() {
|
||||
$.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();
|
||||
});
|
||||
}
|
||||
@@ -215,7 +324,7 @@ $(document).ready(function() {
|
||||
|
||||
$('#users-tbody').on('click', '.user-access', function() {
|
||||
loadAssignments($(this).data('user-id'));
|
||||
document.getElementById('user-select').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
showView('access');
|
||||
});
|
||||
|
||||
$('#user-select').change(function() {
|
||||
@@ -226,8 +335,9 @@ $(document).ready(function() {
|
||||
|
||||
$('#folders-overview').on('click', '.folder-assign-btn', function() {
|
||||
var folder = $(this).data('folder');
|
||||
showView('access');
|
||||
if (!selectedUserId) {
|
||||
alert('Bitte zuerst einen Nutzer in der Ordner-Zuweisung waehlen.');
|
||||
alert('Bitte zuerst einen Nutzer in den Freigaben waehlen.');
|
||||
return;
|
||||
}
|
||||
var $checkbox = $('#folder-access-list input').filter(function() {
|
||||
@@ -240,6 +350,7 @@ $(document).ready(function() {
|
||||
|
||||
$('#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();
|
||||
@@ -288,7 +399,13 @@ $(document).ready(function() {
|
||||
var name = $(this).data('username');
|
||||
if (!confirm('Nutzer "' + name + '" wirklich loeschen?')) return;
|
||||
$.ajax({ url: '/api/admin/users/' + id, type: 'DELETE' })
|
||||
.done(refreshDashboard)
|
||||
.done(function() {
|
||||
if (String(selectedUserId) === String(id)) {
|
||||
selectedUserId = "";
|
||||
assignedFolders = [];
|
||||
}
|
||||
refreshDashboard();
|
||||
})
|
||||
.fail(function(err) { alert(err.responseJSON ? err.responseJSON.error : 'Fehler'); });
|
||||
});
|
||||
|
||||
@@ -309,22 +426,6 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$('#sync-site').click(function() {
|
||||
$('#sync-status').text('Sync laeuft...');
|
||||
$('#sync-site').prop('disabled', true);
|
||||
$.post('/api/admin/sync')
|
||||
.done(function(resp) {
|
||||
$('#sync-status').text(resp.message || 'Sync gestartet.');
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
var msg = (xhr.responseJSON && (xhr.responseJSON.error || xhr.responseJSON.details)) || 'Sync fehlgeschlagen';
|
||||
$('#sync-status').text(msg);
|
||||
})
|
||||
.always(function() {
|
||||
$('#sync-site').prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
$('#admin-pw-form').submit(function(e) {
|
||||
e.preventDefault();
|
||||
$.post('/api/auth/change-password', {
|
||||
|
||||
Reference in New Issue
Block a user