@mamoorali295/rbac
Version:
Complete RBAC (Role-Based Access Control) system for Node.js with Express middleware, NestJS integration, GraphQL support, MongoDB & PostgreSQL support, modern admin dashboard, TypeScript support, and dynamic permission management
327 lines (305 loc) • 13.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRoleDetailsView = exports.getRolesListView = void 0;
const layout_1 = require("./layout");
const getRolesListView = (roles, features, permissions) => {
const rolesRows = roles.map(role => {
var _a;
return `
<tr>
<td>
<strong>${role.name}</strong><br>
<small style="color: #666;">${role.description}</small>
</td>
<td>${((_a = role.features) === null || _a === void 0 ? void 0 : _a.length) || 0} features</td>
<td>
<a href="/rbac-admin/roles/${role._id}" class="btn">View Details</a>
<button class="btn btn-danger" onclick="confirmDeleteRole('${role._id}', '${role.name}')">Delete</button>
</td>
</tr>
`;
}).join('');
const featureCheckboxes = features.map(feature => `
<div style="margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
<div>
<input type="checkbox" name="features" value="${feature._id}" id="feature-${feature._id}" onchange="togglePermissions('${feature._id}')">
<label for="feature-${feature._id}" style="margin-left: 8px; font-weight: bold;">${feature.name}</label>
<p style="margin: 5px 0; color: #666; font-size: 14px;">${feature.description}</p>
</div>
<div id="permissions-${feature._id}" style="display: none; margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 4px;">
<strong>Select Permissions:</strong>
<div style="margin-top: 8px;">
${permissions.map(permission => `
<label style="display: inline-block; margin-right: 15px; font-weight: normal;">
<input type="checkbox" name="feature-${feature._id}-permissions" value="${permission._id}">
${permission.name}
</label>
`).join('')}
</div>
</div>
</div>
`).join('');
const content = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<h2>Role Management</h2>
<button class="btn btn-success" onclick="openModal('createRoleModal')">Create New Role</button>
</div>
<div class="card">
<div class="card-header">
<h4>All Roles (${roles.length})</h4>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Role</th>
<th>Features</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${rolesRows}
</tbody>
</table>
${roles.length === 0 ? '<p style="text-align: center; color: #666;">No roles created yet.</p>' : ''}
</div>
</div>
<!-- Create Role Modal -->
<div id="createRoleModal" class="modal">
<div class="modal-content" style="max-width: 800px; max-height: 80vh; overflow-y: auto;">
<span class="close" onclick="closeModal('createRoleModal')">×</span>
<h3>Create New Role</h3>
<form method="POST" action="/rbac-admin/roles/create">
<div class="form-group">
<label>Role Name:</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="form-group">
<label>Description:</label>
<textarea name="description" class="form-control" rows="3" required></textarea>
</div>
<div class="form-group">
<label>Features & Permissions:</label>
<div style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; border-radius: 4px;">
${featureCheckboxes}
</div>
</div>
<button type="submit" class="btn btn-success">Create Role</button>
<button type="button" class="btn" onclick="closeModal('createRoleModal')">Cancel</button>
</form>
</div>
</div>
<script>
function togglePermissions(featureId) {
const checkbox = document.getElementById('feature-' + featureId);
const permissionsDiv = document.getElementById('permissions-' + featureId);
if (checkbox.checked) {
permissionsDiv.style.display = 'block';
} else {
permissionsDiv.style.display = 'none';
// Uncheck all permissions for this feature
const permissionCheckboxes = permissionsDiv.querySelectorAll('input[type="checkbox"]');
permissionCheckboxes.forEach(cb => cb.checked = false);
}
}
function confirmDeleteRole(roleId, roleName) {
if (confirm('Are you sure you want to delete role: ' + roleName + '?\\n\\nThis action cannot be undone and may affect users assigned to this role.')) {
fetch('/rbac-admin/roles/' + roleId + '/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
} else {
window.location.reload();
}
})
.catch(err => alert('Error deleting role: ' + err.message));
}
}
// Handle form submission to format feature permissions correctly
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('#createRoleModal form');
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(form);
const selectedFeatures = [];
// Get all selected features
const featureCheckboxes = document.querySelectorAll('input[name="features"]:checked');
featureCheckboxes.forEach(featureCheckbox => {
const featureId = featureCheckbox.value;
const permissionCheckboxes = document.querySelectorAll('input[name="feature-' + featureId + '-permissions"]:checked');
const permissions = Array.from(permissionCheckboxes).map(cb => cb.value);
if (permissions.length > 0) {
selectedFeatures.push({
feature: featureId,
permissions: permissions
});
}
});
// Create JSON payload with formatted features
const payload = {
name: formData.get('name'),
description: formData.get('description'),
features: selectedFeatures
};
fetch('/rbac-admin/roles/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
} else {
window.location.reload();
}
})
.catch(err => alert('Error creating role: ' + err.message));
});
}
});
</script>
`;
return (0, layout_1.getBaseLayout)('Roles', content);
};
exports.getRolesListView = getRolesListView;
const getRoleDetailsView = (role, allFeatures, allPermissions) => {
const roleFeatures = role.features || [];
const assignedFeaturesHtml = roleFeatures.map((rf) => `
<div class="card" style="margin-bottom: 1rem;">
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
<h5>${rf.feature.name}</h5>
<button class="btn btn-danger" onclick="removeFeatureFromRole('${role._id}', '${rf.feature._id}')">Remove</button>
</div>
<div class="card-body">
<p>${rf.feature.description}</p>
<strong>Permissions:</strong>
${rf.permissions.map((p) => `
<span class="badge" style="margin: 2px;">
${p.name}
<button onclick="removePermissionFromFeature('${role._id}', '${rf.feature._id}', '${p._id}')" style="background: none; border: none; color: white; margin-left: 5px; cursor: pointer;">×</button>
</span>
`).join('')}
<div style="margin-top: 10px;">
<button class="btn" onclick="openModal('addPermissionModal-${rf.feature._id}')">Add Permissions</button>
</div>
</div>
</div>
`).join('');
const unassignedFeatures = allFeatures.filter(f => !roleFeatures.some((rf) => rf.feature._id.toString() === f._id.toString()));
const unassignedFeaturesOptions = unassignedFeatures.map(f => `<option value="${f._id}">${f.name}</option>`).join('');
const permissionModals = roleFeatures.map((rf) => {
const availablePermissions = allPermissions.filter(p => !rf.permissions.some((rp) => rp._id.toString() === p._id.toString()));
return `
<div id="addPermissionModal-${rf.feature._id}" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('addPermissionModal-${rf.feature._id}')">×</span>
<h3>Add Permissions to ${rf.feature.name}</h3>
<form method="POST" action="/rbac-admin/roles/${role._id}/add-permissions">
<input type="hidden" name="featureIds" value="${rf.feature._id}">
<div class="form-group">
<label>Select Permissions to Add:</label>
${availablePermissions.map(p => `
<label style="display: block; margin: 5px 0;">
<input type="checkbox" name="permissionIds" value="${p._id}">
${p.name} - ${p.description}
</label>
`).join('')}
</div>
<button type="submit" class="btn btn-success">Add Selected Permissions</button>
<button type="button" class="btn" onclick="closeModal('addPermissionModal-${rf.feature._id}')">Cancel</button>
</form>
</div>
</div>
`;
}).join('');
const content = `
<div style="margin-bottom: 1rem;">
<a href="/rbac-admin/roles" class="btn">← Back to Roles</a>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<div>
<h2>${role.name}</h2>
<p style="color: #666;">${role.description}</p>
</div>
<button class="btn btn-success" onclick="openModal('addFeatureModal')">Add Feature</button>
</div>
<div class="card">
<div class="card-header">
<h4>Assigned Features & Permissions (${roleFeatures.length})</h4>
</div>
<div class="card-body">
${roleFeatures.length > 0 ? assignedFeaturesHtml : '<p>No features assigned to this role yet.</p>'}
</div>
</div>
<!-- Add Feature Modal -->
<div id="addFeatureModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('addFeatureModal')">×</span>
<h3>Add Features to Role</h3>
<form method="POST" action="/rbac-admin/roles/${role._id}/assign-features">
<div class="form-group">
<label>Select Features:</label>
${unassignedFeatures.length > 0 ?
unassignedFeatures.map(f => `
<label style="display: block; margin: 5px 0;">
<input type="checkbox" name="featureIds" value="${f._id}">
${f.name} - ${f.description}
</label>
`).join('') :
'<p>All features are already assigned to this role.</p>'}
</div>
${unassignedFeatures.length > 0 ?
'<button type="submit" class="btn btn-success">Add Selected Features</button>' : ''}
<button type="button" class="btn" onclick="closeModal('addFeatureModal')">Cancel</button>
</form>
</div>
</div>
${permissionModals}
<script>
function removeFeatureFromRole(roleId, featureId) {
if (confirm('Remove this feature from the role?')) {
fetch('/rbac-admin/roles/' + roleId + '/remove-features', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ featureIds: [featureId] })
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
} else {
window.location.reload();
}
})
.catch(err => alert('Error: ' + err.message));
}
}
function removePermissionFromFeature(roleId, featureId, permissionId) {
if (confirm('Remove this permission?')) {
fetch('/rbac-admin/roles/' + roleId + '/remove-permissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ featureIds: [featureId], permissionIds: [permissionId] })
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
} else {
window.location.reload();
}
})
.catch(err => alert('Error: ' + err.message));
}
}
</script>
`;
return (0, layout_1.getBaseLayout)('Role Details', content);
};
exports.getRoleDetailsView = getRoleDetailsView;