feat: Implement user permission management with presets and dynamic access control
This commit is contained in:
+56
-4
@@ -769,18 +769,24 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="inventoryNavContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="inventoryNavList">
|
||||
{% if current_permissions.pages.get('home', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link {% if current_path == url_for('home') %}nav-active{% endif %}" href="{{ url_for('home') }}">Artikel</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||
{% endif %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
||||
</li>
|
||||
@@ -791,24 +797,46 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||
<li><h6 class="dropdown-header">Verwaltung</h6></li>
|
||||
{% if current_permissions.pages.get('manage_filters', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('manage_filters') }}">Filter verwalten</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('manage_locations', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('manage_locations') }}">Orte verwalten</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('admin_borrowings', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_borrowings') }}">Ausleihen</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('admin_damaged_items', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Defekte Items</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.actions.get('can_view_logs', True) and current_permissions.pages.get('admin_audit_dashboard', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_audit_dashboard') }}">Audit Dashboard</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.actions.get('can_view_logs', True) and current_permissions.pages.get('logs', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('logs') }}">Logs</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% if current_permissions.actions.get('can_manage_users', True) %}
|
||||
<li><h6 class="dropdown-header">System</h6></li>
|
||||
{% if current_permissions.pages.get('user_del', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('user_del') }}">Benutzer verwalten</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('register', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('register') }}">Neuer Benutzer</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('impressum') }}">Impressum</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('license') }}">Lizenz</a></li>
|
||||
</ul>
|
||||
@@ -837,7 +865,9 @@
|
||||
<span class="user-notification-dot {% if unread_notification_count and unread_notification_count > 0 %}visible{% endif %}" aria-hidden="true"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invUserMenuDropdown">
|
||||
{% if current_permissions.pages.get('notifications_view', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('notifications_view') }}">Benachrichtigungen</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('change_password') }}">Passwort ändern</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
@@ -860,18 +890,24 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="libraryNavContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="libraryNavList">
|
||||
{% if current_permissions.pages.get('library_view', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link {% if current_path == url_for('library_view') %}nav-active{% endif %}" href="{{ url_for('library_view') }}">Medien</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||
{% endif %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) and current_permissions.pages.get('library_admin', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
||||
</li>
|
||||
@@ -882,23 +918,37 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libMoreDropdown">
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('tutorial_page') }}">Tutorial</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||
{% if 'username' in session and (session.get('admin', False) or is_admin) and current_permissions.actions.get('can_manage_settings', True) %}
|
||||
<li><h6 class="dropdown-header">Bibliotheks-Verwaltung</h6></li>
|
||||
{% if current_permissions.pages.get('library_loans_admin', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('library_loans_admin') }}">Ausleihen</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('admin_damaged_items', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin_damaged_items') }}">Defekte Items</a></li>
|
||||
{% endif %}
|
||||
{% if student_cards_module_enabled %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('student_cards_admin') }}">Bibliotheksausweis</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% if current_permissions.actions.get('can_manage_users', True) %}
|
||||
<li><h6 class="dropdown-header">System</h6></li>
|
||||
{% if current_permissions.pages.get('user_del', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('user_del') }}">Benutzer verwalten</a></li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('register', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('register') }}">Neuer Benutzer</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('impressum') }}">Impressum</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('license') }}">Lizenz</a></li>
|
||||
</ul>
|
||||
@@ -927,7 +977,9 @@
|
||||
<span class="user-notification-dot {% if unread_notification_count and unread_notification_count > 0 %}visible{% endif %}" aria-hidden="true"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libUserMenuDropdown">
|
||||
{% if current_permissions.pages.get('notifications_view', True) %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('notifications_view') }}">Benachrichtigungen</a></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('change_password') }}">Passwort ändern</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
<th>Vorname</th>
|
||||
<th>Nachname</th>
|
||||
<th>Administrator</th>
|
||||
<th>Rechte-Preset</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -72,6 +73,7 @@
|
||||
<td>{{ user.name if user.name else user.username }}</td>
|
||||
<td>{{ user.last_name if user.last_name else '' }}</td>
|
||||
<td>{{ "Ja" if user.admin else "Nein" }}</td>
|
||||
<td>{{ permission_presets.get(user.permission_preset, {}).get('label', user.permission_preset) }}</td>
|
||||
<td class="actions">
|
||||
<form method="POST" action="{{ url_for('delete_user') }}" class="d-inline">
|
||||
<input type="hidden" name="username" value="{{ user.username }}">
|
||||
@@ -90,6 +92,14 @@
|
||||
onclick="openResetPasswordModal('{{ user.username }}')">
|
||||
Passwort zurücksetzen
|
||||
</button>
|
||||
<button type="button" class="btn btn-info btn-sm"
|
||||
data-username="{{ user.username }}"
|
||||
data-preset="{{ user.permission_preset }}"
|
||||
data-action-permissions='{{ user.action_permissions | tojson }}'
|
||||
data-page-permissions='{{ user.page_permissions | tojson }}'
|
||||
onclick="openPermissionsModal(this)">
|
||||
Berechtigungen
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -99,6 +109,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permission Modal -->
|
||||
<div class="modal fade" id="permissionsModal" tabindex="-1" aria-labelledby="permissionsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="permissionsModalLabel">Berechtigungen bearbeiten</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('admin_update_user_permissions') }}">
|
||||
<div class="modal-body permissions-modal-body">
|
||||
<input type="hidden" id="perm-username" name="username">
|
||||
<div class="mb-3">
|
||||
<label for="perm-username-display" class="form-label">Benutzer</label>
|
||||
<input type="text" class="form-control" id="perm-username-display" disabled>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="permission-preset" class="form-label">Preset</label>
|
||||
<select class="form-select" id="permission-preset" name="permission_preset">
|
||||
{% for preset_key, preset_value in permission_presets.items() %}
|
||||
<option value="{{ preset_key }}">{{ preset_value.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="permissions-grid">
|
||||
<div class="permissions-group">
|
||||
<h6>Aktionsrechte</h6>
|
||||
{% for action_key, action_label in permission_action_options %}
|
||||
<div class="form-check">
|
||||
<input class="form-check-input permission-action-checkbox" type="checkbox" id="action-{{ action_key }}" name="action_{{ action_key }}">
|
||||
<label class="form-check-label" for="action-{{ action_key }}">{{ action_label }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="permissions-group">
|
||||
<h6>Seitenrechte</h6>
|
||||
{% for endpoint_name, endpoint_label in permission_page_options %}
|
||||
<div class="form-check">
|
||||
<input class="form-check-input permission-page-checkbox" type="checkbox" id="page-{{ endpoint_name }}" name="page_{{ endpoint_name }}">
|
||||
<label class="form-check-label" for="page-{{ endpoint_name }}">{{ endpoint_label }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">Berechtigungen speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Modal -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="editUserModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
@@ -165,6 +231,24 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var permissionPresets = {{ permission_presets | tojson }};
|
||||
|
||||
function applyPresetToPermissionForm(presetKey) {
|
||||
var preset = permissionPresets[presetKey] || {};
|
||||
var actionDefaults = preset.actions || {};
|
||||
var pageDefaults = preset.pages || {};
|
||||
|
||||
document.querySelectorAll('.permission-action-checkbox').forEach(function (checkbox) {
|
||||
var key = checkbox.name.replace('action_', '');
|
||||
checkbox.checked = !!actionDefaults[key];
|
||||
});
|
||||
|
||||
document.querySelectorAll('.permission-page-checkbox').forEach(function (checkbox) {
|
||||
var key = checkbox.name.replace('page_', '');
|
||||
checkbox.checked = !!pageDefaults[key];
|
||||
});
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
var search = document.getElementById('filter-search').value.toLowerCase();
|
||||
var adminFilter = document.getElementById('filter-admin').value.toLowerCase();
|
||||
@@ -229,6 +313,13 @@
|
||||
document.getElementById('filter-admin').addEventListener('change', applyFilters);
|
||||
document.getElementById('filter-column').addEventListener('change', applyFilters);
|
||||
document.getElementById('filter-direction').addEventListener('change', applyFilters);
|
||||
|
||||
var presetSelect = document.getElementById('permission-preset');
|
||||
if (presetSelect) {
|
||||
presetSelect.addEventListener('change', function () {
|
||||
applyPresetToPermissionForm(this.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function openEditUserModal(button) {
|
||||
@@ -253,6 +344,42 @@
|
||||
var modal = new bootstrap.Modal(document.getElementById('resetPasswordModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function openPermissionsModal(button) {
|
||||
var username = button.getAttribute('data-username') || '';
|
||||
var preset = button.getAttribute('data-preset') || 'standard_user';
|
||||
var actionPermissions = {};
|
||||
var pagePermissions = {};
|
||||
|
||||
try {
|
||||
actionPermissions = JSON.parse(button.getAttribute('data-action-permissions') || '{}');
|
||||
} catch (err) {
|
||||
actionPermissions = {};
|
||||
}
|
||||
|
||||
try {
|
||||
pagePermissions = JSON.parse(button.getAttribute('data-page-permissions') || '{}');
|
||||
} catch (err) {
|
||||
pagePermissions = {};
|
||||
}
|
||||
|
||||
document.getElementById('perm-username').value = username;
|
||||
document.getElementById('perm-username-display').value = username;
|
||||
document.getElementById('permission-preset').value = preset;
|
||||
|
||||
document.querySelectorAll('.permission-action-checkbox').forEach(function (checkbox) {
|
||||
var key = checkbox.name.replace('action_', '');
|
||||
checkbox.checked = !!actionPermissions[key];
|
||||
});
|
||||
|
||||
document.querySelectorAll('.permission-page-checkbox').forEach(function (checkbox) {
|
||||
var key = checkbox.name.replace('page_', '');
|
||||
checkbox.checked = !!pagePermissions[key];
|
||||
});
|
||||
|
||||
var modal = new bootstrap.Modal(document.getElementById('permissionsModal'));
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -286,5 +413,28 @@
|
||||
.password-requirements p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.permissions-modal-body {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.permissions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.permissions-group {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.permissions-group h6 {
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user