Skip to content

Re-design UI#749

Open
timja wants to merge 81 commits into
jenkinsci:masterfrom
timja:refine-ui
Open

Re-design UI#749
timja wants to merge 81 commits into
jenkinsci:masterfrom
timja:refine-ui

Conversation

@timja
Copy link
Copy Markdown
Member

@timja timja commented Apr 3, 2026

TODO:

  • Get upstream dialogs change merged in that this PR requires
  • Check opening of the dialog is done right, I think its being done in JS when it should be done by setting the right jelly values
  • Check UI performance when lots of roles
  • Check groups work properly, e.g. display name and searching by display name
  • Adapt for system read
  • Test system read end 2 end once all other changes done
  • Check role manager permission works
  • Update permission templates to jelly dialog from js dialog
  • Switch to setting-subpage
  • Decide on navigation, e.g. permissions templates from roles page, should each action be duplicated or something else, Markus' feedback about subcategories could be useful here so we use the sidepanel instead of app-bar
  • The entry page name, heading is current role assignment but the title is something different
  • Should there be Save/ apply buttons or just save after every change like it currently does or should there be edit button that pops up a dialog
  • In the add dialog its just got plain radio and checkboxes, should that be enhanced?
  • Bottom button bar not working, fixed on role assignments page
  • Security realm update user and group name e.g ID to display name
    • Should work now, need to test groups
  • Review code in general

For later:

  • Item based selection rather than just regex
  • User / group autocompletion / validation on add - might look at it in this
role-strategy.mov

Testing done

Extensively manually tested including with different personas:

  • Admin
  • SystemRead
  • Item roles admin
  • Agent roles admin
  • Item and Agent roles admin

Verified behaviour is correct across all of these.
I intend to create some playwright tests as well as there's 0 UI coverage at all in this repo (or in ATH), but that will likely be for another PR although I may stack it on this to get it going

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

@timja
Copy link
Copy Markdown
Member Author

timja commented Apr 4, 2026

There's probably some changes needed but its ready for a look from a design PoV.

  • Code not ready for review yet
  • Documentation probably needs updating
  • Once we're happy with the design, data saving etc, I can look at ATH / PCT.

cc @mawinter69

@timja timja changed the title Init Re-design UI Apr 5, 2026
@timja timja mentioned this pull request Apr 13, 2026
6 tasks
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

rbas.doAddTemplate(templateName, permIds, overwrite);
}

rsp.sendRedirect(redirectUrl);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleTemplateSubmit calls RoleBasedAuthorizationStrategy#doAddTemplate(...), which is an HTTP handler that may write an error response (e.g. 400 on duplicate template name) via Stapler.getCurrentResponse2().sendError(...). This method then unconditionally redirects, which can mask the error or attempt to redirect after the response is committed.

Consider moving the template creation logic into RoleStrategyConfig (or a shared non-HTTP helper) so you can return a proper error to the dialog, and only redirect on success.

Suggested change
rsp.sendRedirect(redirectUrl);
if (!rsp.isCommitted()) {
rsp.sendRedirect(redirectUrl);
}

Copilot uses AI. Check for mistakes.
class="jenkins-button jenkins-button--tertiary rsp-card__action rsp-template-edit"
tooltip="${%Edit template}"
data-type="dialog-opener"
data-dialog-url="${rootURL}/manage/role-strategy/edit-template-dialog?templateName=${h.escape(template.name)}">
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dialog URL embeds template.name in a query string using h.escape(...), which HTML-escapes but does not URL-encode. Template names containing spaces, &, ?, etc. will produce a broken URL (and can lead to confusing request parameters).

URL-encode the query parameter value (e.g., with a URL-encoding helper) rather than HTML-escaping it.

Suggested change
data-dialog-url="${rootURL}/manage/role-strategy/edit-template-dialog?templateName=${h.escape(template.name)}">
data-dialog-url="${rootURL}/manage/role-strategy/edit-template-dialog?templateName=${h.urlEncode(template.name)}">

Copilot uses AI. Check for mistakes.
<button type="button" class="jenkins-button jenkins-button--tertiary rsp-card__action rsp-card__edit"
tooltip="${%Edit role}"
data-type="dialog-opener"
data-dialog-url="${rootURL}/manage/role-strategy/edit-role-dialog?roleName=${h.escape(role.name)}&amp;scope=${roleType}">
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dialog URL embeds role.name in the query string using h.escape(...), which HTML-escapes but does not URL-encode. Role names containing spaces or reserved URL characters will break the generated URL.

URL-encode query parameter values instead of HTML-escaping them.

Suggested change
data-dialog-url="${rootURL}/manage/role-strategy/edit-role-dialog?roleName=${h.escape(role.name)}&amp;scope=${roleType}">
data-dialog-url="${rootURL}/manage/role-strategy/edit-role-dialog?roleName=${h.urlEncode(role.name)}&amp;scope=${roleType}">

Copilot uses AI. Check for mistakes.
Comment on lines +434 to +455
card.remove();
rspUpdateCardBorders();
tplDelete(name)
.then(() => {
if (
document.querySelectorAll("#rsp-template-cards .rsp-card")
.length === 0
) {
window.location.reload();
} else {
notificationBar.show(
`Template "${name}" deleted`,
notificationBar.SUCCESS,
);
}
})
.catch((err) => {
notificationBar.show(
"Failed to save: " + err.message,
notificationBar.ERROR,
);
});
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete flow removes the template card from the DOM before the server-side delete succeeds. If the tplDelete(...) request fails, the UI stays out of sync (the template appears deleted but still exists server-side) and the user must reload to recover.

Consider only removing the card after a successful delete, or implement a rollback path on failure (re-insert the card / reload on error).

Copilot uses AI. Check for mistakes.
Comment on lines +603 to +615
* @param role The role to check
* @param sid The user or group name
* @param type "USER" or "GROUP"
* @return true if the sid is assigned to the role
*/
public boolean isAssigned(@NonNull Role role, @NonNull String sid, @NonNull String type) {
Set<PermissionEntry> entries = this.grantedRoles.get(role);
if (entries == null) {
return false;
}
AuthorizationType authType = "GROUP".equals(type) ? AuthorizationType.GROUP : AuthorizationType.USER;
return entries.contains(new PermissionEntry(authType, sid))
|| entries.contains(new PermissionEntry(AuthorizationType.EITHER, sid));
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RoleMap#isAssigned Javadoc says type is only "USER" or "GROUP", but callers in this PR also pass "EITHER". The implementation treats any non-"GROUP" as USER and then checks USER or EITHER entries, so it works, but the documentation is now inaccurate.

Update the Javadoc to include "EITHER" (or clarify how it is handled).

Copilot uses AI. Check for mistakes.
Comment on lines +455 to 464
@CheckForNull
private static JSONObject getSubmittedFormOrRedirect(
StaplerRequest2 req, StaplerResponse2 rsp, String redirectUrl) throws IOException {
req.setCharacterEncoding("UTF-8");
JSONObject json = req.getSubmittedForm();
JSONObject rolesMapping;
if (json.has("submit")) {
String rm = json.getString("rolesMapping");
rolesMapping = JSONObject.fromObject(rm);
} else {
rolesMapping = json.getJSONObject("rolesMapping");
try {
return req.getSubmittedForm();
} catch (Exception e) {
rsp.sendRedirect(redirectUrl);
return null;
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getSubmittedFormOrRedirect swallows all getSubmittedForm() failures and issues a redirect. For endpoints like doAddRoleSubmit / doAssignRoleSubmit / template submits, an invalid or empty POST will likely become a 302 (often followed to 200 by the client) rather than a clear 400 error. This also makes the submit endpoints behave more like navigational pages than form APIs.

Consider returning a 400 (sendError) with a useful message when the form JSON is missing/invalid (and only redirect for genuine navigation flows), so both dialogs and tests can reliably detect bad submissions.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +60
var escapeHTML = function (unsafe) {
return unsafe.replace(/[&<>"']/g, function(m) {
switch (m) {
case "&":
return "&amp;";
case "<":
return "&lt;";
case '"':
return "&quot;";
default:
return "&#039;";
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escapeHTML currently maps the > character to &#039; (apostrophe) because there is no explicit case '>'. This will corrupt output containing > and also fails to properly HTML-escape >.

Add an explicit case '>' -> '&gt;' (and keep the apostrophe mapping only for ').

Copilot uses AI. Check for mistakes.
if (permsJson != null) {
for (String rawKey : permsJson.keySet()) {
if (permsJson.optBoolean(rawKey, false)) {
Permission p = Permission.fromId(rawKey);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collectPermissionsFromFlat calls Permission.fromId(rawKey) directly, but the checkbox names in the edit-role dialog are of the form name="[${p.id}]". That means the JSON keys are likely bracketed, so Permission.fromId will return null and editing a role can unintentionally clear all permissions.

Strip the surrounding brackets (reuse stripBrackets) before calling Permission.fromId, consistent with collectPermissionsFromScoped/collectPermissionIds.

Suggested change
Permission p = Permission.fromId(rawKey);
String permId = stripBrackets(rawKey);
Permission p = Permission.fromId(permId);

Copilot uses AI. Check for mistakes.
Comment thread pom.xml Outdated
Comment thread pom.xml Outdated
Comment thread pom.xml Outdated
Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com>
Comment thread seed-data.sh
@@ -0,0 +1,201 @@
#!/bin/bash
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful script for performance testing

@timja timja marked this pull request as ready for review April 21, 2026 14:55
@timja timja requested a review from a team as a code owner April 21, 2026 14:55
Copy link
Copy Markdown
Contributor

@mawinter69 mawinter69 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check boxes in the dialogs are touching each other.

Image

Maybe add some gap

.rsp-assign-dialog__group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

</div>
</f:entry>

<f:bottomButtonBar>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<f:bottomButtonBar>
<f:bottomButtonBar borderless="true">

</div>
</f:entry>

<f:bottomButtonBar>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<f:bottomButtonBar>
<f:bottomButtonBar borderless="true">


</f:entry>

<f:bottomButtonBar>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<f:bottomButtonBar>
<f:bottomButtonBar borderless="true">


</f:entry>

<f:bottomButtonBar>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<f:bottomButtonBar>
<f:bottomButtonBar borderless="true">

</div>
</f:entry>

<f:bottomButtonBar>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<f:bottomButtonBar>
<f:bottomButtonBar borderless="true">

</div>
</f:entry>

<f:bottomButtonBar>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<f:bottomButtonBar>
<f:bottomButtonBar borderless="true">

@mawinter69
Copy link
Copy Markdown
Contributor

  • When I assign a role and that user already has some roles assigned there is no error or warning shown, it seems to silently merge the roles. This is dangerous when when you have userids that are just like numbers, a simple typo could make you grant someone permissions that he should not have
  • In the role management page the actually used userid/groupid is not visible also not as tooltip
  • It would be nice when in the add role dialog the userid/groupis is directly resolved and the user/group displayname is shown, this helps to prevent giving the wrong user permissions
  • In the dialogs to assign roles it is hard to know which permissions are connected to the roles. A tooltip with the permissions might help here, either hovering the role or via a small icon on the right
  • Similarly when viewing the assigned roles of a user a tooltip might be helpful

@mawinter69
Copy link
Copy Markdown
Contributor

Seems in Matrix auth you have added small ? icons showing the description of the permission as tooltip. Would be good to have that here as well

@mawinter69
Copy link
Copy Markdown
Contributor

In Matrix auth the permissions are directly editable. Maybe we can do that here as well (unless a template is used) and limit the edit dialog to item and agent roles to modify the pattern / template only.

@mawinter69
Copy link
Copy Markdown
Contributor

Handling for ambiguous assignments seems to be missing

@mawinter69
Copy link
Copy Markdown
Contributor

  • the lib/rolestrategy/dropdownList.jelly taglib is not needed anymore
  • the braces.svg is not used anymore I think
  • Adding a new role or assignment is currently a page reload, I think in matrix auth you made that purely in javascript

@timja
Copy link
Copy Markdown
Member Author

timja commented Apr 25, 2026

Adding a new role or assignment is currently a page reload, I think in matrix auth you made that purely in javascript

Yes because saving is done when the security page as a whole is saved, whereas here its saved when the dialog closes.

It seemed clearer in this case to simplify everything by saving as part of the dialog, rather than saving every object on the page when you likely aren't changing much of it when adding e.g. new roles.

@mawinter69
Copy link
Copy Markdown
Contributor

Adding a new role or assignment is currently a page reload, I think in matrix auth you made that purely in javascript

Yes because saving is done when the security page as a whole is saved, whereas here its saved when the dialog closes.

It seemed clearer in this case to simplify everything by saving as part of the dialog, rather than saving every object on the page when you likely aren't changing much of it when adding e.g. new roles.

That would be an argument against my comment about being able to directly modify the permissions on the page.
The APIs should be there that allow to add the role/assignment in javascript in the background. Would just need rendering the new entry once adding/editing was successful. When you want to add several users you and have that page reload after every user is not the best UX I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants