You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Data-access service: extend core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.ts to cover /v1/roles/*, /v1/users/filter — replacing every RoleAjax DWR call needed by the Users tab and the roles tree that the Angular portlet renders directly. Permission endpoints and Tool Group endpoints (/v1/permissions/*, /v1/roles/layouts, /v1/portlet/custom, /v1/toolgroups/*, DWR RoleAjax.getRolePermissions / saveRolePermission / loadRoleLayouts / saveRoleLayouts / addNewLayout / etc.) are not consumed from the Angular portlet in this Epic — they stay behind the iframe wrappers described below
Reuse models from libs/dotcms-models: DotRole (extend as needed)
PrimeNG only for every UI component: p-tree (roles hierarchy with drag-and-drop), p-tabView (Users / Permissions / Tools), p-table (users grid), p-dialog (Add Role / Edit Role / confirm-destroy), p-select (Parent dropdown), p-checkbox (Can Grant flags), p-textarea (Description), p-overlay-panel or p-autoComplete (Grant to User popover)
Users table follows the project's shared table styling conventions (see dot-users and dot-tags for reference); the design is a guide, not pixel-locked
Tests: Jest + Spectator on shell / page / store / each tab, with data-testid selectors on every interactive element
Screen structure (per new design)
Layout: two-column shell — left Roles panel, right Role detail panel. No page-level top bar beyond the shared dotCMS header (breadcrumbs Home > Settings > Roles, site selector, notifications, user avatar).
Left panel — Roles
Header: ROLES label + New button (opens Add Role dialog)
Filter roles search input (debounced client-side filter of the loaded tree)
Hierarchical p-tree list:
Top-level roles use a folder icon with expand/collapse chevron
Child roles use a shield icon
Per-row badge: user count with person icon (e.g., 👤 2)
Hover reveals inline + action to add a child role under that parent (opens Add Role dialog with Parent prefilled)
Drag-and-drop reparenting: dragging a role node onto a different parent updates Role.parent via a v1 endpoint (endpoint identified during the spike; if the current REST surface does not expose a reparent operation, file it as a backend hardening [Task] linked to this issue)
Selected node highlighted; selection drives the right-hand detail area
Right panel — Role detail
Header row: role icon (folder for parents, shield for children) + role name + subtitle N users · M tools granted + Edit Role button top-right
Three tabs (p-tabView):
1. Users — full Angular implementation
Grant to User button top-right opens an overlay panel with:
Search users input (debounced, hits /v1/users/filter or the equivalent identified in the spike)
Scrollable list of users (avatar + name + email); click a row to grant this role to that user
Below: p-table of current members
Columns: NAME (avatar + name), EMAIL, GRANTED FROM (chip showing the role that granted access — direct assignment shows the current role's name; inherited shows the parent role's name)
Empty state: dashed card with helper text This role has no users yet and copy Grant this role to a user using the selector above.
2. Permissions — iframe interim
Angular tab renders an <iframe> whose src points at a new lightweight JSP wrapper (see Backend wiring below) that hosts the existing Dojo/DWR permissions UI unchanged
No Angular re-implementation of the accordion permission matrix in this Epic; a follow-up [Task] is filed for the eventual Angular replacement so the iframe can be swapped by dropping in the new Angular component without touching the surrounding shell
Existing permission endpoints (RoleAjax.getRolePermissions, saveRolePermission, /v1/permissions/* where already used) stay behind the iframe
Iframe height / resize behavior: fills the tab body with a postMessage or auto-resize helper so users don't see nested scrollbars
3. Tools — iframe interim
Angular tab renders an <iframe> whose src points at a new lightweight JSP wrapper hosting the existing Dojo/DWR Tool Groups management UI unchanged (available / assigned layouts, New Layout dialog with dot-material-icon-picker, save role-layouts binding)
No Angular re-implementation of Tool Groups in this Epic because the design is still being defined; a follow-up [Task] is filed for the eventual Angular replacement so the iframe can be swapped by dropping in the new component
Existing Tool Group endpoints (RoleAjax.loadRoleLayouts / saveRoleLayouts / addNewLayout / etc., /v1/roles/layouts, /v1/portlet/custom) stay behind the iframe
Add Role dialog (triggered by New in the roles panel or the inline + on a row)
Title: Add Role
Fields:
Role * (required, red border on validation fail)
Key
Parent (p-select, defaults to None (top level); when opened from an inline +, prefilled with that parent role)
Can Grant (three p-checkbox: Users, Permissions, Tools — all checked by default per the design)
Description (p-textarea)
Actions: Cancel / Save (Save disabled until required fields valid)
closable: true, closeOnEscape: true
Edit Role dialog (triggered by Edit Role in the detail header)
Same fields as Add Role, prefilled with the selected role's values
Additional Delete Role destructive action bottom-left (red outline). Click triggers a confirm-destroy dialog
closable: true, closeOnEscape: true
Confirm-destroy dialog
Confirm-remove role, per dotCMS dialog standards (closable: true, closeOnEscape: true)
Backend wiring
dotCMS/src/main/webapp/WEB-INF/portlet.xml:
Add a new roles-beta portlet entry: <portlet-class>com.dotcms.spring.portlet.PortletController</portlet-class> + <portlet-url>/roles</portlet-url> + display name Roles and Tools (Beta)
The existing Dojo roles portlet entry stays untouched so it remains reachable via the "Add Portlet" picker
Not added to any default layout or UpgradeTask — admins opt in via "Add Portlet" in Tools
Angular route wired at roles in core-web/apps/dotcms-ui/src/app/app.routes.ts loading @dotcms/portlets/dot-roles/portlet
Reuse the existing PortletID.ROLES enum entry and the existing roles i18n title key; add a roles-beta i18n title key for the Beta display name
License + role gating preserved (requiredPortlet("roles") on v1 endpoints, admin-only surfaces enforced)
Permissions iframe wrapper JSP
Add a new minimal JSP under dotCMS/src/main/webapp/html/portlet/ext/roleadmin/ (naming to be decided in the spike, e.g., view_role_permissions_wrapper.jsp) that includes the existing view_role_permissions_inc.jsp + view_role_permissions_js_inc.jsp output and nothing else
The wrapper accepts the roleId via query string, initializes the existing Dojo/DWR components, and communicates readiness / size to the parent Angular tab via postMessage
Kept intentionally thin so the eventual Angular replacement (tracked as a separate [Task]) only requires swapping the iframe src for a new Angular component in dot-roles-page.component.ts
Tools iframe wrapper JSP
Add a second minimal JSP under dotCMS/src/main/webapp/html/portlet/ext/roleadmin/ (e.g., view_role_tools_wrapper.jsp) that surfaces just the Tool Groups management section of the existing view_roles_js_inc.jsp (available / assigned grids, New Layout dialog, save role-layouts)
Same postMessage contract as the permissions wrapper, same roleId query-string input, same thin-wrapper philosophy so the future Angular replacement is a drop-in
Untouched files
view_roles.jsp remains untouched — the wrappers are new files dedicated to the iframe use case, so the current Dojo portlet at /#/c/roles keeps working for admins who don't opt into the Beta
Reparent endpoint
If the drag-to-reparent flow requires a new v1 endpoint (spike output will confirm), open a backend [Task] linked to this issue and unblock it before merge
Inputs
Spike output from the consolidated audit issue (parity matrix + API audit + UI mapping + hardening backlog + reparent endpoint decision + iframe wrapper design for Permissions and Tools)
Every acceptance criterion in epic #36909 passes manually against the local build:
Beta portlet appears in the "Add Portlet" picker as Roles and Tools (Beta)
Adding the Beta portlet to a layout serves the Angular shell at /#/c/roles
Roles panel renders the full hierarchy with folder icons for parents and shield icons for children, expand/collapse, and user-count badges
Filter roles narrows the visible tree client-side
New opens the Add Role dialog; save creates the role and refreshes the tree
Inline + on a parent row opens Add Role dialog with Parent prefilled
Drag-and-drop of a role onto a different parent persists the reparent via v1 endpoint
Edit Role in the detail header opens the Edit dialog; save updates the role; Delete Role opens a confirm-destroy and removes the role
Users tab: Grant to User popover searches users and grants the role on selection; the members table renders NAME / EMAIL / GRANTED FROM (with inheritance chip); remove and bulk-remove work; empty state matches the design copy
Permissions tab: iframe loads the new JSP wrapper and renders the existing permissions UI functional (matrix, cascade toggle, apply changes, cascade-progress) — behavior parity with the current Dojo portlet
Tools tab: iframe loads the new JSP wrapper and renders the existing Tool Groups management UI functional (available / assigned layouts, New Layout dialog, save role-layouts) — behavior parity with the current Dojo portlet
License + role gating preserved on every write path
Dojo roles portlet entry remains registered so admins can revert by removing the Beta portlet from their layout
Goal
Ship the Angular Roles and Tools portlet end-to-end — UI,
portlet.xmlregistration as an opt-in Beta portlet, and rollback story — in a single PR.Frontend scope
New Nx library:
core-web/libs/portlets/dot-roles/dot-roles-shell.component.tsdot-roles-page.component.tslib.routes.tsexported asdotRolesRoutes, imported atrolesincore-web/apps/dotcms-ui/src/app/app.routes.tsDotRolesStore(role tree, selected role, active tab, per-tab pagination + search, drag state, in-flight flags)core-web/libs/data-access/src/lib/dot-roles/dot-roles.service.tsto cover/v1/roles/*,/v1/users/filter— replacing everyRoleAjaxDWR call needed by the Users tab and the roles tree that the Angular portlet renders directly. Permission endpoints and Tool Group endpoints (/v1/permissions/*,/v1/roles/layouts,/v1/portlet/custom,/v1/toolgroups/*, DWRRoleAjax.getRolePermissions/saveRolePermission/loadRoleLayouts/saveRoleLayouts/addNewLayout/ etc.) are not consumed from the Angular portlet in this Epic — they stay behind the iframe wrappers described belowlibs/dotcms-models:DotRole(extend as needed)p-tree(roles hierarchy with drag-and-drop),p-tabView(Users / Permissions / Tools),p-table(users grid),p-dialog(Add Role / Edit Role / confirm-destroy),p-select(Parent dropdown),p-checkbox(Can Grant flags),p-textarea(Description),p-overlay-panelorp-autoComplete(Grant to User popover)dot-usersanddot-tagsfor reference); the design is a guide, not pixel-lockeddata-testidselectors on every interactive elementScreen structure (per new design)
Layout: two-column shell — left
Rolespanel, rightRole detailpanel. No page-level top bar beyond the shared dotCMS header (breadcrumbsHome > Settings > Roles, site selector, notifications, user avatar).Left panel — Roles
ROLESlabel +Newbutton (opens Add Role dialog)Filter rolessearch input (debounced client-side filter of the loaded tree)p-treelist:👤 2)+action to add a child role under that parent (opens Add Role dialog withParentprefilled)Role.parentvia a v1 endpoint (endpoint identified during the spike; if the current REST surface does not expose a reparent operation, file it as a backend hardening[Task]linked to this issue)Right panel — Role detail
Header row: role icon (folder for parents, shield for children) + role name + subtitle
N users · M tools granted+Edit Rolebutton top-rightThree tabs (
p-tabView):1. Users — full Angular implementation
Grant to Userbutton top-right opens an overlay panel with:Search usersinput (debounced, hits/v1/users/filteror the equivalent identified in the spike)p-tableof current membersRemoveaction; multi-select supports bulk removeThis role has no users yetand copyGrant this role to a user using the selector above.2. Permissions — iframe interim
<iframe>whosesrcpoints at a new lightweight JSP wrapper (see Backend wiring below) that hosts the existing Dojo/DWR permissions UI unchanged[Task]is filed for the eventual Angular replacement so the iframe can be swapped by dropping in the new Angular component without touching the surrounding shellRoleAjax.getRolePermissions,saveRolePermission,/v1/permissions/*where already used) stay behind the iframepostMessageor auto-resize helper so users don't see nested scrollbars3. Tools — iframe interim
<iframe>whosesrcpoints at a new lightweight JSP wrapper hosting the existing Dojo/DWR Tool Groups management UI unchanged (available / assigned layouts,New Layoutdialog withdot-material-icon-picker, save role-layouts binding)[Task]is filed for the eventual Angular replacement so the iframe can be swapped by dropping in the new componentRoleAjax.loadRoleLayouts/saveRoleLayouts/addNewLayout/ etc.,/v1/roles/layouts,/v1/portlet/custom) stay behind the iframeAdd Role dialog (triggered by
Newin the roles panel or the inline+on a row)Add RoleRole *(required, red border on validation fail)KeyParent(p-select, defaults toNone (top level); when opened from an inline+, prefilled with that parent role)Can Grant(threep-checkbox: Users, Permissions, Tools — all checked by default per the design)Description(p-textarea)Cancel/Save(Save disabled until required fields valid)closable: true,closeOnEscape: trueEdit Role dialog (triggered by
Edit Rolein the detail header)Delete Roledestructive action bottom-left (red outline). Click triggers a confirm-destroy dialogclosable: true,closeOnEscape: trueConfirm-destroy dialog
closable: true,closeOnEscape: true)Backend wiring
dotCMS/src/main/webapp/WEB-INF/portlet.xml:roles-betaportlet entry:<portlet-class>com.dotcms.spring.portlet.PortletController</portlet-class>+<portlet-url>/roles</portlet-url>+ display nameRoles and Tools (Beta)rolesportlet entry stays untouched so it remains reachable via the "Add Portlet" pickerUpgradeTask— admins opt in via "Add Portlet" in Toolsrolesincore-web/apps/dotcms-ui/src/app/app.routes.tsloading@dotcms/portlets/dot-roles/portletPortletID.ROLESenum entry and the existingrolesi18n title key; add aroles-betai18n title key for the Beta display namerequiredPortlet("roles")on v1 endpoints, admin-only surfaces enforced)Permissions iframe wrapper JSP
dotCMS/src/main/webapp/html/portlet/ext/roleadmin/(naming to be decided in the spike, e.g.,view_role_permissions_wrapper.jsp) that includes the existingview_role_permissions_inc.jsp+view_role_permissions_js_inc.jspoutput and nothing elseroleIdvia query string, initializes the existing Dojo/DWR components, and communicates readiness / size to the parent Angular tab viapostMessage[Task]) only requires swapping the iframesrcfor a new Angular component indot-roles-page.component.tsTools iframe wrapper JSP
dotCMS/src/main/webapp/html/portlet/ext/roleadmin/(e.g.,view_role_tools_wrapper.jsp) that surfaces just the Tool Groups management section of the existingview_roles_js_inc.jsp(available / assigned grids,New Layoutdialog, save role-layouts)postMessagecontract as the permissions wrapper, sameroleIdquery-string input, same thin-wrapper philosophy so the future Angular replacement is a drop-inUntouched files
view_roles.jspremains untouched — the wrappers are new files dedicated to the iframe use case, so the current Dojo portlet at/#/c/roleskeeps working for admins who don't opt into the BetaReparent endpoint
[Task]linked to this issue and unblock it before mergeInputs
core-web/libs/portlets/CLAUDE.mdcore-web/libs/portlets/dot-users/(same Beta pattern, same iframe-for-undesigned-surfaces approach) andcore-web/libs/portlets/dot-velocity-playground/Definition of Done
Every acceptance criterion in epic #36909 passes manually against the local build:
Roles and Tools (Beta)/#/c/rolesFilter rolesnarrows the visible tree client-sideNewopens the Add Role dialog; save creates the role and refreshes the tree+on a parent row opens Add Role dialog withParentprefilledEdit Rolein the detail header opens the Edit dialog; save updates the role;Delete Roleopens a confirm-destroy and removes the roleGrant to Userpopover searches users and grants the role on selection; the members table renders NAME / EMAIL / GRANTED FROM (with inheritance chip); remove and bulk-remove work; empty state matches the design copyNew Layoutdialog, save role-layouts) — behavior parity with the current Dojo portletrolesportlet entry remains registered so admins can revert by removing the Beta portlet from their layout[Task]filed for the Angular replacement of the Permissions tab, linked to this issue and to epic Dojo to Angular: Roles and Tools Portlet #36909[Task]filed for the Angular replacement of the Tools tab (once design is defined), linked to this issue and to epic Dojo to Angular: Roles and Tools Portlet #36909