454 lines
14 KiB
HTML
Executable File
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 %} |