const ctx = new ContextMenu(); ////////////////////////////// // 🌐 API LAYER ////////////////////////////// const api = async (url, method = 'GET', body) => { const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined }); if(res.status >= 400) { const text = await res.text(); sendUserEvent('RBAC', `Hoppla, da ist etwas schief gelaufen:\r\n${text}`, null, 4); } return res.json(); }; ////////////////////////////// // 🧠 RBAC SERVICE LAYER ////////////////////////////// const RBAC = { // 👤 USERS loadUsers: async () => (await api('/api/rbac/auth/get', 'POST')) .map(({ active, online, ...rest }) => ({ ...rest, Aktiv: active })) .sort((a, b) => a.sAMAccountName.localeCompare(b.sAMAccountName)), createUser: (data) => api('/api/rbac/auth/create', 'POST', data), syncUsersFromAD: () => api('/api/rbac/auth/syncFromAD', 'POST', { }), deleteUser: (guid) => api(`/api/rbac/auth/${guid}`, 'DELETE'), // 👥 GROUPS loadGroups: () => api('/api/rbac/group/get', 'POST'), createGroup: (name) => api('/api/rbac/group/create', 'POST', { name }), syncGroupsFromAD: () => api('/api/rbac/group/syncFromAD', 'POST', { }), deleteGroup: (guid) => api(`/api/rbac/group/${guid}`, 'DELETE'), // 🎭 ROLES loadRoles: () => api('/api/rbac/role/get', 'POST'), createRole: (name) => api('/api/rbac/role/create', 'POST', { name }), deleteRole: (id) => api(`/api/rbac/role/${id}`, 'DELETE'), // 🎭 PERMISSIONS loadPermissions: () => api('/api/rbac/permission/get', 'POST'), createPermission: (data) => api('/api/rbac/permission/create', 'POST', data), deletePermission: (id) => api(`/api/rbac/permission/${id}`, 'DELETE'), // 🔗 ASSIGNMENTS addUserToGroup: (authGuid, groupGuid) => api('/api/rbac/group/add-user', 'POST', { authGuid, groupGuid }), addUserToRole: (authGuid, roleId) => api('/api/rbac/role/add-user', 'POST', { authGuid, roleId }), addGroupToRole: (groupGuid, roleId) => api('/api/rbac/role/add-group', 'POST', { groupGuid, roleId }), addPermissionToRole: (roleId, permissionId) => api('/api/rbac/role/add-permission', 'POST', { roleId, permissionId }) }; ////////////////////////////// // 🖱️ DRAG & DROP ////////////////////////////// function createDragZone(el, data, type) { el.draggable = true; el.addEventListener('dragstart', (evt) => { evt.dataTransfer.setData('application/json', JSON.stringify( { ...data, type })); }); } function createDropZone(el, type, target) { const targetValue = target.ObjectGUID || target.ID || target.Role_ID || target.Permission_ID; let process = { action: null, response: null, failure: false }; el.addEventListener('dragover', e => e.preventDefault()); el.addEventListener('drop', async (e) => { e.preventDefault(); const data = JSON.parse( e.dataTransfer.getData('application/json') ); if(!targetValue || !data.type) { sendUserEvent('RBAC', 'Deinem Drop-Ziel wurde kein ID-Attribut zugewiesen. Frag einfach Manuel ', null, 2); return; } // NOCH NICHT UNTER ADMINROUTES EDITIERT: WENN BENUTZER/GRUPPE BEREITS IN ROLLE ENTHALTEN IST switch (type) { case 'group': if(data.type === 'user') { process.action = `Du hast den Benutzer der Gruppe hinzugefügt`; process.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue); } break; case 'role': if(data.type === 'user') { process.action = `Du hast den Benutzer der Rolle hinzugefügt`; process.response = await RBAC.addUserToRole(data.ObjectGUID, targetValue); break; } if(data.type === 'group') { process.action = `Du hast die Gruppe der Rolle hinzugefügt`; process.response = await RBAC.addGroupToRole(data.ObjectGUID, targetValue); break; } case 'permission': process.action = `Du hast die Berechtigung der Rolle hinzugefügt`; process.response = await RBAC.addPermissionToRole(targetValue, data.ID); break; } if(process.failure || !process.action) return; loadUsers(); loadGroups(); loadRoles(); sendUserEvent('RBAC', process.action, null, 0); }); } ////////////////////////////// // 📋 TABLE (USERS) ////////////////////////////// const rbacUsersVT = virtualTable({ tableEl: document.querySelector('#rbacUsersTable'), data: [], rowHeight: 20, buffer: 5, groupKey: 'ObjectSourceName', rowKey: 'ObjectGUID', filterConfig: { hideCounter: true, exceptedColumns: ['', 'Rollen', 'Gruppen'], columnModes: { Aktiv: 'dropdown', Online: 'dropdown' } }, customRender: (row, tr) => { createDragZone(tr, row, 'user'); tr.addEventListener('contextmenu', (evt) => { evt.preventDefault(); ctx.setItems([ { label: "Details", onClick: () => showAuthDetails(row.ObjectGUID) } ]); ctx.show(evt.pageX + 5, { y: evt.pageY + 5 }); }); createTd(tr, ``, { styles: { 'position': 'sticky', 'left': '0px', 'max-width': '20px', 'z-index': '2' }, classes: [ 'text-align:left' ], onclick: () => { if(row['ObjectGUID'] === '00000000-0000-0000-0000-000000000001') return; deleteUser(row['ObjectGUID'], row['sAMAccountName']); } }); createTd(tr, row['ObjectGUID'], { classes: [ 'text-align:left' ], styles: { 'max-width': '100px' }, attributes: { 'data-tooltip': row['ObjectGUID'] } }); createTd(tr, row['sAMAccountName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sAMAccountName'] } }); createTd(tr, row['RoleCount'], { classes: [ 'text-align:center' ] }); createTd(tr, row['GroupCount'], { classes: [ 'text-align:center' ] }); createTd(tr, row['sn'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sn'] } }); createTd(tr, row['givenName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['givenName'] } }); createTd(tr, row['mail'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['mail'] } }); createTd(tr, row['Aktiv'], { classes: [ 'text-align:center' ] }); } }); ////////////////////////////// // 📋 TABLE (PERMISSIONS) ////////////////////////////// const rbacPermissionsVT = virtualTable({ tableEl: document.querySelector('#rbacPermissionsTable'), data: [], rowHeight: 20, buffer: 5, groupKey: null, rowKey: 'ID', filterConfig: { hideCounter: true, exceptedColumns: ['', 'ID'] }, customRender: (row, tr) => { createDragZone(tr, row, 'user'); tr.addEventListener('contextmenu', (evt) => { evt.preventDefault(); ctx.setItems([ { label: "Details", onClick: () => showAuthDetails(row.ObjectGUID) } ]); ctx.show(evt.pageX + 5, { y: evt.pageY + 5 }); }); createTd(tr, ``, { styles: { 'position': 'sticky', 'left': '0px', 'max-width': '20px', 'z-index': '2' }, classes: [ 'text-align:left' ], onclick: () => { if(row['ID'] === 1) return; deletePermission(row['ID'], `${row['Scope']}.${row['Resource']}.${row['Action']}`); } }); createTd(tr, row['Permission_ID'], { classes: [ 'text-align:left' ], styles: { 'width': '100px' } } ); createTd(tr, row['GroupUserCount'], { classes: [ 'text-align:center' ] }); createTd(tr, row['TotalUserCount'], { classes: [ 'text-align:center' ] }); createTd(tr, row['RoleCount'], { classes: [ 'text-align:center' ], styles: { 'width': '100px' } }); createTd(tr, row['Scope'], { classes: [ 'text-align:right' ], styles: { 'width': '100px' } }); createTd(tr, row['Resource'], { classes: [ 'text-align:center' ], styles: { 'width': '100px' } }); createTd(tr, row['Action'], { classes: [ 'text-align:left' ], styles: { 'width': '100px' } }); } }); ////////////////////////////// // 📥 LOADERS ////////////////////////////// async function loadUsers() { try { rbacUsersVT.source(await RBAC.loadUsers()); } catch (err) { console.error(err); } } async function loadPermissions() { try { rbacPermissionsVT.source(await RBAC.loadPermissions()); } catch (err) { console.error(err); } } async function loadGroups() { const container = document.getElementById('rbacGroupContainer'); container.innerHTML = ''; const groups = await RBAC.loadGroups(); const fragment = document.createDocumentFragment(); groups.forEach(group => { const section = document.createElement('section'); const groupName = document.createElement('span'); groupName.innerHTML = `[${group.UserCount}] ${group.Name}`; groupName.dataset.tooltip = group.Name; groupName.dataset.tooltipMode = 'ellipsis'; section.append(groupName); const removeButton = document.createElement('button'); if(group.ObjectGUID === '00000000-0000-0000-0000-000000000001') { removeButton.disabled = true; removeButton.dataset.tooltip = `Die Gruppe ${group.Name} kann nicht gelöscht werden`; } removeButton.className = 'removeButton'; removeButton.innerHTML = 'X'; removeButton.onclick = async () => { await deleteGroup(group.ObjectGUID, group.Name); }; section.append(removeButton); createDragZone(section, group, 'group'); createDropZone(section, 'group', group); fragment.appendChild(section); }); container.appendChild(fragment); } async function createGroup() { const name = document.getElementById('newGroupName').value; const group = await api('/api/rbac/group/create', 'POST', { name }); if(group) { sendUserEvent('RBAC', `Gruppe ${name} angelegt`, null, 0); loadGroups(); } } async function loadRoles() { const container = document.getElementById('rbacRoleContainer'); container.innerHTML = ''; const roles = await RBAC.loadRoles(); const fragment = document.createDocumentFragment(); roles.forEach(role => { const section = document.createElement('section'); const roleName = document.createElement('span'); roleName.innerHTML = `[${role.UserCount}][${role.GroupCount}] ${role.Name}`; roleName.dataset.tooltip = role.Name; section.append(roleName); const removeButton = document.createElement('button'); removeButton.className = 'removeButton'; removeButton.innerHTML = 'X'; removeButton.onclick = async () => { await deleteRole(role.ID, role.Name); }; section.append(removeButton); createDropZone(section, 'role', role); fragment.appendChild(section); }); container.appendChild(fragment); } ////////////////////////////// // 👤 USER ACTIONS ////////////////////////////// async function createUser() { const name = document.getElementById('newUserName').value; if (!name || !name.includes('.')) return; const [givenName, sn] = name.split('.'); const user = await RBAC.createUser({ sAMAccountName: name, mail: `${name}@test.com`, givenName: givenName[0].toUpperCase() + givenName.slice(1), sn: sn[0].toUpperCase() + sn.slice(1) }); sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName} [${user.ObjectGUID}] angelegt`, null, 0); loadUsers(); } async function syncUsersFromAD() { const users = await RBAC.syncUsersFromAD(); sendUserEvent('RBAC', `${users.length} Benutzer aus dem AD synchronisiert`, null, 0); loadUsers(); } async function deleteUser(guid, name) { feedbox({ title: `Benutzer löschen`, message: `Soll der Benutzer ${name} und
alle seine Zugehörigkeiten entfernt werden`, buttons: { no: { text: 'Nein' }, yes: { text: 'Ja', onClick: async () => { const user = await RBAC.deleteUser(guid); sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName || ''} [${user.ObjectGUID}] gelöscht`, null, 0); loadUsers(); loadGroups(); loadRoles(); loadPermissions(); } } } }); } ////////////////////////////// // 👥 GROUP ACTIONS ////////////////////////////// async function deleteGroup(guid, name) { feedbox({ title: `Gruppe löschen`, message: `Möchtest du die Gruppe ${name} [${guid}] wirklich löschen`, buttons: { no: { text: 'Nein' }, yes: { text: 'Ja', onClick: async () => { const group = await RBAC.deleteGroup(guid); sendUserEvent('RBAC', `Du hast die Gruppe ${name || ''} [${group.ObjectGUID}] gelöscht`, null, 0); loadUsers(); loadGroups(); loadRoles(); loadPermissions(); } } } }); } async function syncGroupsFromAD() { const group = await RBAC.syncGroupsFromAD(); sendUserEvent('RBAC', `${group.length} Gruppen aus dem AD synchronisiert`, null, 0); loadGroups(); loadUsers(); } ////////////////////////////// // 🎭 ROLE ACTIONS ////////////////////////////// async function createRole() { const name = document.getElementById('newRoleName').value; if (!name) return; const role = await RBAC.createRole(name); sendUserEvent('RBAC', `Rolle ${name} angelegt`, null, 0); loadRoles(); } async function deleteRole(id, name) { feedbox({ title: `Rolle löschen`, message: `Möchtest du die Rolle ${name} wirklich löschen`, buttons: { no: { text: 'Nein' }, yes: { text: 'Ja', onClick: async () => { await RBAC.deleteRole(id); sendUserEvent('RBAC', `Rolle ${name} gelöscht`, null, 0); loadUsers(); loadGroups(); loadRoles(); loadPermissions(); // optional, falls RoleCount betroffen } } } }); } ////////////////////////////// // 🎭 PERMISSION ACTIONS ////////////////////////////// async function createPermission() { const scope = document.getElementById('permScope').value; const resource = document.getElementById('permResource').value; const action = document.getElementById('permAction').value; if (!scope || !resource || !action) return; const permission = await RBAC.createPermission( { scope, resource, action } ); sendUserEvent('RBAC', `Berechtigung ${scope}.${resource}.${action} angelegt`, null, 0); loadPermissions(); } async function deletePermission(id, name) { feedbox({ title: `Berechtigung löschen`, message: `Möchtest du die Berechtigung ${name} wirklich löschen`, buttons: { no: { text: 'Nein' }, yes: { text: 'Ja', onClick: async () => { await RBAC.deletePermission(id); sendUserEvent('RBAC', `Berechtigung ${name} gelöscht`, null, 0); loadUsers(); loadGroups(); loadRoles(); loadPermissions(); } } } }); } ////////////////////////////// // 🚀 INIT ////////////////////////////// loadUsers(); loadGroups(); loadRoles(); loadPermissions();