diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java index c6f7416c6e..b389221f76 100644 --- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java +++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java @@ -57,6 +57,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.management.Badge; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; @@ -471,5 +472,14 @@ public class OldDataMonitor extends AdministrativeMonitor { public String getDisplayName() { return Messages.OldDataMonitor_DisplayName(); } + + @Override + public Badge getBadge() { + int size = get(Jenkins.get()).data.size(); + if (size > 0) { + return new Badge(Integer.toString(size), Messages.OldDataMonitor_OldDataTooltip(), Badge.Severity.WARNING); + } + return null; + } } } diff --git a/core/src/main/java/hudson/model/AdministrativeMonitor.java b/core/src/main/java/hudson/model/AdministrativeMonitor.java index 3c70207ff8..7a9a0dde97 100644 --- a/core/src/main/java/hudson/model/AdministrativeMonitor.java +++ b/core/src/main/java/hudson/model/AdministrativeMonitor.java @@ -70,7 +70,9 @@ import org.kohsuke.stapler.interceptor.RequirePOST; * {@code http://SERVER/jenkins/manage} page. This view should typically render * a DIV box with class='alert alert-danger' or class='alert alert-warning' with a human-readable text * inside it. It often also contains a link to a page that provides more details - * about the problem. + * about the problem.
+ * Additionally 2 numbers are shown in the Jenkins header of administrators, one with the number or active + * non-security relevant monitors and one with the number of active security relevant monitors. * * * diff --git a/core/src/main/java/hudson/model/ManagementLink.java b/core/src/main/java/hudson/model/ManagementLink.java index 666c2fd125..86bfae783e 100644 --- a/core/src/main/java/hudson/model/ManagementLink.java +++ b/core/src/main/java/hudson/model/ManagementLink.java @@ -34,6 +34,7 @@ import hudson.security.Permission; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.management.Badge; import jenkins.model.Jenkins; import org.jvnet.localizer.Localizable; import org.kohsuke.accmod.Restricted; @@ -214,5 +215,15 @@ public abstract class ManagementLink implements ExtensionPoint, Action { } } + /** + * A {@link Badge} shown as overlay over the icon on "Manage Jenkins". + * + * @return badge or {@code null} if no badge should be shown. + * @since TODO + */ + public @CheckForNull Badge getBadge() { + return null; + } + private static final Logger LOGGER = Logger.getLogger(ManagementLink.class.getName()); } diff --git a/core/src/main/java/jenkins/management/Badge.java b/core/src/main/java/jenkins/management/Badge.java new file mode 100644 index 0000000000..ebae6f96e8 --- /dev/null +++ b/core/src/main/java/jenkins/management/Badge.java @@ -0,0 +1,104 @@ +/* + * The MIT License + * + * Copyright (c) 2022, Markus Winter + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package jenkins.management; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.diagnosis.OldDataMonitor; +import hudson.model.AdministrativeMonitor; +import hudson.model.ManagementLink; +import java.util.Locale; + +/** + * Definition of a badge that can be returned by a {@link ManagementLink} implementation. + * The badge is shown as a small overlay over the corresponding icon on the {@code Manage Jenkins} page, + * it can display additional information in a tooltip and change it's color depending on the severity. + * + *

+ * A badge mainly serves as a fast feedback for the corresponding management page. + * It could be used to just display some short status information or hint that some action can be taken. + * For example the badge on {@code Manage Plugins} shows information about the number of available updates + * and in its tooltip additionally how many updates contain incompatible changes or fix + * security vulnerabilities. It also changes its color when there are security fixes available. + * + *

+ * A badge might display the same information as an {@link AdministrativeMonitor}. While an {@link AdministrativeMonitor} + * can be disabled, a badge will always be shown. E.g. the badge of {@link OldDataMonitor.ManagementLinkImpl} always shows the number of old data entries. + * + * @since TODO + */ +public class Badge { + + private final String text; + private final String tooltip; + private final Severity severity; + + /** + * Create a new Badge + * + * @param text The text to be shown in the badge. + * Keep it short, ideally just a number. More than 6 or 7 characters do not look good. Avoid spaces as they will lead to line breaks. + * as this might lead to line breaks. + * @param tooltip The tooltip to show for the badge. + * Do not include html tags. + * @param severity The severity of the badge (danger, warning, info) + */ + public Badge(@NonNull String text, @NonNull String tooltip, @NonNull Severity severity) { + this.text = text; + this.tooltip = tooltip; + this.severity = severity; + } + + /** + * The text to be shown in the badge. + * + * @return badge text + */ + public String getText() { + return text; + } + + /** + * The tooltip of the badge. + * + * @return tooltip + */ + public String getTooltip() { + return tooltip; + } + + /** + * The severity of the badge. + * Influences the background color of the badge. + * + * @return severity as String + */ + public String getSeverity() { + return severity.toString().toLowerCase(Locale.US); + } + + public enum Severity { + DANGER, WARNING, INFO + } +} diff --git a/core/src/main/java/jenkins/management/PluginsLink.java b/core/src/main/java/jenkins/management/PluginsLink.java index 40fbd756e0..f935792752 100644 --- a/core/src/main/java/jenkins/management/PluginsLink.java +++ b/core/src/main/java/jenkins/management/PluginsLink.java @@ -28,11 +28,11 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.model.ManagementLink; import hudson.model.UpdateCenter; +import hudson.model.UpdateSite.Plugin; import hudson.security.Permission; +import java.util.List; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; /** * @author Nicolas De Loof @@ -72,13 +72,49 @@ public class PluginsLink extends ManagementLink { return Category.CONFIGURATION; } - @Restricted(NoExternalUse.class) - public int getUpdateCount() { + @Override + public Badge getBadge() { final UpdateCenter updateCenter = Jenkins.get().getUpdateCenter(); if (!updateCenter.isSiteDataReady()) { // Do not display message during this page load, but possibly later. - return 0; + return null; } - return updateCenter.getUpdates().size(); + List plugins = updateCenter.getUpdates(); + int size = plugins.size(); + if (size > 0) { + StringBuilder tooltip = new StringBuilder(); + Badge.Severity severity = Badge.Severity.WARNING; + int securityFixSize = (int) plugins.stream().filter(plugin -> plugin.fixesSecurityVulnerabilities()).count(); + int incompatibleSize = (int) plugins.stream().filter(plugin -> !plugin.isCompatibleWithInstalledVersion()).count(); + if (size > 1) { + tooltip.append(Messages.PluginsLink_updatesAvailable(size)); + } else { + tooltip.append(Messages.PluginsLink_updateAvailable()); + } + switch (incompatibleSize) { + case 0: + break; + case 1: + tooltip.append("\n").append(Messages.PluginsLink_incompatibleUpdateAvailable()); + break; + default: + tooltip.append("\n").append(Messages.PluginsLink_incompatibleUpdatesAvailable(incompatibleSize)); + break; + } + switch (securityFixSize) { + case 0: + break; + case 1: + tooltip.append("\n").append(Messages.PluginsLink_securityUpdateAvailable()); + severity = Badge.Severity.DANGER; + break; + default: + tooltip.append("\n").append(Messages.PluginsLink_securityUpdatesAvailable(securityFixSize)); + severity = Badge.Severity.DANGER; + break; + } + return new Badge(Integer.toString(size), tooltip.toString(), severity); + } + return null; } } diff --git a/core/src/main/resources/hudson/diagnosis/Messages.properties b/core/src/main/resources/hudson/diagnosis/Messages.properties index 11ff96dd7c..9ef785add8 100644 --- a/core/src/main/resources/hudson/diagnosis/Messages.properties +++ b/core/src/main/resources/hudson/diagnosis/Messages.properties @@ -2,6 +2,7 @@ MemoryUsageMonitor.USED=Used MemoryUsageMonitor.TOTAL=Total OldDataMonitor.Description=Scrub configuration files to remove remnants from old plugins and earlier versions. OldDataMonitor.DisplayName=Manage Old Data +OldDataMonitor.OldDataTooltip=Old data exists HudsonHomeDiskUsageMonitor.DisplayName=Disk Usage Monitor NullIdDescriptorMonitor.DisplayName=Missing Descriptor ID diff --git a/core/src/main/resources/hudson/diagnosis/Messages_de.properties b/core/src/main/resources/hudson/diagnosis/Messages_de.properties index 8e7ea3d41e..26cb82440b 100644 --- a/core/src/main/resources/hudson/diagnosis/Messages_de.properties +++ b/core/src/main/resources/hudson/diagnosis/Messages_de.properties @@ -24,6 +24,7 @@ MemoryUsageMonitor.USED=Verwendet MemoryUsageMonitor.TOTAL=Total OldDataMonitor.DisplayName=Veraltete Daten verwalten OldDataMonitor.Description=Konfiguration bereinigen, um Überbleibsel alter Plugins und früherer Versionen zu entfernen. +OldDataMonitor.OldDataTooltip=Es existieren veraltete Daten HudsonHomeDiskUsageMonitor.DisplayName=Speicherplatz-Monitor NullIdDescriptorMonitor.DisplayName=Fehlende Descriptor-ID ReverseProxySetupMonitor.DisplayName=Reverse-Proxy-Konfiguration diff --git a/core/src/main/resources/hudson/model/ManageJenkinsAction/index.jelly b/core/src/main/resources/hudson/model/ManageJenkinsAction/index.jelly index 74ebf219de..27be7d16d2 100644 --- a/core/src/main/resources/hudson/model/ManageJenkinsAction/index.jelly +++ b/core/src/main/resources/hudson/model/ManageJenkinsAction/index.jelly @@ -89,9 +89,10 @@ THE SOFTWARE.