From 61dfeaf113eaa3c8d475f2d90b2263f650f32eaa Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Thu, 5 Mar 2020 18:20:35 +0100 Subject: [PATCH] Manage Jenkins with flow layout and categories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Félix Queiruga --- core/src/main/java/hudson/AboutJenkins.java | 6 ++++ .../java/hudson/diagnosis/OldDataMonitor.java | 5 +++ .../lifecycle/WindowsInstallerLink.java | 8 +++++ .../java/hudson/model/ManagementLink.java | 35 +++++++++++++++++++ .../security/GlobalSecurityConfiguration.java | 6 ++++ .../security/HudsonPrivateSecurityRealm.java | 6 ++++ .../main/java/jenkins/management/CliLink.java | 6 ++++ .../jenkins/management/ConfigureLink.java | 6 ++++ .../java/jenkins/management/ConsoleLink.java | 8 +++++ .../java/jenkins/management/NodesLink.java | 7 +++- .../java/jenkins/management/PluginsLink.java | 8 +++++ .../java/jenkins/management/ReloadLink.java | 8 +++++ .../java/jenkins/management/ShutdownLink.java | 8 +++++ .../jenkins/management/StatisticsLink.java | 6 ++++ .../jenkins/management/SystemInfoLink.java | 8 +++-- .../jenkins/management/SystemLogLink.java | 8 +++++ core/src/main/java/jenkins/model/Jenkins.java | 23 ++++++++++++ .../tools/GlobalToolConfiguration.java | 7 ++++ .../hudson/model/Messages.properties | 8 +++++ .../jenkins/model/Jenkins/manage.jelly | 11 +++--- war/src/main/less/base-styles-v2.less | 2 ++ war/src/main/less/modules/manage-jenkins.less | 25 +++++++++++++ 22 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 war/src/main/less/modules/manage-jenkins.less diff --git a/core/src/main/java/hudson/AboutJenkins.java b/core/src/main/java/hudson/AboutJenkins.java index e1712e040a..bdfc6ac9b6 100644 --- a/core/src/main/java/hudson/AboutJenkins.java +++ b/core/src/main/java/hudson/AboutJenkins.java @@ -46,4 +46,10 @@ public class AboutJenkins extends ManagementLink { public Permission getRequiredPermission() { return Jenkins.SYSTEM_READ; } + + @Nonnull + @Override + public String getCategory() { + return Category.STATUS.toString(); + } } diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java index 856cbb0cec..92774fa405 100644 --- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java +++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java @@ -440,6 +440,11 @@ public class OldDataMonitor extends AdministrativeMonitor { @Extension @Symbol("oldData") public static class ManagementLinkImpl extends ManagementLink { + @Nonnull + @Override + public String getCategory() { + return Category.TROUBLESHOOTING.toString(); + } @Override public String getIconFileName() { return "document.png"; diff --git a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java index a8f412539a..b568744085 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java +++ b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java @@ -53,6 +53,7 @@ import org.apache.tools.ant.DefaultLogger; import org.apache.tools.ant.types.FileSet; import org.kohsuke.stapler.interceptor.RequirePOST; +import javax.annotation.Nonnull; import javax.servlet.ServletException; import java.io.File; import java.io.IOException; @@ -100,6 +101,13 @@ public class WindowsInstallerLink extends ManagementLink { return Messages.WindowsInstallerLink_Description(); } + + @Nonnull + @Override + public String getCategory() { + return Category.CONFIGURATION.toString(); + } + /** * Is the installation successful? */ diff --git a/core/src/main/java/hudson/model/ManagementLink.java b/core/src/main/java/hudson/model/ManagementLink.java index 777a368bf9..8da0477483 100644 --- a/core/src/main/java/hudson/model/ManagementLink.java +++ b/core/src/main/java/hudson/model/ManagementLink.java @@ -30,7 +30,9 @@ import hudson.ExtensionList; import hudson.security.Permission; import jenkins.model.Jenkins; +import java.lang.reflect.InvocationTargetException; import java.util.List; + import org.kohsuke.stapler.interceptor.RequirePOST; import javax.annotation.CheckForNull; @@ -131,4 +133,37 @@ public abstract class ManagementLink implements ExtensionPoint, Action { public boolean getRequiresPOST() { return false; } + + /** + * Category for management link, uses {@code String} so it can be done with core dependency pre-dating the version this feature was added. + * + * @return An enum value of {@link Category}. + * @since TODO + */ + public @Nonnull String getCategory() { + return Category.UNCATEGORIZED.toString(); + } + + /** + * Categories supported by this version of core. + * + * @since TODO + */ + public enum Category { + CONFIGURATION, + SECURITY, + STATUS, + TROUBLESHOOTING, + TOOLS, + MISC, + UNCATEGORIZED; + + public @Nonnull String getLabel() { + try { + return Messages.class.getMethod("ManagementLink_Category_" + this.toString()).invoke(null).toString(); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + return Messages.ManagementLink_Category_UNCATEGORIZED(); + } + } + } } diff --git a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java index 620e755f7e..0c854af083 100644 --- a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java +++ b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java @@ -41,6 +41,7 @@ import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; import javax.servlet.ServletException; import jenkins.model.GlobalConfigurationCategory; @@ -93,6 +94,11 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr return Jenkins.get().isDisableRememberMe(); } + @Nonnull + @Override + public String getCategory() { + return Category.SECURITY.toString(); + } @POST public synchronized void doConfigure(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException { // for compatibility reasons, the actual value is stored in Jenkins diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index bbfd617ae6..c518a6d9e4 100644 --- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -771,6 +771,12 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea public String getDescription() { return Messages.HudsonPrivateSecurityRealm_ManageUserLinks_Description(); } + + @Nonnull + @Override + public String getCategory() { + return Category.SECURITY.toString(); + } } /** diff --git a/core/src/main/java/jenkins/management/CliLink.java b/core/src/main/java/jenkins/management/CliLink.java index e51ba7ee03..5baae11b55 100644 --- a/core/src/main/java/jenkins/management/CliLink.java +++ b/core/src/main/java/jenkins/management/CliLink.java @@ -62,4 +62,10 @@ public class CliLink extends ManagementLink { public String getUrlName() { return "cli"; } + + @Nonnull + @Override + public String getCategory() { + return Category.TOOLS.toString(); + } } diff --git a/core/src/main/java/jenkins/management/ConfigureLink.java b/core/src/main/java/jenkins/management/ConfigureLink.java index 3b04dab5f5..ff6175c812 100644 --- a/core/src/main/java/jenkins/management/ConfigureLink.java +++ b/core/src/main/java/jenkins/management/ConfigureLink.java @@ -62,4 +62,10 @@ public class ConfigureLink extends ManagementLink { public String getUrlName() { return "configure"; } + + @Nonnull + @Override + public String getCategory() { + return Category.CONFIGURATION.toString(); + } } diff --git a/core/src/main/java/jenkins/management/ConsoleLink.java b/core/src/main/java/jenkins/management/ConsoleLink.java index c51a10dc1a..6d77ae7009 100644 --- a/core/src/main/java/jenkins/management/ConsoleLink.java +++ b/core/src/main/java/jenkins/management/ConsoleLink.java @@ -30,6 +30,8 @@ import hudson.security.Permission; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; +import javax.annotation.Nonnull; + /** * @author Nicolas De Loof */ @@ -59,4 +61,10 @@ public class ConsoleLink extends ManagementLink { public Permission getRequiredPermission() { return Jenkins.ADMINISTER; } + + @Nonnull + @Override + public String getCategory() { + return Category.TOOLS.toString(); + } } diff --git a/core/src/main/java/jenkins/management/NodesLink.java b/core/src/main/java/jenkins/management/NodesLink.java index 57e5ebc716..71642f52ff 100644 --- a/core/src/main/java/jenkins/management/NodesLink.java +++ b/core/src/main/java/jenkins/management/NodesLink.java @@ -27,7 +27,6 @@ package jenkins.management; import hudson.Extension; import hudson.model.ManagementLink; import hudson.security.Permission; -import jenkins.management.Messages; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; @@ -63,4 +62,10 @@ public class NodesLink extends ManagementLink { public String getUrlName() { return "computer"; } + + @Nonnull + @Override + public String getCategory() { + return Category.CONFIGURATION.toString(); + } } diff --git a/core/src/main/java/jenkins/management/PluginsLink.java b/core/src/main/java/jenkins/management/PluginsLink.java index cae9c139c4..54e9b9ae8f 100644 --- a/core/src/main/java/jenkins/management/PluginsLink.java +++ b/core/src/main/java/jenkins/management/PluginsLink.java @@ -28,6 +28,8 @@ import hudson.Extension; import hudson.model.ManagementLink; import org.jenkinsci.Symbol; +import javax.annotation.Nonnull; + /** * @author Nicolas De Loof */ @@ -52,4 +54,10 @@ public class PluginsLink extends ManagementLink { public String getUrlName() { return "pluginManager"; } + + @Nonnull + @Override + public String getCategory() { + return Category.CONFIGURATION.toString(); + } } diff --git a/core/src/main/java/jenkins/management/ReloadLink.java b/core/src/main/java/jenkins/management/ReloadLink.java index 59fabb8ba1..93afcd1147 100644 --- a/core/src/main/java/jenkins/management/ReloadLink.java +++ b/core/src/main/java/jenkins/management/ReloadLink.java @@ -28,6 +28,8 @@ import hudson.Extension; import hudson.model.ManagementLink; import org.jenkinsci.Symbol; +import javax.annotation.Nonnull; + /** * @author Nicolas De Loof */ @@ -61,4 +63,10 @@ public class ReloadLink extends ManagementLink { public boolean getRequiresPOST() { return true; } + + @Nonnull + @Override + public String getCategory() { + return Category.TOOLS.toString(); + } } diff --git a/core/src/main/java/jenkins/management/ShutdownLink.java b/core/src/main/java/jenkins/management/ShutdownLink.java index dde06e1290..6595cc132c 100644 --- a/core/src/main/java/jenkins/management/ShutdownLink.java +++ b/core/src/main/java/jenkins/management/ShutdownLink.java @@ -29,6 +29,8 @@ import hudson.model.ManagementLink; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; +import javax.annotation.Nonnull; + /** * @author Nicolas De Loof */ @@ -58,4 +60,10 @@ public class ShutdownLink extends ManagementLink { public boolean getRequiresPOST() { return true; } + + @Nonnull + @Override + public String getCategory() { + return Category.TOOLS.toString(); + } } diff --git a/core/src/main/java/jenkins/management/StatisticsLink.java b/core/src/main/java/jenkins/management/StatisticsLink.java index b0aa5c499c..87b45c4111 100644 --- a/core/src/main/java/jenkins/management/StatisticsLink.java +++ b/core/src/main/java/jenkins/management/StatisticsLink.java @@ -62,4 +62,10 @@ public class StatisticsLink extends ManagementLink { public String getUrlName() { return "load-statistics"; } + + @Nonnull + @Override + public String getCategory() { + return Category.STATUS.toString(); + } } diff --git a/core/src/main/java/jenkins/management/SystemInfoLink.java b/core/src/main/java/jenkins/management/SystemInfoLink.java index 5e2a55edf0..3e3ebca4b3 100644 --- a/core/src/main/java/jenkins/management/SystemInfoLink.java +++ b/core/src/main/java/jenkins/management/SystemInfoLink.java @@ -31,8 +31,6 @@ import javax.annotation.Nonnull; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; -import javax.annotation.CheckForNull; - /** * @author Nicolas De Loof */ @@ -63,4 +61,10 @@ public class SystemInfoLink extends ManagementLink { public String getUrlName() { return "systemInfo"; } + + @Nonnull + @Override + public String getCategory() { + return Category.STATUS.toString(); + } } diff --git a/core/src/main/java/jenkins/management/SystemLogLink.java b/core/src/main/java/jenkins/management/SystemLogLink.java index 1ee08aca00..6c47df1db7 100644 --- a/core/src/main/java/jenkins/management/SystemLogLink.java +++ b/core/src/main/java/jenkins/management/SystemLogLink.java @@ -28,6 +28,8 @@ import hudson.Extension; import hudson.model.ManagementLink; import org.jenkinsci.Symbol; +import javax.annotation.Nonnull; + /** * @author Nicolas De Loof */ @@ -52,4 +54,10 @@ public class SystemLogLink extends ManagementLink { public String getUrlName() { return "log"; } + + @Nonnull + @Override + public String getCategory() { + return Category.STATUS.toString(); + } } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index be29e6f85b..4d37a5540f 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -4705,6 +4705,29 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve public List getManagementLinks() { return ManagementLink.all(); } + + // for Jelly + @Restricted(NoExternalUse.class) + public Map> getCategorizedManagementLinks() { + Map> byCategory = new TreeMap<>(); + for (ManagementLink link : ManagementLink.all()) { + if (link.getIconFileName() == null) { + continue; + } + if (!Jenkins.get().hasPermission(link.getRequiredPermission())) { + continue; + } + String categoryName = link.getCategory(); + ManagementLink.Category category; + try { + category = ManagementLink.Category.valueOf(categoryName); + } catch (IllegalArgumentException ex) { + category = ManagementLink.Category.UNCATEGORIZED; + } + byCategory.computeIfAbsent(category, c -> new ArrayList<>()).add(link); + } + return byCategory; + } /** * If set, a currently active setup wizard - e.g. installation diff --git a/core/src/main/java/jenkins/tools/GlobalToolConfiguration.java b/core/src/main/java/jenkins/tools/GlobalToolConfiguration.java index dbabbabdab..c7e689dd80 100644 --- a/core/src/main/java/jenkins/tools/GlobalToolConfiguration.java +++ b/core/src/main/java/jenkins/tools/GlobalToolConfiguration.java @@ -39,6 +39,7 @@ import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.verb.POST; +import javax.annotation.Nonnull; import javax.servlet.ServletException; import java.io.IOException; import java.util.logging.Level; @@ -73,6 +74,12 @@ public class GlobalToolConfiguration extends ManagementLink { return Jenkins.ADMINISTER; } + @Nonnull + @Override + public String getCategory() { + return Category.CONFIGURATION.toString(); + } + @POST public synchronized void doConfigure(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException { boolean result = configure(req, req.getSubmittedForm()); diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties index 2f8f88c54b..fe9925d6ac 100644 --- a/core/src/main/resources/hudson/model/Messages.properties +++ b/core/src/main/resources/hudson/model/Messages.properties @@ -404,3 +404,11 @@ User.IllegalFullname="{0}" is prohibited as a full name for security reasons. TimeZoneProperty.DisplayName=User Defined Time Zone TimeZoneProperty.DisplayDefaultTimeZone=Default + +ManagementLink.Category.CONFIGURATION=System Configuration +ManagementLink.Category.SECURITY=Security +ManagementLink.Category.STATUS=Status Information +ManagementLink.Category.TROUBLESHOOTING=Troubleshooting +ManagementLink.Category.TOOLS=Tools and Actions +ManagementLink.Category.MISC=Other +ManagementLink.Category.UNCATEGORIZED=Uncategorized diff --git a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly index ea2b6cb53e..1573316b43 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly @@ -47,11 +47,13 @@ THE SOFTWARE. - - + +

${category.key.label}

+
+ -
+
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(m.urlName, iconUrl, m.displayName, m.requiresPOST, m.requiresConfirmation) : null} @@ -89,7 +91,8 @@ THE SOFTWARE.
- + +
diff --git a/war/src/main/less/base-styles-v2.less b/war/src/main/less/base-styles-v2.less index 9c21f535a1..9343dfa3c0 100644 --- a/war/src/main/less/base-styles-v2.less +++ b/war/src/main/less/base-styles-v2.less @@ -15,3 +15,5 @@ html { // Keep page header code in this bundle to avoid loading multiple files // that are always needed @import './modules/page-header.less'; + +@import './modules/manage-jenkins.less'; diff --git a/war/src/main/less/modules/manage-jenkins.less b/war/src/main/less/modules/manage-jenkins.less new file mode 100644 index 0000000000..16e7303b3e --- /dev/null +++ b/war/src/main/less/modules/manage-jenkins.less @@ -0,0 +1,25 @@ +.manage-page__row { + display: block; + display: flex; + flex-wrap: wrap; +} +.manage-page__column { + width: 100%; + // TODO: set padding + // TODO: set margin bottom +} +@media screen and (min-width: 992px) { + .manage-page__column { + width: 50%; + } +} +@media screen and (min-width: 1200px) { + .manage-page__column { + width: 33.333%; + } +} +@media screen and (min-width: 1800px) { + .manage-page__column { + width: 25%; + } +}