Nutzerverwaltung-Upgrade: 3 Rollen, Ordner-Zuweisung, Passwort-Aenderung

- 3 Rollen: admin (voll), user (permanent), client (30 Tage Ablauf)
- Ordner-basierte Bildzuweisung statt Einzelbilder (user_folders Tabelle)
- Root-Bilder oeffentlich, Unterordner nur fuer zugewiesene Nutzer
- Passwort-Aenderung fuer alle Nutzer (Self-Service + Admin)
- Admin kann beliebige Admins erstellen/loeschen
- Thumbnail-Generierung fuer Unterordner
- Admin-Dashboard komplett ueberarbeitet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kroonk
2026-04-12 17:53:46 +02:00
parent a4f3cf738c
commit 941d041b5d
9 changed files with 429 additions and 171 deletions

View File

@@ -1,101 +1,167 @@
$(document).ready(function() {
// Check if admin
// Auth check
$.get('/api/auth/me')
.done(function(user) {
if(user.role !== 'admin') {
alert("Kein Zugriff!");
alert('Kein Zugriff!');
window.location.href = '/';
} else {
loadUsers();
loadAssignmentImages();
loadFolders();
$('body').removeClass('is-preload');
}
})
.fail(function() {
window.location.href = '/';
});
.fail(function() { window.location.href = '/'; });
// Logout
$('#logout-btn').click(function(e) {
e.preventDefault();
$.post('/api/auth/logout').done(function() { window.location.href = '/'; });
});
// ==================== NUTZER ====================
function loadUsers() {
$.get('/api/admin/users').done(function(users) {
$('#users-tbody').empty();
$('#user-select').html('<option value="">Nutzer wählen...</option>');
$('#user-select').html('<option value="">Nutzer waehlen...</option>');
users.forEach(function(u) {
var badge = '<span class="badge badge-' + u.role + '">' + u.role + '</span>';
var created = u.created_at ? new Date(u.created_at).toLocaleDateString('de-DE') : '-';
var info = created;
if (u.role === 'client' && u.expires_at) {
var expDate = new Date(u.expires_at).toLocaleDateString('de-DE');
if (u.expired) {
badge = '<span class="badge badge-expired">abgelaufen</span>';
info = 'Abgelaufen: ' + expDate;
} else {
info = 'Bis: ' + expDate;
}
}
var actions = '<button class="btn-small" onclick="changePassword(' + u.id + ', \'' + u.username.replace(/'/g, "\\'") + '\')">PW</button>';
actions += '<button class="btn-small" onclick="deleteUser(' + u.id + ', \'' + u.username.replace(/'/g, "\\'") + '\')">X</button>';
$('#users-tbody').append(
'<tr>' +
'<td>' + u.id + '</td>' +
'<td>' + u.username + ' ' + (u.role === 'admin' ? '(Admin)' : '') + '</td>' +
'<td>' + (u.id !== 1 ? '<button onclick="deleteUser(' + u.id + ')">Löschen</button>' : '') + '</td>' +
'</tr>'
'<tr><td>' + u.id + '</td><td>' + u.username + ' ' + badge +
'</td><td>' + u.role + '</td><td>' + info +
'</td><td>' + actions + '</td></tr>'
);
if(u.role !== 'admin') {
$('#user-select').append('<option value="' + u.id + '">' + u.username + '</option>');
// Dropdown fuer Ordner-Zuweisung (nur non-admin)
if (u.role !== 'admin') {
$('#user-select').append('<option value="' + u.id + '">' + u.username + ' (' + u.role + ')</option>');
}
});
});
}
window.deleteUser = function(id) {
if(confirm("Nutzer loeschen?")) {
$.ajax({url: '/api/admin/users/'+id, type: 'DELETE'}).done(loadUsers);
// Nutzer erstellen
$('#create-user-form').submit(function(e) {
e.preventDefault();
$.post('/api/admin/users', {
username: $('#new-username').val(),
password: $('#new-password').val(),
role: $('#new-role').val()
}).done(function() {
$('#new-username').val('');
$('#new-password').val('');
loadUsers();
}).fail(function(err) {
alert('Fehler: ' + (err.responseJSON ? err.responseJSON.error : 'Unbekannt'));
});
});
// Nutzer loeschen
window.deleteUser = function(id, name) {
if (confirm('Nutzer "' + name + '" wirklich loeschen?')) {
$.ajax({ url: '/api/admin/users/' + id, type: 'DELETE' })
.done(loadUsers)
.fail(function(err) { alert(err.responseJSON ? err.responseJSON.error : 'Fehler'); });
}
};
$('#create-user-form').submit(function(e) {
e.preventDefault();
$.post('/api/admin/users', { username: $('#new-username').val(), password: $('#new-password').val(), role: 'user' })
.done(function() {
$('#new-username').val('');
$('#new-password').val('');
loadUsers();
}).fail(function(err) { alert("Fehler: " + err.responseJSON.error); });
});
// Passwort aendern (Admin setzt fuer anderen User)
window.changePassword = function(id, name) {
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');
});
};
function loadAssignmentImages() {
$.get('/images/fulls/').done(function(files) {
$('#image-checkboxes').empty();
files.forEach(function(f) {
$('#image-checkboxes').append(
'<label class="img-check">' +
'<img src="/images/thumbs/' + encodeURIComponent(f.name) + '" />' +
'<br/><input type="checkbox" value="' + f.name + '" class="img-cb"/>' +
'</label>'
// ==================== ORDNER-ZUWEISUNG ====================
function loadFolders() {
$.get('/api/admin/folders').done(function(folders) {
$('#folder-checkboxes').empty();
if (folders.length === 0) {
$('#folder-checkboxes').html('<p>Keine Unterordner in Websitebilder gefunden. Erstelle Ordner in Nextcloud.</p>');
return;
}
folders.forEach(function(f) {
$('#folder-checkboxes').append(
'<div class="folder-check"><label>' +
'<input type="checkbox" value="' + f + '" class="folder-cb"/> ' +
'<i class="fa fa-folder"></i> ' + f +
'</label></div>'
);
});
});
}
// Bei User-Auswahl: zugewiesene Ordner laden
$('#user-select').change(function() {
var uid = $(this).val();
$('.img-cb').prop('checked', false);
if(!uid) return;
$.get('/api/admin/assign').done(function(assignments) {
assignments.filter(function(a) { return a.user_id == uid; }).forEach(function(a) {
$('.img-cb[value="' + a.image_name + '"]').prop('checked', true);
$('.folder-cb').prop('checked', false);
if (!uid) return;
$.get('/api/admin/assign?user_id=' + uid).done(function(assignedFolders) {
assignedFolders.forEach(function(f) {
$('.folder-cb[value="' + f + '"]').prop('checked', true);
});
});
});
// Zuweisung speichern
$('#save-assignments').click(function() {
var uid = $('#user-select').val();
if(!uid) return alert('Bitte Nutzer waehlen');
if (!uid) return alert('Bitte Nutzer waehlen');
var selected = [];
$('.img-cb:checked').each(function() { selected.push($(this).val()); });
$.post('/api/admin/assign', { user_id: uid, image_names: selected })
.done(function() { alert("Zuweisung gespeichert!"); });
$('.folder-cb:checked').each(function() { selected.push($(this).val()); });
$.ajax({
url: '/api/admin/assign',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ user_id: uid, folders: selected })
}).done(function() {
$('#assign-status').text('Gespeichert!').fadeIn().delay(2000).fadeOut();
}).fail(function() {
alert('Fehler beim Speichern');
});
});
$('#update-btn').click(function() {
if(!confirm("Achtung: Dies startet den Server auf dem NAS neu. Fortfahren?")) return;
$('#update-status').text('Update laeuft...');
$.post('/api/admin/update').done(function(res) {
$('#update-status').text(res.message);
setTimeout(function() { window.location.reload(); }, 5000);
// ==================== EIGENES PASSWORT ====================
$('#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(err) {
alert(err.responseJSON ? err.responseJSON.error : 'Fehler');
});
});
});

View File

@@ -1 +1 @@
$(document).ready(function(){function e(){$.get("/api/admin/users").done(function(e){$("#users-tbody").empty(),$("#user-select").html('<option value="">Nutzer wählen...</option>'),e.forEach(function(e){$("#users-tbody").append("<tr><td>"+e.id+"</td><td>"+e.username+" "+("admin"===e.role?"(Admin)":"")+"</td><td>"+(1!==e.id?'<button onclick="deleteUser('+e.id+')">Löschen</button>':"")+"</td></tr>"),"admin"!==e.role&&$("#user-select").append('<option value="'+e.id+'">'+e.username+"</option>")})})}$.get("/api/auth/me").done(function(n){"admin"!==n.role?(alert("Kein Zugriff!"),window.location.href="/"):(e(),$.get("/images/fulls/").done(function(e){$("#image-checkboxes").empty(),e.forEach(function(e){$("#image-checkboxes").append('<label class="img-check"><img src="/images/thumbs/'+encodeURIComponent(e.name)+'" /><br/><input type="checkbox" value="'+e.name+'" class="img-cb"/></label>')})}),$("body").removeClass("is-preload"))}).fail(function(){window.location.href="/"}),$("#logout-btn").click(function(e){e.preventDefault(),$.post("/api/auth/logout").done(function(){window.location.href="/"})}),window.deleteUser=function(n){confirm("Nutzer loeschen?")&&$.ajax({url:"/api/admin/users/"+n,type:"DELETE"}).done(e)},$("#create-user-form").submit(function(n){n.preventDefault(),$.post("/api/admin/users",{username:$("#new-username").val(),password:$("#new-password").val(),role:"user"}).done(function(){$("#new-username").val(""),$("#new-password").val(""),e()}).fail(function(e){alert("Fehler: "+e.responseJSON.error)})}),$("#user-select").change(function(){var e=$(this).val();$(".img-cb").prop("checked",!1),e&&$.get("/api/admin/assign").done(function(n){n.filter(function(n){return n.user_id==e}).forEach(function(e){$('.img-cb[value="'+e.image_name+'"]').prop("checked",!0)})})}),$("#save-assignments").click(function(){var e=$("#user-select").val();if(!e)return alert("Bitte Nutzer waehlen");var n=[];$(".img-cb:checked").each(function(){n.push($(this).val())}),$.post("/api/admin/assign",{user_id:e,image_names:n}).done(function(){alert("Zuweisung gespeichert!")})}),$("#update-btn").click(function(){confirm("Achtung: Dies startet den Server auf dem NAS neu. Fortfahren?")&&($("#update-status").text("Update laeuft..."),$.post("/api/admin/update").done(function(e){$("#update-status").text(e.message),setTimeout(function(){window.location.reload()},5e3)}))})});
$(document).ready(function(){function e(){$.get("/api/admin/users").done(function(e){$("#users-tbody").empty(),$("#user-select").html('<option value="">Nutzer waehlen...</option>'),e.forEach(function(e){var n='<span class="badge badge-'+e.role+'">'+e.role+"</span>",a=e.created_at?new Date(e.created_at).toLocaleDateString("de-DE"):"-";if("client"===e.role&&e.expires_at){var t=new Date(e.expires_at).toLocaleDateString("de-DE");e.expired?(n='<span class="badge badge-expired">abgelaufen</span>',a="Abgelaufen: "+t):a="Bis: "+t}var r='<button class="btn-small" onclick="changePassword('+e.id+", '"+e.username.replace(/'/g,"\\'")+"')\">PW</button>";r+='<button class="btn-small" onclick="deleteUser('+e.id+", '"+e.username.replace(/'/g,"\\'")+"')\">X</button>",$("#users-tbody").append("<tr><td>"+e.id+"</td><td>"+e.username+" "+n+"</td><td>"+e.role+"</td><td>"+a+"</td><td>"+r+"</td></tr>"),"admin"!==e.role&&$("#user-select").append('<option value="'+e.id+'">'+e.username+" ("+e.role+")</option>")})})}$.get("/api/auth/me").done(function(n){"admin"!==n.role?(alert("Kein Zugriff!"),window.location.href="/"):(e(),$.get("/api/admin/folders").done(function(e){$("#folder-checkboxes").empty(),0!==e.length?e.forEach(function(e){$("#folder-checkboxes").append('<div class="folder-check"><label><input type="checkbox" value="'+e+'" class="folder-cb"/> <i class="fa fa-folder"></i> '+e+"</label></div>")}):$("#folder-checkboxes").html("<p>Keine Unterordner in Websitebilder gefunden. Erstelle Ordner in Nextcloud.</p>")}),$("body").removeClass("is-preload"))}).fail(function(){window.location.href="/"}),$("#logout-btn").click(function(e){e.preventDefault(),$.post("/api/auth/logout").done(function(){window.location.href="/"})}),$("#create-user-form").submit(function(n){n.preventDefault(),$.post("/api/admin/users",{username:$("#new-username").val(),password:$("#new-password").val(),role:$("#new-role").val()}).done(function(){$("#new-username").val(""),$("#new-password").val(""),e()}).fail(function(e){alert("Fehler: "+(e.responseJSON?e.responseJSON.error:"Unbekannt"))})}),window.deleteUser=function(n,a){confirm('Nutzer "'+a+'" wirklich loeschen?')&&$.ajax({url:"/api/admin/users/"+n,type:"DELETE"}).done(e).fail(function(e){alert(e.responseJSON?e.responseJSON.error:"Fehler")})},window.changePassword=function(e,n){var a=prompt('Neues Passwort fuer "'+n+'":');a&&$.ajax({url:"/api/admin/users/"+e+"/password",type:"PUT",contentType:"application/json",data:JSON.stringify({new_password:a})}).done(function(){alert("Passwort geaendert!")}).fail(function(e){alert(e.responseJSON?e.responseJSON.error:"Fehler")})},$("#user-select").change(function(){var e=$(this).val();$(".folder-cb").prop("checked",!1),e&&$.get("/api/admin/assign?user_id="+e).done(function(e){e.forEach(function(e){$('.folder-cb[value="'+e+'"]').prop("checked",!0)})})}),$("#save-assignments").click(function(){var e=$("#user-select").val();if(!e)return alert("Bitte Nutzer waehlen");var n=[];$(".folder-cb:checked").each(function(){n.push($(this).val())}),$.ajax({url:"/api/admin/assign",type:"POST",contentType:"application/json",data:JSON.stringify({user_id:e,folders:n})}).done(function(){$("#assign-status").text("Gespeichert!").fadeIn().delay(2e3).fadeOut()}).fail(function(){alert("Fehler beim Speichern")})}),$("#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(e){alert(e.responseJSON?e.responseJSON.error:"Fehler")})})});

View File

@@ -182,8 +182,11 @@
images.forEach(function(file) {
var article = $('<article class="thumb"></article>');
var link = $('<a class="image" href="/images/fulls/' + encodeURIComponent(file.name) + '"></a>');
var img = $('<img src="/images/thumbs/' + encodeURIComponent(file.name) + '" alt="" data-name="' + file.name.replace(/"/g, '&quot;') + '" />');
var prefix = file.folder ? encodeURIComponent(file.folder) + '/' : '';
var fullPath = '/images/fulls/' + prefix + encodeURIComponent(file.name);
var thumbPath = '/images/thumbs/' + prefix + encodeURIComponent(file.name);
var link = $('<a class="image" href="' + fullPath + '"></a>');
var img = $('<img src="' + thumbPath + '" alt="" data-name="' + file.name.replace(/"/g, '&quot;') + '" />');
link.append(img);
article.append(link);
$main.append(article);
@@ -286,6 +289,7 @@
$.get('/api/auth/me').done(function(u) {
$('#login-username, #login-password, #login-submit').hide();
$('#logout-btn').show().css('display', 'inline-block');
$('#pw-change-section').show();
if(u.role === 'admin') {
$('#admin-link').html('<a href="/admin.html" class="button">Admin-Dashboard</a>');
}
@@ -296,7 +300,13 @@
e.preventDefault();
$.post('/api/auth/login', { username: $('#login-username').val(), password: $('#login-password').val() })
.done(function() { window.location.reload(); })
.fail(function(err) { alert("Login fehlgeschlagen. Bitte Zugangsdaten ueberpruefen."); });
.fail(function(xhr) {
var msg = 'Login fehlgeschlagen.';
if (xhr.responseJSON && xhr.responseJSON.error === 'Account abgelaufen') {
msg = 'Dein Account ist abgelaufen. Bitte kontaktiere den Administrator.';
}
alert(msg);
});
});
$('#logout-btn').click(function(e) {
@@ -304,4 +314,14 @@
$.post('/api/auth/logout').done(function() { window.location.reload(); });
});
$('#pw-change-form').submit(function(e) {
e.preventDefault();
var newPw = $('#pw-new').val();
var confirmPw = $('#pw-confirm').val();
if (newPw !== confirmPw) { alert('Passwoerter stimmen nicht ueberein.'); return; }
$.post('/api/auth/change-password', { old_password: $('#pw-old').val(), new_password: newPw })
.done(function() { alert('Passwort geaendert!'); $('#pw-old, #pw-new, #pw-confirm').val(''); })
.fail(function(xhr) { alert(xhr.responseJSON ? xhr.responseJSON.error : 'Fehler'); });
});
})(jQuery);

File diff suppressed because one or more lines are too long