Implement RBAC roles system

This commit is contained in:
2026-07-21 21:05:02 +01:00
parent 7973ee0ea4
commit e051958bea
35 changed files with 2338 additions and 2257 deletions
+42 -7
View File
@@ -2,10 +2,20 @@ const fs = require('fs');
const path = require('path');
const Handlebars = require('handlebars');
const { version: appVersion } = require('../../package.json');
const { hasPermission, hasAnyPermission } = require('../rbac');
const VIEWS_ROOT = path.join(__dirname, 'views');
const cache = new Map();
function inferMessageVariant(message, fallbackVariant) {
const text = String(message || '').trim();
if (/\b(?:unable to|cannot|can't|could not|failed to)\s+delete\b/i.test(text) || /\bdelete\b.*\b(?:before|first)\b/i.test(text) || /\bstill (?:in use|linked|assigned|used)\b/i.test(text)) {
return 'danger';
}
return String(fallbackVariant || '').trim().toLowerCase() || 'primary';
}
Handlebars.registerHelper('eq', function (left, right) {
return left === right;
});
@@ -23,6 +33,16 @@ Handlebars.registerHelper('json', function (value) {
return new Handlebars.SafeString(JSON.stringify(value).replace(/</g, '\\u003c'));
});
Handlebars.registerHelper('hasPermission', function (currentUser, permissionKey) {
return hasPermission(currentUser, permissionKey);
});
Handlebars.registerHelper('anyPermission', function (currentUser) {
const args = Array.prototype.slice.call(arguments, 1);
args.pop();
return hasAnyPermission(currentUser, args);
});
Handlebars.registerHelper('usernameInitial', function (username) {
const value = String(username || '').trim();
if (!value) {
@@ -39,6 +59,16 @@ Handlebars.registerHelper('userInitial', function (name, username) {
return value.charAt(0).toUpperCase();
});
Handlebars.registerHelper('truncateText', function (value, maxLength) {
const text = String(value || '').trim();
const limit = Number(maxLength);
if (!text || !Number.isFinite(limit) || limit <= 0 || text.length <= limit) {
return text;
}
return `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}`;
});
Handlebars.registerHelper('saveActionButtons', function (options) {
const hash = options && options.hash ? options.hash : {};
const formId = String(hash.formId || '').trim();
@@ -53,6 +83,7 @@ Handlebars.registerHelper('saveActionButtons', function (options) {
const showSaveAndClose = hash.showSaveAndClose === undefined ? true : String(hash.showSaveAndClose).toLowerCase() !== 'false';
const showSaveAndNew = hash.showSaveAndNew === undefined ? true : String(hash.showSaveAndNew).toLowerCase() !== 'false';
const ariaLabel = String(hash.ariaLabel || 'Save actions');
const hasSecondaryActions = showSaveAndClose || showSaveAndNew;
if (!formId) {
return '';
@@ -64,13 +95,11 @@ Handlebars.registerHelper('saveActionButtons', function (options) {
return new Handlebars.SafeString([
'<div class="btn-group save-action-group" role="group">',
`<button type="submit" class="${escape(saveButtonClass)}"${formAttr} name="${escape(saveActionName)}" value="${escape(saveActionValue)}">${escape(saveLabel)}</button>`,
`<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">`,
'<span class="visually-hidden">Toggle save options</span>',
'</button>',
'<div class="dropdown-menu dropdown-menu-end">',
showSaveAndClose ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(closeValue)}">${escape(closeLabel)}</button>` : '',
showSaveAndNew ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(newValue)}">${escape(newLabel)}</button>` : '',
'</div>',
hasSecondaryActions ? `<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">${escape(ariaLabel)}</span></button>` : '',
hasSecondaryActions ? '<div class="dropdown-menu dropdown-menu-end">' : '',
hasSecondaryActions && showSaveAndClose ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(closeValue)}">${escape(closeLabel)}</button>` : '',
hasSecondaryActions && showSaveAndNew ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(newValue)}">${escape(newLabel)}</button>` : '',
hasSecondaryActions ? '</div>' : '',
'</div>',
].join(''));
});
@@ -89,6 +118,9 @@ function loadTemplate(relativePath) {
function renderView(viewName, context) {
const viewContext = Object.assign({ stylesheets: [], scripts: [], appVersion: appVersion }, context || {});
if (!viewContext.messageVariant) {
viewContext.messageVariant = inferMessageVariant(viewContext.message, 'primary');
}
const page = loadTemplate(`${viewName}.hbs`);
const layout = loadTemplate(path.join('layout.hbs'));
const body = page(viewContext);
@@ -97,6 +129,9 @@ function renderView(viewName, context) {
function renderFragment(viewName, context) {
const viewContext = Object.assign({ stylesheets: [], scripts: [], appVersion: appVersion }, context || {});
if (!viewContext.messageVariant) {
viewContext.messageVariant = inferMessageVariant(viewContext.message, 'primary');
}
const page = loadTemplate(`${viewName}.hbs`);
const layout = loadTemplate(path.join('frame-layout.hbs'));
const body = page(viewContext);