Files
quality_recticel/py_app/app/templates/role_permissions.html
2025-10-05 14:32:47 -04:00

454 lines
14 KiB
HTML
Executable File

{% extends "base.html" %}
{% block title %}Role Permissions Management{% endblock %}
{% block head %}
<style>
.permissions-container {
max-width: 1600px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.permissions-table-container {
background: white;
border-radius: 15px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
overflow: hidden;
margin: 0 auto 30px auto;
border: 2px solid #dee2e6;
max-width: 100%;
}
.permissions-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
margin: 0;
}
.permissions-table thead {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
}
.permissions-table th {
padding: 15px 12px;
text-align: left;
font-weight: 600;
border-bottom: 2px solid rgba(255,255,255,0.2);
}
.permissions-table th:nth-child(1) { width: 15%; }
.permissions-table th:nth-child(2) { width: 20%; }
.permissions-table th:nth-child(3) { width: 25%; }
.permissions-table th:nth-child(4) { width: 40%; }
.permission-row {
border-bottom: 2px solid #dee2e6 !important;
transition: all 0.3s ease;
}
.permission-row:hover {
background: linear-gradient(135deg, #e3f2fd, #f0f8ff) !important;
transform: translateY(-1px) !important;
box-shadow: 0 4px 12px rgba(0,123,255,0.15) !important;
}
.role-cell, .module-cell, .page-cell, .functions-cell {
padding: 15px 12px !important;
vertical-align: top !important;
border-right: 1px solid #f1f3f4 !important;
}
.role-cell {
border-left: 4px solid #007bff !important;
}
.module-cell {
border-left: 2px solid #28a745 !important;
}
.page-cell {
border-left: 2px solid #ffc107 !important;
}
.functions-cell {
border-left: 2px solid #dc3545 !important;
}
.role-badge {
display: flex;
align-items: center;
gap: 8px;
background: #e3f2fd;
padding: 8px 12px;
border-radius: 20px;
}
.functions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.function-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
.function-toggle {
display: flex;
align-items: center;
cursor: pointer;
}
.toggle-slider {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
background: #ccc;
border-radius: 20px;
transition: all 0.3s ease;
}
.toggle-slider::before {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: white;
border-radius: 50%;
transition: all 0.3s ease;
}
input[type="checkbox"]:checked + .toggle-slider {
background: #007bff;
}
input[type="checkbox"]:checked + .toggle-slider::before {
transform: translateX(20px);
}
input[type="checkbox"] {
display: none;
}
.function-text {
font-size: 12px;
font-weight: 500;
}
.role-separator, .module-separator {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.separator-line {
padding: 12px 20px;
font-weight: 600;
color: #495057;
background: linear-gradient(135deg, #e9ecef, #f8f9fa);
}
.module-badge {
padding: 8px 15px;
background: linear-gradient(135deg, #28a745, #20c997);
color: white;
border-radius: 15px;
font-weight: 500;
}
.action-buttons-container {
text-align: center;
margin: 30px 0;
}
.action-buttons {
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,123,255,0.3);
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #545b62;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(108,117,125,0.3);
}
</style>
{% endblock %}
{% block content %}
<div class="permissions-container">
<div style="text-align: center; margin-bottom: 40px;">
<h1 style="color: #2c3e50; margin-bottom: 15px; font-weight: 700; font-size: 32px;">
🔐 Role Permissions Management
</h1>
<p style="color: #6c757d; font-size: 16px;">
Configure granular access permissions for each role in the system
</p>
</div>
<!-- 4-Column Permissions Table -->
<div class="permissions-table-container">
<table class="permissions-table" id="permissionsTable">
<thead>
<tr>
<th>👤 Role Name</th>
<th>🏢 Module Name</th>
<th>📄 Page Name</th>
<th>⚙️ Functions & Permissions</th>
</tr>
</thead>
<tbody>
{% set current_role = '' %}
{% set current_module = '' %}
{% for role_name, role_data in roles.items() %}
{% for page_key, page_data in pages.items() %}
{% for section_key, section_data in page_data.sections.items() %}
<!-- Role separator row -->
{% if current_role != role_name %}
{% set current_role = role_name %}
<tr class="role-separator">
<td colspan="4">
<div class="separator-line">
<span>{{ role_data.display_name }} (Level {{ role_data.level }})</span>
</div>
</td>
</tr>
{% endif %}
<!-- Module separator -->
{% if current_module != page_key %}
{% set current_module = page_key %}
<tr class="module-separator">
<td></td>
<td colspan="3">
<div style="padding: 8px 15px;">
<span class="module-badge">{{ page_data.name }}</span>
</div>
</td>
</tr>
{% endif %}
<tr class="permission-row" data-role="{{ role_name }}" data-module="{{ page_key }}">
<td class="role-cell">
<div class="role-badge">
<span>👤</span>
<span>{{ role_data.display_name }}</span>
</div>
</td>
<td class="module-cell">
<span>{{ page_data.name }}</span>
</td>
<td class="page-cell">
<div style="display: flex; align-items: center; gap: 8px;">
<span>📋</span>
<span>{{ section_data.name }}</span>
</div>
</td>
<td class="functions-cell">
<div class="functions-grid">
{% for action in section_data.actions %}
{% set permission_key = page_key + '.' + section_key + '.' + action %}
<div class="function-item" data-permission="{{ permission_key }}" data-role="{{ role_name }}">
<label class="function-toggle">
<input type="checkbox"
data-role="{{ role_name }}"
data-page="{{ page_key }}"
data-section="{{ section_key }}"
data-action="{{ action }}"
onchange="togglePermission('{{ role_name }}', '{{ page_key }}', '{{ section_key }}', '{{ action }}', this)">
<span class="toggle-slider"></span>
</label>
<span class="function-text">{{ action_names[action] }}</span>
</div>
{% endfor %}
</div>
</td>
</tr>
{% endfor %}
{% set current_module = '' %}
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
<!-- Action Buttons -->
<div class="action-buttons-container">
<div class="action-buttons">
<button class="btn btn-secondary" onclick="resetAllToDefaults()">
<span>🔄</span>
Reset All to Defaults
</button>
<button class="btn btn-primary" onclick="saveAllPermissions()">
<span>💾</span>
Save All Changes
</button>
</div>
</div>
</div>
<script>
// Initialize data from backend
let permissions = {{ permissions_json|safe }};
let rolePermissions = {{ role_permissions_json|safe }};
// Toggle permission function
function togglePermission(roleName, pageKey, sectionKey, action, checkbox) {
const isChecked = checkbox.checked;
const permissionKey = `${pageKey}.${sectionKey}.${action}`;
// Update visual state of the function item
const functionItem = checkbox.closest('.function-item');
if (isChecked) {
functionItem.classList.remove('disabled');
} else {
functionItem.classList.add('disabled');
}
// Update data structure (flat array format)
if (!rolePermissions[roleName]) {
rolePermissions[roleName] = [];
}
if (isChecked && !rolePermissions[roleName].includes(permissionKey)) {
rolePermissions[roleName].push(permissionKey);
} else if (!isChecked) {
const index = rolePermissions[roleName].indexOf(permissionKey);
if (index > -1) {
rolePermissions[roleName].splice(index, 1);
}
}
}
// Save all permissions
function saveAllPermissions() {
// Convert flat permission arrays to nested structure for backend
const structuredPermissions = {};
for (const [roleName, permissions] of Object.entries(rolePermissions)) {
structuredPermissions[roleName] = {};
permissions.forEach(permissionKey => {
const [pageKey, sectionKey, action] = permissionKey.split('.');
if (!structuredPermissions[roleName][pageKey]) {
structuredPermissions[roleName][pageKey] = {};
}
if (!structuredPermissions[roleName][pageKey][sectionKey]) {
structuredPermissions[roleName][pageKey][sectionKey] = [];
}
structuredPermissions[roleName][pageKey][sectionKey].push(action);
});
}
fetch('/settings/save_all_role_permissions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
permissions: structuredPermissions
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('All permissions saved successfully!');
} else {
alert('Error saving permissions: ' + data.error);
}
})
.catch(error => {
alert('Error saving permissions: ' + error);
});
}
// Reset all permissions to defaults
function resetAllToDefaults() {
if (confirm('Are you sure you want to reset ALL role permissions to defaults? This will overwrite all current settings.')) {
fetch('/settings/reset_all_role_permissions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error resetting permissions: ' + data.error);
}
})
.catch(error => {
alert('Error resetting permissions: ' + error);
});
}
}
// Initialize checkbox states when page loads
document.addEventListener('DOMContentLoaded', function() {
// Set initial states based on data
document.querySelectorAll('.function-item').forEach(item => {
const roleName = item.dataset.role;
const permissionKey = item.dataset.permission;
const checkbox = item.querySelector('input[type="checkbox"]');
// Check if this role has this permission
const hasPermission = rolePermissions[roleName] && rolePermissions[roleName].includes(permissionKey);
if (hasPermission) {
checkbox.checked = true;
item.classList.remove('disabled');
} else {
checkbox.checked = false;
item.classList.add('disabled');
}
});
});
</script>
{% endblock %}