diff --git a/pom.xml b/pom.xml index 903b4ae1..d2ea3556 100644 --- a/pom.xml +++ b/pom.xml @@ -68,8 +68,8 @@ 999999-SNAPSHOT jenkinsci/${project.artifactId}-plugin - 2.479 - ${jenkins.baseline}.1 + 2.504 + ${jenkins.baseline}.3 false 1372 @@ -92,7 +92,7 @@ io.jenkins.tools.bom bom-${jenkins.baseline}.x - 3482.vc10d4f6da_28a_ + 5804.v80587a_38d937 import pom @@ -121,6 +121,10 @@ org.jenkins-ci.plugins bouncycastle-api + + io.jenkins.plugins + ionicons-api + org.mockito diff --git a/src/main/java/com/cloudbees/plugins/credentials/CredentialsStore.java b/src/main/java/com/cloudbees/plugins/credentials/CredentialsStore.java index 92b445dc..7997817c 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/CredentialsStore.java +++ b/src/main/java/com/cloudbees/plugins/credentials/CredentialsStore.java @@ -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; @@ -529,6 +531,21 @@ public String getRelativeLinkTo(Domain domain) { 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 + */ + public final String getIconClassName() { + ModelObject context = getContext(); + if (context instanceof User user) { + return UserAvatarResolver.resolve(user, "96x96"); + } else { + return getProviderOrDie().getIconClassName(); + } + } + /** * 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 diff --git a/src/main/java/com/cloudbees/plugins/credentials/CredentialsStoreAction.java b/src/main/java/com/cloudbees/plugins/credentials/CredentialsStoreAction.java index 5cd54db4..2ab27181 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/CredentialsStoreAction.java +++ b/src/main/java/com/cloudbees/plugins/credentials/CredentialsStoreAction.java @@ -588,7 +588,7 @@ public boolean hasPermission(@NonNull Permission permission) { */ @ExportedBean public static class DomainWrapper extends AbstractDescribableImpl implements - ModelObjectWithContextMenu, ModelObjectWithChildren, AccessControlled { + ModelObjectWithContextMenu, ModelObjectWithChildren, AccessControlled, IconSpec { /** * The {@link CredentialsStoreAction} that we belong to. @@ -668,6 +668,13 @@ public String getDisplayName() { 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"; + } + /** * Return the full name. * diff --git a/src/main/java/com/cloudbees/plugins/credentials/ViewCredentialsAction.java b/src/main/java/com/cloudbees/plugins/credentials/ViewCredentialsAction.java index 6eb744b7..493d7657 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/ViewCredentialsAction.java +++ b/src/main/java/com/cloudbees/plugins/credentials/ViewCredentialsAction.java @@ -513,6 +513,11 @@ public String getId() { return credentials instanceof IdCredentials ? ((IdCredentials) credentials).getId() : null; } + @SuppressWarnings("unused") // jelly + public boolean isEditable() { + return store.hasPermission(CredentialsProvider.UPDATE); + } + /** * Returns the {@link Credentials#getScope()} of the {@link #credentials}. * diff --git a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.jelly b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.jelly index 9f8eed6d..cb2d6f83 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.jelly +++ b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.jelly @@ -23,98 +23,118 @@ ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ~ THE SOFTWARE. --> - + + + + - - - - ${%Add Credentials} - - + + + + + ${%Add Credentials} + + + - - ${%Configure domain} - - - - ${%Delete domain} + ${%Configure} + + + +
+ +
+ ${%Delete domain} +
+
+
-
- -
- - - - - - - - - - - - + +
+ +
+
+ + + - -
- - + + + ${%noCredentialsCallToAction} + - - - - - - - - - - - + - -
- ${%ID}${%Name}${%Kind}${%Description} -
- - - ${%noCredentialsAddSome} - - - ${%noCredentials} - - -
- - - ${c.id} - - ${c.displayName} - - ${c.typeName} - - ${safeDescription} - - - - - - -
- + + +
+ + + +
+
+ + +
+ ${c.id} + + - + ${safeDescription} + +
+ +
+
+ + + + + + + + + +
+ +
+ ${%Delete credential} +
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.properties b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.properties index ed675334..1c76ae07 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.properties +++ b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index.properties @@ -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 adding some credentials? +noCredentials=This credentials domain is empty +noCredentialsCallToAction=How about adding some credentials? +delete.domain=Are you sure you want to delete {0}? +delete.credential=Are you sure you want to delete {0}? diff --git a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_de.properties b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_de.properties index 73aeaaea..07ffaffc 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_de.properties +++ b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_de.properties @@ -22,7 +22,6 @@ # THE SOFTWARE. # noCredentials=Diese Zugangsdaten-Dom\u00E4ne ist leer. -noCredentialsAddSome=Diese Zugangsdaten-Dom\u00E4ne ist leer. M\u00F6chten Sie Zugangsdaten hinzuf\u00FCgen? Name=Name Kind=Art Description=Beschreibung diff --git a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_fr.properties b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_fr.properties index f5f06016..7e39b5ec 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_fr.properties +++ b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_fr.properties @@ -21,7 +21,6 @@ # THE SOFTWARE. noCredentials=Ce domaine d''identifiants est vide. -noCredentialsAddSome=Ce domaine d''identifiants est vide. Que diriez-vous d''ajouter des identifiants ? Name=Nom Kind=Type Description=Description diff --git a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_ja.properties b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_ja.properties index 73cb1bab..bd162efe 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_ja.properties +++ b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/DomainWrapper/index_ja.properties @@ -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\u8a8d\u8a3c\u60c5\u5831\u3092\u8ffd\u52a0\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Update=\u66f4\u65b0 diff --git a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/index.jelly b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/index.jelly index e35e5090..66652fc7 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/index.jelly +++ b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/index.jelly @@ -23,7 +23,7 @@ ~ THE SOFTWARE. --> - @@ -35,46 +35,63 @@ + + - + ${%Add domain} - - - - - - - - - - - - - - - - - - - - -
- ${%Domain}${%Description}
- - - ${d.displayName} - ${safeDescription}
- +
+ + +
+
+ +
+ ${%credentialsCount(size(d.credentials))} + + - + ${safeDescription} + +
+
+
+ + + + + + + +
+ +
+ ${%Delete domain} +
+
+
+
+
+
+
+
diff --git a/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/index.properties b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/index.properties new file mode 100644 index 00000000..46474787 --- /dev/null +++ b/src/main/resources/com/cloudbees/plugins/credentials/CredentialsStoreAction/index.properties @@ -0,0 +1,2 @@ +credentialsCount={0} {0,choice,0#credentials|1#credential|1 - - + xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:c="/lib/credentials" xmlns:dd="/lib/layout/dropdowns"> + + + + - + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + +
+ -
- - - - - - - -
${%T}${%P}${%Store}${%Domain}${%ID}${%Name}
- - - - - - ${d.store.context == app ? d.store.displayName : d.store.contextDisplayName} - - - - - - ${%global} - - - ${app.markupFormatter.translate(d.domain.name)} - - - - - - ${d.id} - - - ${d.name} -
+ +
+ +
+ + + + + + + + + + +
+ +
+ ${%Delete credential} +
+
+
+
+
+
+ + + +
+ -

${%storesScopedTo(it.contextFullDisplayName)}

- - - - - - - - - - - - - - - - - - - - - - -
${%P}${%Store}${%Domains}
- - - - ${store.context == app ? store.displayName : store.contextDisplayName} - - - - - - - ${%global} - - - ${app.markupFormatter.translate(domain.name)} - - - - - -
+ +
+ + + + + + + + +
+
+ -

${%Stores from parent}

- - - - - - - - - - - - - - - - - - - - - - - - -
${%P}${%Store}${%Domains}
- - - - ${store.context == app ? store.displayName : store.contextDisplayName} - - - - - - - ${%global} - - - ${app.markupFormatter.translate(domain.name)} - - - - - -
+ +
+ + + + + + + + +
+
- -
diff --git a/src/main/resources/com/cloudbees/plugins/credentials/ViewCredentialsAction/index.properties b/src/main/resources/com/cloudbees/plugins/credentials/ViewCredentialsAction/index.properties index cdda9a2e..93f26aaa 100644 --- a/src/main/resources/com/cloudbees/plugins/credentials/ViewCredentialsAction/index.properties +++ b/src/main/resources/com/cloudbees/plugins/credentials/ViewCredentialsAction/index.properties @@ -1,2 +1,3 @@ -global=(global) +global=Global storesScopedTo=Stores scoped to {0} +delete.credential=Are you sure you want to delete {0}? diff --git a/src/main/resources/com/cloudbees/plugins/credentials/common/card.css b/src/main/resources/com/cloudbees/plugins/credentials/common/card.css new file mode 100644 index 00000000..4fd9addc --- /dev/null +++ b/src/main/resources/com/cloudbees/plugins/credentials/common/card.css @@ -0,0 +1,100 @@ +.masked-credential { + opacity: 0.3; + + * { + color: var(--text-color-secondary) !important; + } +} + +.credentials-wrapper { + display: flex; + flex-direction: column; + background: var(--card-background); + border: var(--card-border-width) solid var(--card-border-color); + border-radius: var(--form-input-border-radius); + margin-bottom: var(--section-padding); +} + +.credentials-card { + position: relative; + flex-direction: row; + display: flex; + gap: 0.5rem; + border-bottom: var(--jenkins-border); + + &:last-of-type { + border-bottom: none; + } +} + +.credentials-card__controls { + display: flex; + gap: var(--jenkins-border-width); + align-items: center; + + .jenkins-button { + padding: 0; + aspect-ratio: 1; + } +} + +.credentials-card__inner { + padding: 0.75rem 1rem; +} + +.credentials-card__inner--split { + display: grid; + grid-template-columns: 350px 1fr; +} + +.credentials-card__details { + display: inline-flex; + align-items: center; + justify-content: start; + flex-wrap: wrap; + gap: 1ch; + padding-left: 2rem; + margin-top: 0.1875rem; + font-size: var(--font-size-xs); + color: var(--text-color-secondary); + word-break: break-all; + + a { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 1ch; + color: inherit; + + img, svg { + width: 0.875rem !important; + height: 0.875rem !important; + } + } +} + +.credentials-card__tags { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: var(--font-size-xs); + color: var(--text-color-secondary); +} + +.credentials-card__controls { + margin-left: auto; + padding-right: 0.75rem; +} + +.credentials-card__title { + display: flex; + gap: 0.75rem; + word-break: break-all; + + & > svg, img { + width: 1.25rem !important; + height: 1.25rem !important; + flex-shrink: 0; + margin-top: 0.0625rem; + } +} \ No newline at end of file diff --git a/src/main/resources/lib/credentials/store.jelly b/src/main/resources/lib/credentials/store.jelly new file mode 100644 index 00000000..8cf47bee --- /dev/null +++ b/src/main/resources/lib/credentials/store.jelly @@ -0,0 +1,57 @@ + + + + + + A reusable Store card. + + + + diff --git a/src/main/resources/lib/credentials/store.properties b/src/main/resources/lib/credentials/store.properties new file mode 100644 index 00000000..1e1ebcc6 --- /dev/null +++ b/src/main/resources/lib/credentials/store.properties @@ -0,0 +1 @@ +global=Global diff --git a/src/test/java/com/cloudbees/plugins/credentials/ViewCredentialsActionTest.java b/src/test/java/com/cloudbees/plugins/credentials/ViewCredentialsActionTest.java index c459f5a4..4e7e6c45 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/ViewCredentialsActionTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/ViewCredentialsActionTest.java @@ -2,8 +2,8 @@ import static com.cloudbees.plugins.credentials.XmlMatchers.isSimilarToIgnoringPrivateAttrs; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue;