Re-design UI - #749
Conversation
|
There's probably some changes needed but its ready for a look from a design PoV.
cc @mawinter69 |
Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com>
| @@ -0,0 +1,201 @@ | |||
| #!/bin/bash | |||
There was a problem hiding this comment.
Useful script for performance testing
| </div> | ||
| </f:entry> | ||
|
|
||
| <f:bottomButtonBar> |
There was a problem hiding this comment.
| <f:bottomButtonBar> | |
| <f:bottomButtonBar borderless="true"> |
| </div> | ||
| </f:entry> | ||
|
|
||
| <f:bottomButtonBar> |
There was a problem hiding this comment.
| <f:bottomButtonBar> | |
| <f:bottomButtonBar borderless="true"> |
|
|
||
| </f:entry> | ||
|
|
||
| <f:bottomButtonBar> |
There was a problem hiding this comment.
| <f:bottomButtonBar> | |
| <f:bottomButtonBar borderless="true"> |
|
|
||
| </f:entry> | ||
|
|
||
| <f:bottomButtonBar> |
There was a problem hiding this comment.
| <f:bottomButtonBar> | |
| <f:bottomButtonBar borderless="true"> |
| </div> | ||
| </f:entry> | ||
|
|
||
| <f:bottomButtonBar> |
There was a problem hiding this comment.
| <f:bottomButtonBar> | |
| <f:bottomButtonBar borderless="true"> |
| </div> | ||
| </f:entry> | ||
|
|
||
| <f:bottomButtonBar> |
There was a problem hiding this comment.
| <f:bottomButtonBar> | |
| <f:bottomButtonBar borderless="true"> |
|
|
Seems in Matrix auth you have added small |
|
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. |
|
Handling for ambiguous assignments seems to be missing |
|
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. |
| [<j:forEach var="role" items="${it.strategy.getGrantedRolesEntries(it.strategy.GLOBAL)}" varStatus="s"><j:if test="${!s.first}">,</j:if>{"name":"${h.jsStringEscape(role.key.name)}","permissions":[<j:forEach var="p" items="${role.key.permissions}" varStatus="ps"><j:if test="${!ps.first}">,</j:if>"${h.jsStringEscape(p.group.title.toString() + '/' + p.name)}"</j:forEach>]}</j:forEach>] | ||
| </script> | ||
| <script type="application/json" id="rsp-roles-project"> | ||
| [<j:forEach var="role" items="${it.strategy.getGrantedRolesEntries(it.strategy.PROJECT)}" varStatus="s"><j:if test="${!s.first}">,</j:if>{"name":"${h.jsStringEscape(role.key.name)}","pattern":"${h.jsStringEscape(role.key.pattern.toString())}","permissions":[<j:forEach var="p" items="${role.key.permissions}" varStatus="ps"><j:if test="${!ps.first}">,</j:if>"${h.jsStringEscape(p.group.title.toString() + '/' + p.name)}"</j:forEach>]}</j:forEach>] | ||
| </script> | ||
| <script type="application/json" id="rsp-roles-slave"> | ||
| [<j:forEach var="role" items="${it.strategy.getGrantedRolesEntries(it.strategy.SLAVE)}" varStatus="s"><j:if test="${!s.first}">,</j:if>{"name":"${h.jsStringEscape(role.key.name)}","pattern":"${h.jsStringEscape(role.key.pattern.toString())}","permissions":[<j:forEach var="p" items="${role.key.permissions}" varStatus="ps"><j:if test="${!ps.first}">,</j:if>"${h.jsStringEscape(p.group.title.toString() + '/' + p.name)}"</j:forEach>]}</j:forEach>] |
| <l:search-bar placeholder="${%Filter permissions}" clazz="rsp-perm-dialog-filter" hasKeyboardShortcut="false" /> | ||
|
|
||
| <div class="rsp-assign-dialog__roles" name="permissions"> | ||
| <j:forEach var="roleType" items="globalRoles,projectRoles,slaveRoles"> |
| # Seed script: creates ~500 roles, ~150 agent roles, 2500 user assignments, 250 groups | ||
| # Usage: ./seed-data.sh [JENKINS_URL] | ||
| set -euo pipefail | ||
|
|
||
| JENKINS="${1:-http://localhost:8080/jenkins}" | ||
| API="$JENKINS/role-strategy/strategy" | ||
| JENKINS_USER="${JENKINS_USERNAME}" | ||
| JENKINS_TOKEN="${JENKINS_API_TOKEN}" | ||
|
|
||
| echo "=== Seeding role-strategy data at $JENKINS ===" | ||
| echo "Authenticating as $JENKINS_USER..." | ||
| curl -sf -u "$JENKINS_USER:$JENKINS_TOKEN" "$JENKINS/api/json" > /dev/null || { echo "ERROR: Cannot reach Jenkins"; exit 1; } |
| @@ -0,0 +1 @@ | |||
| implied=implied | |||
| Behaviour.specify(".rsp-template-edit", "RoleStrategyTemplates", 0, (btn) => { | ||
| if (btn.dataset.initialized === "true") return; | ||
| btn.dataset.initialized = "true"; | ||
| btn.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| const initDialog = () => { | ||
| const form = document.querySelector("form[name='edit-template']"); | ||
| if (!form) { | ||
| setTimeout(initDialog, 100); | ||
| return; | ||
| } | ||
| tplInitDialog(form, "rsp-edit-template-submit-btn"); | ||
| }; | ||
| setTimeout(initDialog, 200); | ||
| }); | ||
| }); | ||
|
|
||
| // Add template — dialog opened via data-type="dialog-opener" | ||
| Behaviour.specify( | ||
| ".rsp-add-template-btn", | ||
| "RoleStrategyTemplates", | ||
| 0, | ||
| (btn) => { | ||
| if (btn.dataset.initialized === "true") return; | ||
| btn.dataset.initialized = "true"; | ||
| btn.addEventListener("click", () => { | ||
| const initDialog = () => { | ||
| const form = document.querySelector("form[name='add-template']"); | ||
| if (!form) { | ||
| setTimeout(initDialog, 100); | ||
| return; | ||
| } | ||
| tplInitDialog(form, "rsp-add-template-submit-btn"); | ||
| }; | ||
| setTimeout(initDialog, 200); | ||
| }); | ||
| }, | ||
| ); |
| Behaviour.specify( | ||
| "#rsp-template-cards .rsp-perm__item input[type=checkbox]", | ||
| "RoleStrategyTemplates", | ||
| 0, | ||
| (cb) => { | ||
| if (cb.dataset.initialized === "true") return; | ||
| cb.dataset.initialized = "true"; | ||
| cb.addEventListener("change", () => { | ||
| const card = cb.closest(".rsp-card"); | ||
| if (card) { | ||
| tplUpdateImplied(card); | ||
| tplUpdateSummary(card); | ||
| } | ||
| }); | ||
| }, | ||
| ); |
| @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; | ||
| } | ||
| } |
|
@mawinter69 would you be open to this being done in React. I've got a port here: timja#1 Its got issues that I'm working through but just wanted to see if you'd be open to that? |
|
I have no experience with react, but if you think that this will make things easier then go ahead |
Yeah its far easier to handle the interactivity and simpler code. I'll extract some changes from this and start opening some PRs, react port is mostly there. |

TODO:
For later:
role-strategy.mov
Testing done
Extensively manually tested including with different personas:
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