Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4c5b9ca
Use card layout for credentials tables
timja Dec 19, 2025
d04b667
Refine card UI
janfaracik Dec 20, 2025
af8b1e0
Adjust colors + padding
janfaracik Dec 20, 2025
84a38e1
Update domain items
janfaracik Dec 20, 2025
b54d2c9
Add Configure button
janfaracik Dec 20, 2025
221cd44
Add credential count
janfaracik Dec 20, 2025
d348efb
Start migrating home page
janfaracik Dec 20, 2025
ff3c9a6
WIP homepage cards
janfaracik Dec 20, 2025
291f7c7
Update credentials.css
janfaracik Dec 20, 2025
8dc4e38
Fixes
janfaracik Dec 20, 2025
d883f31
Refine further
janfaracik Dec 20, 2025
068dcb7
Update credentials.css
janfaracik Dec 20, 2025
da86efb
Add missing check
timja Dec 20, 2025
468a242
Remove no longer needed brackets
timja Dec 20, 2025
be99fc9
Fix non editable stores showing actions
timja Dec 20, 2025
e78f847
Add empty state support
timja Dec 20, 2025
fe5718b
Remove unneeded brackets around description
timja Dec 20, 2025
451d6ae
Drop the .
janfaracik Dec 21, 2025
17bdf0f
Switch to adjunct for settings-subpage compat
timja Dec 21, 2025
73c94f1
Fix text wrap, fix icon shrinking + alignment
janfaracik Dec 22, 2025
275d125
Fix description wrap
janfaracik Dec 22, 2025
9753ab3
Pull out Stores into own Jelly file
janfaracik Dec 22, 2025
1113246
Use symbol-business-outline plugin-ionicons-api
janfaracik Dec 22, 2025
2250dba
Fix page description spacing
janfaracik Dec 22, 2025
ad653e4
Use dialog for deleting domains
janfaracik Dec 26, 2025
a9b60f1
Use dialog for deleting credentials
janfaracik Dec 26, 2025
aaff327
Update index.jelly
janfaracik Dec 26, 2025
8604f37
Fix ATH
timja Dec 27, 2025
80f5dbe
Use different icon for globals
janfaracik Dec 28, 2025
0781ef4
Use user avatar for user stores - happy to revert if not right approach
janfaracik Dec 28, 2025
c19f8c2
Update CredentialsStore.java
janfaracik Dec 28, 2025
c3818b6
Don't need to export iconClassName
timja Dec 28, 2025
362ecf0
Increase baseline for jenkins-badge
timja Dec 30, 2025
0e3a77f
Adjust junit5 import from not up-to date branch
timja Dec 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@
<changelist>999999-SNAPSHOT</changelist>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>
<!-- https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/ -->
<jenkins.baseline>2.479</jenkins.baseline>
<jenkins.version>${jenkins.baseline}.1</jenkins.version>
<jenkins.baseline>2.504</jenkins.baseline>
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
<ban-junit4-imports.skip>false</ban-junit4-imports.skip>
<hpi.compatibleSinceVersion>1372</hpi.compatibleSinceVersion>
</properties>
Expand All @@ -92,7 +92,7 @@
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-${jenkins.baseline}.x</artifactId>
<version>3482.vc10d4f6da_28a_</version>
<version>5804.v80587a_38d937</version>
<scope>import</scope>
<type>pom</type>
</dependency>
Expand Down Expand Up @@ -121,6 +121,10 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>bouncycastle-api</artifactId>
</dependency>
<dependency>
<groupId>io.jenkins.plugins</groupId>
<artifactId>ionicons-api</artifactId>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.mockito</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import hudson.tasks.UserAvatarResolver;
import jenkins.model.Jenkins;
import org.apache.commons.lang3.StringUtils;
import org.kohsuke.stapler.Stapler;
Expand Down Expand Up @@ -529,6 +531,21 @@
return relativeLink + domain.getUrl();
}

/**
* Returns the icon class name of the {@link #getContext()} of this {@link CredentialsStore}.
*
* @return the icon class name for the store.
* @since TODO

Check warning on line 538 in src/main/java/com/cloudbees/plugins/credentials/CredentialsStore.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL:
*/
public final String getIconClassName() {
Copy link
Member

Choose a reason for hiding this comment

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

This broke several CredentialsStore implementations in CloudBees CI which were implementing IconSpec (and thus defining their own getIconClassName methods). Is there a recommended migration?

ModelObject context = getContext();
if (context instanceof User user) {
return UserAvatarResolver.resolve(user, "96x96");
} else {
return getProviderOrDie().getIconClassName();

Check warning on line 545 in src/main/java/com/cloudbees/plugins/credentials/CredentialsStore.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 541-545 are not covered by tests
}
}

/**
* Returns the display name of the {@link #getContext()} of this {@link CredentialsStore}. The default
* implementation can handle both {@link Item} and {@link ItemGroup} as long as these are accessible from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@
*/
@ExportedBean
public static class DomainWrapper extends AbstractDescribableImpl<DomainWrapper> implements
ModelObjectWithContextMenu, ModelObjectWithChildren, AccessControlled {
ModelObjectWithContextMenu, ModelObjectWithChildren, AccessControlled, IconSpec {

/**
* The {@link CredentialsStoreAction} that we belong to.
Expand Down Expand Up @@ -668,6 +668,13 @@
return isGlobal() ? Messages.CredentialsStoreAction_GlobalDomainDisplayName() : domain.getName();
}

/**
* {@inheritDoc}
*/
public String getIconClassName() {
return isGlobal() ? "symbol-globe-outline plugin-ionicons-api" : "symbol-business-outline plugin-ionicons-api";

Check warning on line 675 in src/main/java/com/cloudbees/plugins/credentials/CredentialsStoreAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 675 is not covered by tests
}

/**
* Return the full name.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,11 @@
return credentials instanceof IdCredentials ? ((IdCredentials) credentials).getId() : null;
}

@SuppressWarnings("unused") // jelly
public boolean isEditable() {
return store.hasPermission(CredentialsProvider.UPDATE);

Check warning on line 518 in src/main/java/com/cloudbees/plugins/credentials/ViewCredentialsAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 518 is not covered by tests
}

/**
* Returns the {@link Credentials#getScope()} of the {@link #credentials}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,98 +23,118 @@
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
~ THE SOFTWARE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout"
xmlns:f="/lib/form" xmlns:c="/lib/credentials" xmlns:t="/lib/hudson">
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:dd="/lib/layout/dropdowns"
xmlns:f="/lib/form" xmlns:c="/lib/credentials" xmlns:t="/lib/hudson">
<l:layout title="${it.fullDisplayName}" permission="${it.parent.VIEW}" type="one-column">
<st:adjunct includes="com.cloudbees.plugins.credentials.common.card"/>

<j:set var="creds" value="${it.credentials.values()}"/>
<l:main-panel>
<l:app-bar title="${it.displayName}">
<l:hasPermission permission="${it.parent.CREATE}">
<a href="newCredentials" class="jenkins-button jenkins-button--primary">
<l:icon src="symbol-add" />
${%Add Credentials}
</a>
</l:hasPermission>
<j:if test="${!creds.isEmpty()}">
<l:hasPermission permission="${it.parent.CREATE}">
<a href="newCredentials" class="jenkins-button jenkins-button--primary">
<l:icon src="symbol-add"/>
${%Add Credentials}
</a>
</l:hasPermission>
</j:if>
<j:if test="${!it.global and it.parent.domainsModifiable}">
<l:hasPermission permission="${it.parent.CREATE}">
<a href="configure" class="jenkins-button">
<l:icon class="icon-setting icon-md" />
${%Configure domain}
</a>
<a href="delete" class="jenkins-button jenkins-button--destructive jenkins-!-destructive-color">
<l:icon class="icon-edit-delete icon-md" />
${%Delete domain}
${%Configure}
</a>
<l:overflowButton>
<dd:custom>
<l:confirmationLink class="jenkins-dropdown__item jenkins-!-destructive-color"
href="doDelete"
title="${%delete.domain(it.displayName)}"
post="true"
destructive="true">
<div class="jenkins-dropdown__item__icon">
<l:icon src="symbol-trash" />
</div>
${%Delete domain}
</l:confirmationLink>
</dd:custom>
</l:overflowButton>
</l:hasPermission>
</j:if>
</l:app-bar>
<div>
<j:out value="${it.description!=null ? app.markupFormatter.translate(it.description) : ''}" />
</div>
<t:setIconSize/>
<table class="jenkins-table ${iconSize == '16x16' ? 'jenkins-table--small' : iconSize == '24x24' ? 'jenkins-table--medium' : ''} sortable">
<thead>
<tr>
<th class="jenkins-table__cell--tight" data-sort-disable="true"/>
<th>${%ID}</th>
<th>${%Name}</th>
<th>${%Kind}</th>
<th>${%Description}</th>
<th class="jenkins-table__cell--tight" data-sort-disable="true"/>
</tr>
</thead>
<tbody>
<j:set var="creds" value="${it.credentials.values()}"/>

<j:if test="${it.description!=null}">
<div class="jenkins-page-description">
<j:out value="${app.markupFormatter.translate(it.description)}" />
</div>
</j:if>

<j:choose>
<j:when test="${creds.isEmpty()}">
<j:choose>
<j:when test="${creds.isEmpty()}">
<tr>
<td colspan="6" align="center">
<j:choose>
<j:when test="${it.store.hasPermission(it.parent.CREATE)}">
${%noCredentialsAddSome}
</j:when>
<j:otherwise>
${%noCredentials}
</j:otherwise>
</j:choose>
</td>
</tr>
<j:when test="${it.store.hasPermission(it.parent.CREATE)}">
<l:notice icon="symbol-credentials plugin-credentials" title="${%noCredentials}">
${%noCredentialsCallToAction}
</l:notice>
</j:when>
<j:otherwise>
<j:forEach var="c" items="${creds}">
<j:set var="safeDescription" value="${c.description==null?'':app.markupFormatter.translate(c.description)}"/>
<tr>
<td class="jenkins-table__cell--tight jenkins-table__icon">
<l:icon class="${c.iconClassName} icon-lg" alt="${c.typeName}" tooltip="${c.typeName}" />
</td>
<td>
<a href="credential/${c.urlName}" class='model-link inside jenkins-table__link'>${c.id}</a>
</td>
<td>
${c.displayName}
</td>
<td>
${c.typeName}
</td>
<td>
${safeDescription}
</td>
<td>
<j:if test="${h.hasPermission(c, it.parent.UPDATE)}">
<a href="credential/${c.urlName}/update" class="jenkins-table__link">
<l:icon alt="${%Update}"
tooltip="${%Update}"
class="icon-setting ${icons.toNormalizedIconSizeClass(iconSize)}"/>
</a>
</j:if>
</td>
</tr>
</j:forEach>
<l:notice icon="symbol-credentials plugin-credentials" title="${%noCredentials}"/>
</j:otherwise>
</j:choose>
</tbody>
</table>
<t:iconSize/>
</j:when>
<j:otherwise>
<div class="credentials-wrapper">
<j:forEach var="c" items="${creds}">
<j:set var="safeDescription"
value="${c.description==null?'':app.markupFormatter.translate(c.description)}"/>

<div class="credentials-card">
<div class="credentials-card__inner">
<div class="credentials-card__title">
<l:icon class="${c.iconClassName}" tooltip="${c.typeName}"/>
<a href="credential/${c.urlName}">${c.displayName}</a>
</div>

<div class="credentials-card__details">
<span>${c.id}</span>
<j:if test="${!empty(safeDescription)}">
<span>-</span>
<span>${safeDescription}</span>
</j:if>
</div>

</div>
<div class="credentials-card__controls">
<j:if test="${h.hasPermission(c, it.parent.UPDATE)}">
<a href="credential/${c.urlName}/update"
tooltip="${%Configure}"
class="jenkins-button jenkins-button--tertiary">
<l:icon src="symbol-settings"/>
</a>
<l:overflowButton clazz="jenkins-button--tertiary">
<dd:item icon="symbol-swap"
text="${%Move credential}"
href="credential/${c.urlName}/move" />
<dd:separator />
<dd:custom>
<l:confirmationLink class="jenkins-dropdown__item jenkins-!-destructive-color"
href="credential/${c.urlName}/doDelete"
title="${%delete.credential(c.displayName)}"
post="true"
destructive="true">
<div class="jenkins-dropdown__item__icon">
<l:icon src="symbol-trash" />
</div>
${%Delete credential}
</l:confirmationLink>
</dd:custom>
</l:overflowButton>
</j:if>
</div>
</div>
</j:forEach>
</div>
</j:otherwise>
</j:choose>
</l:main-panel>
</l:layout>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
noCredentials=This credential domain is empty.
noCredentialsAddSome=This credential domain is empty. How about <a href="./newCredentials">adding some credentials</a>?
noCredentials=This credentials domain is empty
noCredentialsCallToAction=How about <a href="newCredentials">adding some credentials</a>?
delete.domain=Are you sure you want to delete {0}?
delete.credential=Are you sure you want to delete {0}?
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
# THE SOFTWARE.
#
noCredentials=Diese Zugangsdaten-Dom\u00E4ne ist leer.
noCredentialsAddSome=Diese Zugangsdaten-Dom\u00E4ne ist leer. M\u00F6chten Sie <a href\="./newCredentials">Zugangsdaten hinzuf\u00FCgen</a>?
Name=Name
Kind=Art
Description=Beschreibung
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
# THE SOFTWARE.

noCredentials=Ce domaine d''identifiants est vide.
noCredentialsAddSome=Ce domaine d''identifiants est vide. Que diriez-vous <a href="./newCredentials">d''ajouter des identifiants</a> ?
Name=Nom
Kind=Type
Description=Description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,5 @@ Name=\u540d\u79f0
Kind=\u7a2e\u985e
Description=\u8aac\u660e
noCredentials=\u3053\u306e\u8a8d\u8a3c\u30c9\u30e1\u30a4\u30f3\u306f\u672a\u8a2d\u5b9a\u3067\u3059\u3002
noCredentialsAddSome=\
\u3053\u306e\u8a8d\u8a3c\u30c9\u30e1\u30a4\u30f3\u306f\u672a\u8a2d\u5b9a\u3067\u3059\u3002<a href="./newCredentials">\u8a8d\u8a3c\u60c5\u5831\u3092\u8ffd\u52a0</a>\u3057\u3066\u304f\u3060\u3055\u3044\u3002
Update=\u66f4\u65b0

Loading
Loading