118 lines
5.9 KiB
JavaScript
118 lines
5.9 KiB
JavaScript
/* ============================================================
|
|
rbac.js — Enterprise Role Based Access Control
|
|
Microsoft Admin Center-style permission matrix
|
|
============================================================ */
|
|
window.Views = window.Views || {};
|
|
window.RBAC = {};
|
|
|
|
Views.rbac = function () {
|
|
const state = { roleIdx: 0 };
|
|
RBAC._state = state;
|
|
|
|
const html = `
|
|
<div class="page">
|
|
<div class="page-head">
|
|
<div><h1 class="page-title">Access Control</h1><p class="page-sub">Enterprise RBAC — configure permissions for every role and module</p></div>
|
|
<div class="page-head-actions">
|
|
<button class="btn btn-secondary" onclick="RBAC.addRole()">${UI.icon('plus')} New Role</button>
|
|
<button class="btn btn-primary" onclick="UI.toast('Permission changes saved','success')">${UI.icon('check')} Save Changes</button>
|
|
</div>
|
|
</div>
|
|
<div class="rbac-layout">
|
|
<div class="card" style="align-self:start"><div class="card-body" style="padding:12px">
|
|
<div class="nav-section-label" style="padding:6px 8px">Roles</div>
|
|
<div class="role-list" id="roleList"></div>
|
|
</div></div>
|
|
<div class="card"><div id="rbacDetail"></div></div>
|
|
</div>
|
|
</div>`;
|
|
|
|
return {
|
|
html,
|
|
onMount() { RBAC._renderRoles(); RBAC._renderDetail(); }
|
|
};
|
|
};
|
|
|
|
RBAC._renderRoles = function () {
|
|
const el = document.getElementById('roleList');
|
|
el.innerHTML = DB.rbacRoles.map((r, i) => `
|
|
<div class="role-item ${i === RBAC._state.roleIdx ? 'active' : ''}" data-idx="${i}">
|
|
<span class="role-badge" style="background:${r.color}">${UI.icon('shield')}</span>
|
|
<div style="flex:1;min-width:0"><div class="fw-600 text-sm">${r.name}</div><div class="cell-sub">${r.users} user${r.users === 1 ? '' : 's'}</div></div>
|
|
</div>`).join('');
|
|
el.querySelectorAll('.role-item').forEach(item => item.onclick = () => {
|
|
RBAC._state.roleIdx = +item.dataset.idx;
|
|
RBAC._renderRoles(); RBAC._renderDetail();
|
|
});
|
|
};
|
|
|
|
RBAC._renderDetail = function () {
|
|
const r = DB.rbacRoles[RBAC._state.roleIdx];
|
|
const el = document.getElementById('rbacDetail');
|
|
|
|
const matrixRows = DB.rbacModules.map(mod => `
|
|
<tr>
|
|
<td>${mod}</td>
|
|
${DB.permTypes.map((pt, pi) => `<td><span class="perm-check ${r.matrix[mod][pi] ? 'on' : ''}" data-mod="${mod}" data-perm="${pi}">${UI.icon('check')}</span></td>`).join('')}
|
|
</tr>`).join('');
|
|
|
|
el.innerHTML = `
|
|
<div class="card-head">
|
|
<div class="flex items-center gap-12"><span class="role-badge" style="background:${r.color}">${UI.icon('shield')}</span>
|
|
<div><h3>${r.name}</h3><span class="ch-sub">${r.desc}</span></div></div>
|
|
<div class="flex items-center gap-8">
|
|
<span class="badge b-gray badge-plain">${r.users} users</span>
|
|
<button class="btn btn-ghost btn-sm" onclick="RBAC.toggleAll(true)">Grant all</button>
|
|
<button class="btn btn-ghost btn-sm" onclick="RBAC.toggleAll(false)">Revoke all</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-wrap"><table class="rbac-matrix">
|
|
<thead><tr><th>Module</th>${DB.permTypes.map(p => `<th>${p}</th>`).join('')}</tr></thead>
|
|
<tbody>${matrixRows}</tbody>
|
|
</table></div>
|
|
</div>`;
|
|
|
|
el.querySelectorAll('.perm-check').forEach(chk => chk.onclick = () => {
|
|
const mod = chk.dataset.mod, pi = +chk.dataset.perm;
|
|
r.matrix[mod][pi] = !r.matrix[mod][pi];
|
|
chk.classList.toggle('on');
|
|
});
|
|
};
|
|
|
|
RBAC.toggleAll = function (on) {
|
|
const r = DB.rbacRoles[RBAC._state.roleIdx];
|
|
DB.rbacModules.forEach(mod => r.matrix[mod] = r.matrix[mod].map(() => on));
|
|
RBAC._renderDetail();
|
|
UI.toast(on ? 'All permissions granted for ' + r.name : 'All permissions revoked for ' + r.name, on ? 'success' : 'warning');
|
|
};
|
|
|
|
RBAC.addRole = function () {
|
|
UI.modal({
|
|
title: 'Create Role', subtitle: 'Define a new access role',
|
|
body: `<form id="roleForm"><div class="form-grid">
|
|
<div class="form-field col-span-2"><label>Role Name <span class="req">*</span></label><input name="name" placeholder="e.g. Regional Recruiter"/><span class="field-error">Required</span></div>
|
|
<div class="form-field col-span-2"><label>Description</label><input name="desc" placeholder="What can this role do?"/></div>
|
|
<div class="form-field"><label>Base Template</label><select name="template"><option>View only</option><option>Editor</option><option>Approver</option><option>Manager</option><option>Administrator</option></select></div>
|
|
<div class="form-field"><label>Color</label><select name="color"><option value="var(--av-1)">Utopia Green</option><option value="var(--av-3)">Periwinkle</option><option value="var(--av-6)">Teal</option><option value="var(--av-7)">Violet</option><option value="var(--av-8)">Rose</option></select></div>
|
|
</div></form>`,
|
|
footer: `<button class="btn btn-secondary" onclick="UI.closeModal()">Cancel</button><button class="btn btn-primary" onclick="RBAC._saveRole()">${UI.icon('check')} Create Role</button>`
|
|
});
|
|
};
|
|
RBAC._saveRole = function () {
|
|
const form = document.getElementById('roleForm');
|
|
UI.clearErrors(form);
|
|
const f = Object.fromEntries(new FormData(form));
|
|
if (!f.name.trim()) { UI.fieldError(form.querySelector('[name=name]'), 'Required'); return; }
|
|
const levelMap = { 'View only': 'View', 'Editor': 'Edit', 'Approver': 'Approve', 'Manager': 'Manage', 'Administrator': 'Administrator' };
|
|
const level = levelMap[f.template] || 'View';
|
|
const idxMap = { 'View': 1, 'Edit': 3, 'Approve': 5, 'Manage': 7, 'Administrator': 8 };
|
|
const cutoff = idxMap[level];
|
|
const matrix = {};
|
|
DB.rbacModules.forEach(mod => matrix[mod] = DB.permTypes.map((p, i) => i < cutoff));
|
|
DB.rbacRoles.push({ name: f.name, users: 0, color: f.color, desc: f.desc || 'Custom role', level, matrix });
|
|
RBAC._state.roleIdx = DB.rbacRoles.length - 1;
|
|
UI.closeModal(); RBAC._renderRoles(); RBAC._renderDetail();
|
|
UI.toast('Role "' + f.name + '" created', 'success');
|
|
};
|