mirror of https://github.com/jenkinsci/jenkins.git
				
				
				
			Redesign the Jenkins header (#10245)
* WB * Push * More responsive * Update headerContent.jelly * Push * Update logo.jelly * Update _page-header.scss * Update _page-header.scss * Update header * Tidy up breadcrumbs * Tidy up focus * Update _breadcrumbs.scss * Update configure.jelly * push * Add badges * Tidy * Push * Update headerContent.jelly * Update ManageJenkinsAction.java * Update headerContent.jelly * Working! * WB * Tidy up * Fixes * Update sidepanel.jelly * Lint * Tidy up * Update ManageJenkinsAction.java * Simplify * Update _side-panel-tasks.scss * Update _side-panel-tasks.scss * Update UserAction.java * Update Jenkins.java * Add border to account image * Tidy up avatars * Update _side-panel-tasks.scss * Init * Tidy up * Hide behind flag * Update sidepanel.jelly * Push * Tidy up * Update logo.jelly * Accessibility * Update _breadcrumbs-new.scss * Fix dropdown theme * Update _breadcrumbs.scss * Update _header.scss * Update ManageJenkinsAction.java * Remove flag * Tidy up * Update with HeaderAction * Revert "Update with HeaderAction" This reverts commit2ea0b1f867. * Tidy * Update RootAction.java * Update _breadcrumbs.scss * Push * Update _header.scss * Update _header.scss * Fix invisible actions not actually being invisible, make avatar huge to please Tim * Tidy * Push * Fix breadcrumbs + notification * Update jumplist.jelly * Getting there 🚀 * WB * Update index.jelly * Update headerContent.jelly * Responsive * Push * Push * Push * Update index.js * Push * Tidy up * Tidy up * Tidy * Update logo.jelly * Delete NewHeaderUserExperimentalFlag.java * Lint * Update index.js * Update index.js * Fix some tests * Update headerContent.jelly * Update headerContent.jelly * Remove bravo test - need to confirm this * Update Security3349Test.java * Update pom.xml * Fix SpotBugs + i18n * Add doc for header scroll, support prefers contrast * Add overflow menu for actions, improve accessibility * Update actions-overflow.js * Fix tests + accessibility * Fix JS * Update breadcrumbs-overflow.js * Update breadcrumbs-overflow.js * Add breadcrumb menu on hover, fix issues on mobile * Update _breadcrumbs.scss * i18n * Update pom.xml * Add tab support for user dropdown * Squashed commit of the following: commit847981ebcbMerge:0ea6dcff0e23f2b9ef59Author: Kris Stern <krisstern@outlook.com> Date: Wed Feb 26 09:16:52 2025 +0800 Merge branch 'master' into add-groups-to-command-palette commit0ea6dcff0eMerge:c0777dbe7968425e2cd4Author: Kris Stern <krisstern@outlook.com> Date: Wed Feb 26 01:06:33 2025 +0800 Merge branch 'master' into add-groups-to-command-palette commitc0777dbe79Merge:1638afe17ec37293c52dAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 24 13:40:35 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit1638afe17eMerge:c987a9e536b97764d3fdAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri Feb 21 11:49:03 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commitc987a9e536Merge:f909eec0d416748f4413Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Thu Feb 20 08:17:19 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commitf909eec0d4Merge:85eedb7e88217b0f5742Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Feb 19 16:12:45 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit85eedb7e88Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Feb 19 16:11:24 2025 +0000 Move to Item commit8f4f117bacAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Feb 19 15:43:50 2025 +0000 Tighten up animations + improve contrast commitd7b7d6388dMerge:8750f7cb924fa61274f9Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Feb 19 08:50:45 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit8750f7cb92Merge:7b527340a2a05c33f797Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Feb 18 21:42:31 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit7b527340a2Merge:e2c133d1283505fb3540Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 17 20:59:02 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commite2c133d128Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sun Feb 16 18:23:14 2025 +0000 Update require-changelog-label.yml commitd32a61c1eaAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sun Feb 16 17:11:18 2025 +0000 Update _theme.scss commit42ecfcac5cAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sun Feb 16 17:10:48 2025 +0000 Rename to Items commitcc3779171aMerge:0f1cb2187c2b9d4d62a6Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sun Feb 16 17:10:25 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit0f1cb2187cMerge:04dc6cd2229474c89bf1Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Feb 12 20:42:01 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit04dc6cd222Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Feb 11 17:06:31 2025 +0000 Reduce spacing a touch, fix icon spacing commit0ab3665587Merge:7c9e172b2f848ac9b66aAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Feb 11 14:44:03 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit7c9e172b2fAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Feb 11 11:23:27 2025 +0000 Update Messages.properties commitec6a5e5ee0Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Feb 11 08:51:16 2025 +0000 Fix test commit14a64885a2Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 10 16:44:39 2025 +0000 Tidy up commit46a9e5681aAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 10 16:28:15 2025 +0000 Tidy commitd7270b1fa4Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 10 16:06:04 2025 +0000 Tidy commitb2da3f8d39Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 10 16:03:37 2025 +0000 Tidy up commitb746fba008Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 10 15:53:53 2025 +0000 Move to extensionpoint commit7827304ae1Merge:cac127d119d03a2e11c9Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Feb 10 15:45:28 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commitcac127d119Merge:add75bf6a9e3e3c45270Author: Tim Jacomb <21194782+timja@users.noreply.github.com> Date: Mon Jan 13 11:03:10 2025 +0000 Merge branch 'jenkinsci:master' into add-groups-to-command-palette commitadd75bf6a9Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat Jan 11 19:02:52 2025 +0000 Update _command-palette.scss commiteb4073f4fbAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat Jan 11 18:59:37 2025 +0000 Tidy up commit323e48fddfAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat Jan 11 18:47:34 2025 +0000 Update Job.java commit3cbdfbc4b5Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat Jan 11 18:47:13 2025 +0000 Update _command-palette.scss commit8fecf0d880Merge:428e826fcd331c7685caAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat Jan 11 18:45:30 2025 +0000 Merge branch 'master' into add-groups-to-command-palette commit428e826fcdMerge:5657369d95f1b6d31272Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon Dec 16 20:53:15 2024 +0000 Merge branch 'master' into add-groups-to-command-palette commit5657369d95Merge:26f17a277f674d5085c3Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri Dec 13 09:46:35 2024 +0000 Merge branch 'add-icons-to-command-palette' into add-groups-to-command-palette commit674d5085c3Merge:809d2e61207020e80af8Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri Dec 13 09:42:59 2024 +0000 Merge branch 'master' into add-icons-to-command-palette commit26f17a277fAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 22:10:56 2024 +0000 Update _command-palette.scss commit2b6ffc85f3Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 22:09:09 2024 +0000 Init commit809d2e6120Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 21:37:47 2024 +0000 Make iconXml private, rename to icon commit3d45ca7c39Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 21:29:27 2024 +0000 Add group field commit80f24cbfdcAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 21:25:23 2024 +0000 Init commit1b9faa8fb0Merge:d6868c970a26738449cdAuthor: Tim Jacomb <timjacomb1@gmail.com> Date: Wed Dec 11 21:11:56 2024 +0000 Merge branch 'add-icons-to-command-palette' of github.com:janfaracik/jenkins into add-icons-to-command-palette commitd6868c970aAuthor: Tim Jacomb <timjacomb1@gmail.com> Date: Wed Dec 11 21:11:41 2024 +0000 Reword javadoc commit26738449cdAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 21:03:29 2024 +0000 Implement IconSpec in IComputer commit57910109f3Merge:661f99478305ed7560fdAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 20:23:58 2024 +0000 Merge branch 'master' into add-icons-to-command-palette commit661f994783Merge:23570203eadad5ef3266Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 20:15:22 2024 +0000 Merge branch 'refine-command-palette' into add-icons-to-command-palette commit23570203eaMerge:436a02b9d3788ae63c50Author: Tim Jacomb <timjacomb1@gmail.com> Date: Wed Dec 11 16:35:43 2024 +0000 Merge branch 'add-icons-to-command-palette' of github.com:janfaracik/jenkins into add-icons-to-command-palette commit436a02b9d3Author: Tim Jacomb <timjacomb1@gmail.com> Date: Wed Dec 11 16:35:24 2024 +0000 Add support for images commita3fdb3e0c7Merge:ea67d6a554d22cc2fa3cAuthor: Tim Jacomb <timjacomb1@gmail.com> Date: Wed Dec 11 15:27:12 2024 +0000 Merge branch 'master' into add-icons-to-command-palette commit788ae63c50Merge:ea67d6a554d22cc2fa3cAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed Dec 11 10:00:04 2024 +0000 Merge branch 'jenkinsci:master' into add-icons-to-command-palette commitdad5ef3266Merge:cc63c9c8e5d22cc2fa3cAuthor: Tim Jacomb <21194782+timja@users.noreply.github.com> Date: Wed Dec 11 09:07:05 2024 +0000 Merge branch 'master' into refine-command-palette commitea67d6a554Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Dec 10 22:26:16 2024 +0000 Update Search.java commita9aadbab30Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Dec 10 22:25:40 2024 +0000 Revert "Update Search.java" This reverts commit24837ea667. commit24837ea667Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Dec 10 21:59:03 2024 +0000 Update Search.java commitd43a8d3b2fAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Dec 10 21:54:23 2024 +0000 Init commitcc63c9c8e5Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Tue Dec 10 21:37:09 2024 +0000 Refine command palette * Move logo * Revert "Move logo" This reverts commit25647d6a04. * Move actions to taglib * Split logo from breadcrumbs * Fix sticky app bar * Update _page-header.scss * Update HudsonTest.java * Update _page-header.scss * Move breadcrumb loading above setting mode to header * Reduce header height * Increase logo height * Move getActions to Header * Update header avatar with jenkins-avatar * Squashed commit of the following: commit5060044fcdMerge:0ea3e49fa12fb523ffe3Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat Mar 22 17:56:53 2025 +0000 Merge branch 'master' into improve-tooltips-dropdowns commit0ea3e49fa1Merge:3dd0b9f421a1f9d3e7e2Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Sat Mar 22 12:24:49 2025 +0000 Merge branch 'master' into improve-tooltips-dropdowns commit3dd0b9f421Merge:7f5f814aa573185b257dAuthor: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri Mar 21 09:57:52 2025 +0000 Merge branch 'master' into improve-tooltips-dropdowns commit7f5f814aa5Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Thu Mar 20 10:26:32 2025 +0000 Update _dropdowns.scss commite9eee3c0a4Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Thu Mar 20 10:25:35 2025 +0000 Update _theme.scss commitce11fd1fb3Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Thu Mar 20 10:22:07 2025 +0000 Init * Delete idea files * Update core/src/main/java/jenkins/views/Header.java Co-authored-by: Markus Winter <m.winter@sap.com> * Sort actions manually in header * Update markup and CSS * Handle that dodgy SVG messing up the label * Fix new computer missing sidepanel * Update core/src/main/resources/lib/layout/header/actions.jelly Co-authored-by: Markus Winter <m.winter@sap.com> * Update core/src/main/java/jenkins/views/Header.java Co-authored-by: Markus Winter <m.winter@sap.com> * Update core/src/main/resources/lib/layout/header/actions.jelly Co-authored-by: Markus Winter <m.winter@sap.com> * Update Header.java --------- Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com> Co-authored-by: Kris Stern <krisstern@outlook.com> Co-authored-by: Markus Winter <m.winter@sap.com>
This commit is contained in:
		
							parent
							
								
									ee56e8ffbf
								
							
						
					
					
						commit
						c42ab43c49
					
				|  | @ -27,6 +27,10 @@ package hudson.model; | |||
| import hudson.Extension; | ||||
| import hudson.Util; | ||||
| import java.io.IOException; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.Optional; | ||||
| import jenkins.management.AdministrativeMonitorsDecorator; | ||||
| import jenkins.management.Badge; | ||||
| import jenkins.model.Jenkins; | ||||
| import jenkins.model.ModelObjectWithContextMenu; | ||||
|  | @ -40,11 +44,11 @@ import org.kohsuke.stapler.StaplerRequest2; | |||
| import org.kohsuke.stapler.StaplerResponse2; | ||||
| 
 | ||||
| /** | ||||
|  * Adds the "Manage Jenkins" link to the top page. | ||||
|  * Adds the "Manage Jenkins" link to the navigation bar. | ||||
|  * | ||||
|  * @author Kohsuke Kawaguchi | ||||
|  */ | ||||
| @Extension(ordinal = 100) @Symbol("manageJenkins") | ||||
| @Extension(ordinal = 998) @Symbol("manageJenkins") | ||||
| public class ManageJenkinsAction implements RootAction, StaplerFallback, ModelObjectWithContextMenu { | ||||
|     @Override | ||||
|     public String getIconFileName() { | ||||
|  | @ -88,4 +92,29 @@ public class ManageJenkinsAction implements RootAction, StaplerFallback, ModelOb | |||
|         // If neither is the case, rewrite the relative URL to point to inside the /manage/ URL space | ||||
|         menu.add("manage/" + url, icon, iconXml, text, post, requiresConfirmation, badge, message); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Badge getBadge() { | ||||
|         Jenkins jenkins = Jenkins.get(); | ||||
|         AdministrativeMonitorsDecorator decorator = jenkins.getExtensionList(PageDecorator.class) | ||||
|                 .get(AdministrativeMonitorsDecorator.class); | ||||
| 
 | ||||
|         if (decorator == null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         Collection<AdministrativeMonitor> activeAdministrativeMonitors = Optional.ofNullable(decorator.getMonitorsToDisplay()).orElse(Collections.emptyList()); | ||||
|         boolean anySecurity = activeAdministrativeMonitors.stream().anyMatch(AdministrativeMonitor::isSecurity); | ||||
| 
 | ||||
|         if (activeAdministrativeMonitors.isEmpty()) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         int size = activeAdministrativeMonitors.size(); | ||||
|         String tooltip = size > 1 ? Messages.ManageJenkinsAction_notifications(size) : Messages.ManageJenkinsAction_notification(size); | ||||
| 
 | ||||
|         return new Badge(String.valueOf(size), | ||||
|                 tooltip, | ||||
|                 anySecurity ? Badge.Severity.DANGER : Badge.Severity.WARNING); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -321,7 +321,7 @@ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup | |||
|         return Jenkins.get().getMyViewsTabBar(); | ||||
|     } | ||||
| 
 | ||||
|     @Extension @Symbol("myView") | ||||
|     @Symbol("myView") | ||||
|     public static class GlobalAction implements RootAction { | ||||
| 
 | ||||
|         @Override | ||||
|  |  | |||
|  | @ -24,8 +24,10 @@ | |||
| 
 | ||||
| package hudson.model; | ||||
| 
 | ||||
| import edu.umd.cs.findbugs.annotations.CheckForNull; | ||||
| import hudson.Extension; | ||||
| import hudson.ExtensionPoint; | ||||
| import jenkins.management.Badge; | ||||
| 
 | ||||
| /** | ||||
|  * Marker interface for actions that are added to {@link jenkins.model.Jenkins}. | ||||
|  | @ -38,4 +40,14 @@ import hudson.ExtensionPoint; | |||
|  * @since 1.311 | ||||
|  */ | ||||
| public interface RootAction extends Action, ExtensionPoint { | ||||
| 
 | ||||
|     /** | ||||
|      * A {@link Badge} shown on the button for the action. | ||||
|      * | ||||
|      * @return badge or {@code null} if no badge should be shown. | ||||
|      * @since TODO | ||||
|      */ | ||||
|     default @CheckForNull Badge getBadge() { | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -28,7 +28,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; | |||
| import hudson.Extension; | ||||
| import hudson.diagnosis.ReverseProxySetupMonitor; | ||||
| import hudson.model.AdministrativeMonitor; | ||||
| import hudson.model.ManageJenkinsAction; | ||||
| import hudson.model.PageDecorator; | ||||
| import hudson.util.HudsonIsLoading; | ||||
| import hudson.util.HudsonIsRestarting; | ||||
|  | @ -56,9 +55,6 @@ public class AdministrativeMonitorsDecorator extends PageDecorator { | |||
|     public AdministrativeMonitorsDecorator() { | ||||
|         // otherwise this would be added to every internal context menu building request | ||||
|         ignoredJenkinsRestOfUrls.add("contextMenu"); | ||||
| 
 | ||||
|         // don't show here to allow admins to disable malfunctioning monitors via AdministrativeMonitorsDecorator | ||||
|         ignoredJenkinsRestOfUrls.add("configure"); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|  | @ -163,11 +159,6 @@ public class AdministrativeMonitorsDecorator extends PageDecorator { | |||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         // Don't show on Manage Jenkins | ||||
|         if (o instanceof ManageJenkinsAction) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         // don't show for some URLs served directly by Jenkins | ||||
|         if (o instanceof Jenkins) { | ||||
|             String url = a.getRestOfUrl(); | ||||
|  |  | |||
|  | @ -2369,7 +2369,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve | |||
|     public SearchIndexBuilder makeSearchIndex() { | ||||
|         SearchIndexBuilder builder = super.makeSearchIndex(); | ||||
| 
 | ||||
|         this.actions.stream().filter(e -> e.getIconFileName() != null).forEach(action -> builder.add(new SearchItem() { | ||||
|         this.actions.stream().filter(e -> !(e.getIconFileName() == null || e.getUrlName() == null)).forEach(action -> builder.add(new SearchItem() { | ||||
|             @Override | ||||
|             public String getSearchName() { | ||||
|                 return action.getDisplayName(); | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /* | ||||
|  * The MIT License | ||||
|  * | ||||
|  * Copyright (c) 2011, CloudBees, Inc. | ||||
|  * Copyright (c) 2025, Jan Faracik | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  | @ -22,31 +22,29 @@ | |||
|  * THE SOFTWARE. | ||||
|  */ | ||||
| 
 | ||||
| package lib.hudson; | ||||
| package jenkins.model.navigation; | ||||
| 
 | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| 
 | ||||
| import hudson.model.InvisibleAction; | ||||
| import hudson.Extension; | ||||
| import hudson.model.RootAction; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.jvnet.hudson.test.JenkinsRule; | ||||
| import org.jvnet.hudson.test.TestExtension; | ||||
| 
 | ||||
| /** | ||||
|  * @author Kohsuke Kawaguchi | ||||
|  * TODO | ||||
|  */ | ||||
| public class ActionsTest { | ||||
| @Extension(ordinal = 999) | ||||
| public class SearchAction implements RootAction { | ||||
| 
 | ||||
|     @Rule | ||||
|     public JenkinsRule j = new JenkinsRule(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void override() throws Exception { | ||||
|         assertNotNull(j.createWebClient().goTo("").getElementById("bravo")); | ||||
|     @Override | ||||
|     public String getIconFileName() { | ||||
|         return "symbol-search"; | ||||
|     } | ||||
| 
 | ||||
|     @TestExtension | ||||
|     public static class RootActionImpl extends InvisibleAction implements RootAction { | ||||
|     @Override | ||||
|     public String getDisplayName() { | ||||
|         return "Search"; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getUrlName() { | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,96 @@ | |||
| /* | ||||
|  * The MIT License | ||||
|  * | ||||
|  * Copyright (c) 2025, Jan Faracik | ||||
|  * | ||||
|  * 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.model.navigation; | ||||
| 
 | ||||
| import static hudson.Functions.getAvatar; | ||||
| 
 | ||||
| import hudson.Extension; | ||||
| import hudson.model.Action; | ||||
| import hudson.model.RootAction; | ||||
| import hudson.model.User; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import org.kohsuke.accmod.Restricted; | ||||
| import org.kohsuke.accmod.restrictions.NoExternalUse; | ||||
| 
 | ||||
| /** | ||||
|  * Display the user avatar in the navigation bar. | ||||
|  * Provides a handy jumplist for common user actions. | ||||
|  */ | ||||
| @Extension(ordinal = -1) | ||||
| public class UserAction implements RootAction { | ||||
| 
 | ||||
|     @Override | ||||
|     public String getIconFileName() { | ||||
|         User current = User.current(); | ||||
| 
 | ||||
|         if (current == null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return getAvatar(current, "96x96"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getDisplayName() { | ||||
|         User current = User.current(); | ||||
| 
 | ||||
|         if (current == null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return current.getFullName(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getUrlName() { | ||||
|         User current = User.current(); | ||||
| 
 | ||||
|         if (current == null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return current.getUrl(); | ||||
|     } | ||||
| 
 | ||||
|     @Restricted(NoExternalUse.class) | ||||
|     public User getUser() { | ||||
|         return User.current(); | ||||
|     } | ||||
| 
 | ||||
|     @Restricted(NoExternalUse.class) | ||||
|     public List<Action> getActions() { | ||||
|         User current = User.current(); | ||||
| 
 | ||||
|         if (User.current() == null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         List<Action> actions = new ArrayList<>(); | ||||
|         actions.addAll(current.getPropertyActions()); | ||||
|         actions.addAll(current.getTransientActions()); | ||||
|         return actions.stream().filter(e -> e.getIconFileName() != null).toList(); | ||||
|     } | ||||
| } | ||||
|  | @ -1,8 +1,17 @@ | |||
| package jenkins.views; | ||||
| 
 | ||||
| import hudson.ExtensionComponent; | ||||
| import hudson.ExtensionList; | ||||
| import hudson.ExtensionPoint; | ||||
| import hudson.model.Action; | ||||
| import hudson.model.RootAction; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
| import java.util.stream.Collectors; | ||||
| import jenkins.model.Jenkins; | ||||
| import org.jenkins.ui.icon.IconSpec; | ||||
| import org.kohsuke.accmod.Restricted; | ||||
| import org.kohsuke.accmod.restrictions.NoExternalUse; | ||||
| 
 | ||||
|  | @ -54,4 +63,28 @@ public abstract class Header implements ExtensionPoint { | |||
|         return header.orElseGet(JenkinsHeader::new); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return a list of {@link Action} to show in the header, defaults to {@link hudson.model.RootAction} extensions | ||||
|      */ | ||||
|     @Restricted(NoExternalUse.class) | ||||
|     public List<Action> getActions() { | ||||
|         // There's an issue where new actions (e.g. a new plugin installation) don't appear in the order | ||||
|         // of their ordinal annotation - to work around that we manually sort the list | ||||
|         Map<String, Double> rootActionsOrdinal = ExtensionList.lookup(RootAction.class) | ||||
|                 .getComponents() | ||||
|                 .stream() | ||||
|                 .collect(Collectors.toMap( | ||||
|                         c -> c.getInstance().getClass().getName(), | ||||
|                         ExtensionComponent::ordinal | ||||
|                 )); | ||||
| 
 | ||||
|         return Jenkins.get() | ||||
|                 .getActions() | ||||
|                 .stream() | ||||
|                 .filter(e -> e.getIconFileName() != null || (e instanceof IconSpec is && is.getIconClassName() != null)) | ||||
|                 .sorted(Comparator.comparingDouble( | ||||
|                         a -> rootActionsOrdinal.getOrDefault(a.getClass().getName(), Double.MAX_VALUE) | ||||
|                 ).reversed()) | ||||
|                 .toList(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ public abstract class PartialHeader extends Header { | |||
|      * | ||||
|      * Increment this number when an incompatible change is made to the header (like the search form API). | ||||
|      */ | ||||
|     private static final int compatibilityHeaderVersion = 1; | ||||
|     private static final int compatibilityHeaderVersion = 2; | ||||
| 
 | ||||
|     @Override | ||||
|     public final boolean isCompatible() { | ||||
|  |  | |||
|  | @ -28,11 +28,6 @@ THE SOFTWARE. | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> | ||||
|   <l:layout title="${%Manage Jenkins}" permissions="${app.MANAGE_AND_SYSTEM_READ}"> | ||||
|     <j:if test="${taskTags==null}"> | ||||
|       <st:include page="sidepanel.jelly" it="${app}" /> | ||||
|       <!-- no need for additional breadcrumb here as we're on an index page already including breadcrumb --> | ||||
|     </j:if> | ||||
| 
 | ||||
|     <l:main-panel> | ||||
|       <l:app-bar title="${%Manage Jenkins}"> | ||||
|         <l:search-bar placeholder="${%Search settings}" id="settings-search-bar" /> | ||||
|  |  | |||
|  | @ -199,6 +199,8 @@ LabelExpression.LabelLink=<a href="{0}{2}">Label {1}</a> matches {3,choice,0#no | |||
| LabelExpression.NoMatch=No agent/cloud matches this label expression. | ||||
| LabelExpression.NoMatch_DidYouMean=No agent/cloud matches this label expression. Did you mean ‘{1}’ instead of ‘{0}’? | ||||
| ManageJenkinsAction.DisplayName=Manage Jenkins | ||||
| ManageJenkinsAction.notification={0} notification | ||||
| ManageJenkinsAction.notifications={0} notifications | ||||
| MultiStageTimeSeries.EMPTY_STRING= | ||||
| ParametersDefinitionProperty.BuildButtonText=Build | ||||
| Queue.AllNodesOffline=All nodes of label ‘{0}’ are offline | ||||
|  |  | |||
|  | @ -60,7 +60,6 @@ THE SOFTWARE. | |||
|       <st:include page="sidepanel2.jelly" optional="true"/> | ||||
| 
 | ||||
|       <st:include page="tasks-bottom.jelly" it="${it.owner}" optional="true" /> | ||||
|       <t:actions /> | ||||
|     </l:tasks> | ||||
|     <j:forEach var="w" items="${it.widgets}"> | ||||
|       <j:set var="view" value="${it}" /><!-- expose the view that's rendering this sidepanel to the widget --> | ||||
|  |  | |||
|  | @ -25,7 +25,4 @@ THE SOFTWARE. | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler"> | ||||
|   <st:include page="/hudson/security/SecurityRealm/loginLink.jelly" /> | ||||
|   <j:if test="${it.allowsSignup()}"> | ||||
|     <a href="${rootURL}/signup">${%sign up}</a> | ||||
|   </j:if> | ||||
| </j:jelly> | ||||
|  |  | |||
|  | @ -24,6 +24,12 @@ THE SOFTWARE. | |||
| --> | ||||
| 
 | ||||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler"> | ||||
|   <a href="${rootURL}/${app.securityRealm.loginUrl}?from=${app.securityRealm.from}">${%login}</a> | ||||
| <j:jelly xmlns:j="jelly:core"> | ||||
|   <a class="jenkins-button" | ||||
|      data-type="header-action" | ||||
|      href="${rootURL}/${app.securityRealm.loginUrl}?from=${app.securityRealm.from}" | ||||
|      style="aspect-ratio: unset; padding: 0.5rem 1rem" | ||||
|      tooltip="${%signInTooltip}"> | ||||
|     ${%signIn} | ||||
|   </a> | ||||
| </j:jelly> | ||||
|  |  | |||
|  | @ -21,3 +21,5 @@ | |||
| # THE SOFTWARE. | ||||
| 
 | ||||
| login=log in | ||||
| signIn=Sign in | ||||
| signInTooltip=Sign in to access and manage your Jenkins jobs | ||||
|  |  | |||
|  | @ -1,71 +0,0 @@ | |||
| <!-- | ||||
| The MIT License | ||||
| 
 | ||||
| Copyright (c) 2016, Daniel Beck, Keith Zantow, CloudBees, Inc. | ||||
| 
 | ||||
| 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. | ||||
| --> | ||||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout"> | ||||
|     <j:set var="activeMonitors" value="${it.getMonitorsToDisplay()}"/> | ||||
| 
 | ||||
|     <j:if test="${activeMonitors != null and activeMonitors.size() > 0}"> | ||||
|         <st:adjunct includes="jenkins.management.AdministrativeMonitorsDecorator.resources"/> | ||||
| 
 | ||||
|         <j:set var="activeNonSecurityAM" value="${it.filterNonSecurityAdministrativeMonitors(activeMonitors)}" /> | ||||
|         <j:set var="activeNonSecurityAMCount" value="${activeNonSecurityAM.size()}" /> | ||||
|         <j:set var="activeSecurityAM" value="${it.filterSecurityAdministrativeMonitors(activeMonitors)}" /> | ||||
|         <j:set var="activeSecurityAMCount" value="${activeSecurityAM.size()}" /> | ||||
| 
 | ||||
|         <div id="visible-am-container" class="am-container"> | ||||
|             <j:if test="${activeNonSecurityAMCount > 0}"> | ||||
|                 <a id="visible-am-button" | ||||
|                    class="am-button" | ||||
|                    href="#" | ||||
|                    data-href="${rootURL}/administrativeMonitorsApi/nonSecurityPopupContent" | ||||
|                    title="${%tooltip(activeNonSecurityAMCount)}"> | ||||
|                     <l:icon src="symbol-notifications" class="icon-md"/> | ||||
|                     <div class="am-monitor__indicator-mobile"/> | ||||
|                     <span class="am-monitor__count"> | ||||
|                         ${activeNonSecurityAMCount} | ||||
|                     </span> | ||||
|                 </a> | ||||
|                 <div id="visible-am-list" class="am-list"> | ||||
|                 </div> | ||||
|             </j:if> | ||||
|         </div> | ||||
|         <div id="visible-sec-am-container" class="am-container"> | ||||
|             <j:if test="${activeSecurityAMCount > 0}"> | ||||
|                 <a id="visible-sec-am-button" | ||||
|                    class="am-button security-am" | ||||
|                    href="#" | ||||
|                    data-href="${rootURL}/administrativeMonitorsApi/securityPopupContent" | ||||
|                    title="${%tooltipSec(activeSecurityAMCount)}"> | ||||
|                     <l:icon src="symbol-shield-warning" class="icon-md"/> | ||||
|                     <div class="am-monitor__indicator-mobile"/> | ||||
|                     <span class="am-monitor__count"> | ||||
|                         ${activeSecurityAMCount} | ||||
|                     </span> | ||||
|                 </a> | ||||
|                 <div id="visible-sec-am-list" class="am-list"> | ||||
|                 </div> | ||||
|             </j:if> | ||||
|         </div> | ||||
|     </j:if> | ||||
| </j:jelly> | ||||
|  | @ -1,2 +0,0 @@ | |||
| tooltip=There are {0} active administrative monitors. | ||||
| tooltipSec=There are {0} active security administrative monitors. | ||||
|  | @ -1,27 +0,0 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Bulgarian translation: Copyright (c) 2017, Alexander Shopov <ash@kambanaria.org> | ||||
| # | ||||
| # 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. | ||||
| 
 | ||||
| # There are {0} active administrative monitors. | ||||
| tooltip=\ | ||||
|  В момента има {0} предупреждения. | ||||
| Manage\ Jenkins=\ | ||||
|  Управление на Jenkins | ||||
|  | @ -1,24 +0,0 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Copyright (c) 2017 Daniel Beck and a number of other of contributors | ||||
| # | ||||
| # 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. | ||||
| 
 | ||||
| Manage\ Jenkins=Jenkins verwalten | ||||
| tooltip={0,choice,0#Keine Administrator-Warnungen sind|1#{0} Administrator-Warnung ist|1<{0} Administrator-Warnungen sind} aktiv. | ||||
|  | @ -1,2 +0,0 @@ | |||
| tooltip=Il existe {0} moniteurs d''administration activés. | ||||
| tooltipSec=Il existe {0} moniteurs d''administration de sécurité activés. | ||||
|  | @ -1,25 +0,0 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Italian localization plugin for Jenkins | ||||
| # Copyright © 2020 Alessandro Menti | ||||
| # | ||||
| # 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. | ||||
| 
 | ||||
| Manage\ Jenkins=Gestisci Jenkins | ||||
| tooltip=Ci sono {0} monitor amministrativi attivi. | ||||
|  | @ -1,24 +0,0 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Copyright (c) 2016-2017, Damian Szczepanik | ||||
| # | ||||
| # 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. | ||||
| # There are {0} active administrative monitors. | ||||
| tooltip=Znaleziono {0} aktywnych powiadomień dla administratorów | ||||
| Manage\ Jenkins=Zarządzaj Jenkinsem | ||||
|  | @ -1,24 +0,0 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors | ||||
| # | ||||
| # 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. | ||||
| 
 | ||||
| tooltip=Existem {0} monitores administrativos ativos. | ||||
| tooltipSec=Existem {0} monitores administrativos de segurança ativos. | ||||
|  | @ -1,2 +0,0 @@ | |||
| tooltip=Det finns {0} aktiva administrativa övervakningar. | ||||
| tooltipSec=Det finns {0} aktiva säkerhetsadministrativa övervakningar. | ||||
|  | @ -1,25 +0,0 @@ | |||
| # The MIT License | ||||
| #  | ||||
| # Copyright (c) 2021, Mustafa Ulu | ||||
| #  | ||||
| # 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. | ||||
| 
 | ||||
| Manage\ Jenkins=Jenkins''i Yönet | ||||
| tooltip={0} adet aktif yönetimsel gösterge var. | ||||
| tooltipSec={0} adet aktif yönetimsel güvenlik göstergesi var. | ||||
|  | @ -1,2 +0,0 @@ | |||
| tooltip=有 {0} 個啟用中的管理監視器。 | ||||
| tooltipSec=有 {0} 個啟用中的安全性管理監視器。 | ||||
|  | @ -1,184 +0,0 @@ | |||
| .am-container { | ||||
|   display: contents; | ||||
| } | ||||
| 
 | ||||
| .am-button { | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .am-button .am-monitor__indicator-mobile { | ||||
|   display: none; | ||||
|   position: absolute; | ||||
|   top: 0.25rem; | ||||
|   right: 0.25rem; | ||||
|   border-radius: 50%; | ||||
|   width: 0.65rem; | ||||
|   height: 0.65rem; | ||||
|   background-color: var(--warning-color); | ||||
| } | ||||
| .security-am .am-monitor__indicator-mobile { | ||||
|   background-color: var(--error-color); | ||||
| } | ||||
| 
 | ||||
| .am-button .am-monitor__count { | ||||
|   display: inline-flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   height: 18px; | ||||
|   min-width: 18px; | ||||
| 
 | ||||
|   color: #fff; | ||||
|   background-color: var(--warning-color); | ||||
|   font-weight: var(--font-bold-weight); | ||||
|   font-size: var(--font-size-xs); | ||||
| 
 | ||||
|   border-radius: 16px; | ||||
| } | ||||
| .am-button.security-am .am-monitor__count { | ||||
|   color: #fff; | ||||
|   background-color: var(--error-color); | ||||
| } | ||||
| .am-container div.am-list { | ||||
|   position: absolute; | ||||
|   top: 48px; | ||||
|   right: 2%; | ||||
|   height: auto; | ||||
|   padding: var(--section-padding); | ||||
|   text-align: left; | ||||
|   display: block; | ||||
|   background-color: var(--background); | ||||
|   box-shadow: var(--dropdown-box-shadow); | ||||
|   border-radius: 15px; | ||||
|   opacity: 0; | ||||
|   z-index: 0; | ||||
|   transform: scale(0); | ||||
| } | ||||
| 
 | ||||
| .am-container.am-hidden div.am-list { | ||||
|   animation: hide-am-list 300ms ease-in 1 normal; | ||||
| } | ||||
| 
 | ||||
| .am-container.visible div.am-list { | ||||
|   opacity: 1; | ||||
|   animation: show-am-list 300ms ease-in 1 normal forwards; | ||||
|   z-index: 1000; | ||||
| } | ||||
| 
 | ||||
| @keyframes show-am-list { | ||||
|   from { | ||||
|     opacity: 0; | ||||
|     visibility: hidden; | ||||
|     transform: translateY(-10px) scale(0.975); | ||||
|   } | ||||
|   to { | ||||
|     opacity: 1; | ||||
|     visibility: visible; | ||||
|     transform: scale(1); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @keyframes hide-am-list { | ||||
|   from { | ||||
|     opacity: 1; | ||||
|     visibility: visible; | ||||
|     transform: scale(1); | ||||
|     z-index: 1000; | ||||
|   } | ||||
|   to { | ||||
|     opacity: 0; | ||||
|     visibility: hidden; | ||||
|     transform: translateY(-10px) scale(0.975); | ||||
|     z-index: 1000; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .am-container .am-message { | ||||
|   display: block; | ||||
|   line-height: 1.4em; | ||||
|   margin-bottom: 1.4em; | ||||
| } | ||||
| .am-container.visible .am-button:after { | ||||
|   background: var(--button-background--hover); | ||||
| } | ||||
| 
 | ||||
| .am-message-list { | ||||
|   padding: 0; | ||||
|   margin: 0; | ||||
| } | ||||
| 
 | ||||
| .am-container .am-message .alert form, | ||||
| .am-container .am-message .jenkins-alert form { | ||||
|   position: relative; | ||||
|   float: right; | ||||
|   margin: -6px 0 0 0 !important; | ||||
|   gap: 0.5rem; | ||||
|   display: flex; | ||||
|   padding-left: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .am-container .am-message .alert form span, | ||||
| .am-container .am-message .jenkins-alert form span { | ||||
|   margin: 0 0 0 4px !important; | ||||
| } | ||||
| 
 | ||||
| .am-container .am-message .alert, | ||||
| .am-container .am-message .jenkins-alert { | ||||
|   margin-bottom: 0 !important; | ||||
| } | ||||
| 
 | ||||
| .am-container .am-message dl dt::after { | ||||
|   content: ": "; | ||||
| } | ||||
| 
 | ||||
| /* Restore hyperlink style overriden by the page header */ | ||||
| .am-container .am-list a:link { | ||||
|   display: inline-block; | ||||
|   color: var(--link-color); | ||||
|   text-decoration: underline; | ||||
|   margin-right: 0; | ||||
|   padding: 0; | ||||
|   font-weight: var(--link-font-weight); | ||||
| } | ||||
| .am-container .am-list a:visited { | ||||
|   color: var(--link-color); | ||||
| } | ||||
| .am-container .am-list a:hover, | ||||
| .am-container .am-list a:focus, | ||||
| .am-container .am-list a:active { | ||||
|   color: var(--link-color); | ||||
|   background-color: transparent; | ||||
|   text-decoration: underline; | ||||
|   text-decoration: var(--link-text-decoration--hover); | ||||
| } | ||||
| 
 | ||||
| .am-container .am-list .jenkins-alert-success a { | ||||
|   color: var(--alert-success-text-color); | ||||
| } | ||||
| 
 | ||||
| .am-container .am-list .jenkins-alert-info a { | ||||
|   color: var(--alert-info-text-color); | ||||
| } | ||||
| 
 | ||||
| .am-container .am-list .jenkins-alert-warning a { | ||||
|   color: var(--alert-warning-text-color); | ||||
| } | ||||
| 
 | ||||
| .am-container .am-list .jenkins-alert-danger a { | ||||
|   color: var(--alert-danger-text-color); | ||||
| } | ||||
| 
 | ||||
| @media screen and (max-width: 576px) { | ||||
|   /* Hide non-security monitors on mobile view to avoid messing up the heading */ | ||||
|   #visible-am-container { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media screen and (max-width: 768px) { | ||||
|   .am-button .am-monitor__indicator-mobile { | ||||
|     display: block; | ||||
|   } | ||||
|   .am-button .am-monitor__count { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
|  | @ -1,128 +0,0 @@ | |||
| (function () { | ||||
|   function initializeAmMonitor(amMonitorRoot, options) { | ||||
|     var button = amMonitorRoot.querySelector(".am-button"); | ||||
|     var amList = amMonitorRoot.querySelector(".am-list"); | ||||
|     if (button === null || amList === null) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     var url = button.getAttribute("data-href"); | ||||
| 
 | ||||
|     function onClose(e) { | ||||
|       var list = amList; | ||||
|       var el = e.target; | ||||
|       while (el) { | ||||
|         if (el === list) { | ||||
|           return; // clicked in the list
 | ||||
|         } | ||||
|         el = el.parentElement; | ||||
|       } | ||||
|       close(); | ||||
|     } | ||||
| 
 | ||||
|     function onEscClose(e) { | ||||
|       var escapeKeyCode = 27; | ||||
|       if (e.keyCode === escapeKeyCode) { | ||||
|         close(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     function show() { | ||||
|       if (options.closeAll) { | ||||
|         options.closeAll(); | ||||
|       } | ||||
| 
 | ||||
|       fetch(url).then((rsp) => { | ||||
|         if (rsp.ok) { | ||||
|           rsp.text().then((responseText) => { | ||||
|             var popupContent = responseText; | ||||
|             amList.innerHTML = popupContent; | ||||
|             amMonitorRoot.classList.add("visible"); | ||||
|             amMonitorRoot.classList.remove("am-hidden"); | ||||
|             document.addEventListener("click", onClose); | ||||
|             document.addEventListener("keydown", onEscClose); | ||||
| 
 | ||||
|             // Applies all initialization code to the elements within the popup
 | ||||
|             // Among other things, this sets the CSRF crumb to the forms within
 | ||||
|             Behaviour.applySubtree(amList); | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     function close() { | ||||
|       if (amMonitorRoot.classList.contains("visible")) { | ||||
|         amMonitorRoot.classList.add("am-hidden"); | ||||
|       } | ||||
|       amMonitorRoot.classList.remove("visible"); | ||||
|       document.removeEventListener("click", onClose); | ||||
|       document.removeEventListener("keydown", onEscClose); | ||||
|     } | ||||
| 
 | ||||
|     function toggle(e) { | ||||
|       if (amMonitorRoot.classList.contains("visible")) { | ||||
|         close(); | ||||
|       } else { | ||||
|         show(); | ||||
|       } | ||||
|       e.preventDefault(); | ||||
|     } | ||||
| 
 | ||||
|     function startListeners() { | ||||
|       button.addEventListener("click", toggle); | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       close: close, | ||||
|       startListeners: startListeners, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   document.addEventListener("DOMContentLoaded", function () { | ||||
|     var monitorWidgets; | ||||
| 
 | ||||
|     function closeAll() { | ||||
|       monitorWidgets.forEach(function (widget) { | ||||
|         widget.close(); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     var normalMonitors = initializeAmMonitor( | ||||
|       document.getElementById("visible-am-container"), | ||||
|       { | ||||
|         closeAll: closeAll, | ||||
|       }, | ||||
|     ); | ||||
|     var securityMonitors = initializeAmMonitor( | ||||
|       document.getElementById("visible-sec-am-container"), | ||||
|       { | ||||
|         closeAll: closeAll, | ||||
|       }, | ||||
|     ); | ||||
|     monitorWidgets = [normalMonitors, securityMonitors].filter( | ||||
|       function (widget) { | ||||
|         return widget !== null; | ||||
|       }, | ||||
|     ); | ||||
| 
 | ||||
|     monitorWidgets.forEach(function (widget) { | ||||
|       widget.startListeners(); | ||||
|     }); | ||||
|   }); | ||||
| })(); | ||||
| 
 | ||||
| document.addEventListener("DOMContentLoaded", function () { | ||||
|   var amContainer = document.getElementById("visible-am-container"); | ||||
|   var amInsertion = document.getElementById("visible-am-insertion"); | ||||
| 
 | ||||
|   if (amInsertion) { | ||||
|     amInsertion.appendChild(amContainer); | ||||
|   } | ||||
| 
 | ||||
|   var secAmContainer = document.getElementById("visible-sec-am-container"); | ||||
|   var secAmInsertion = document.getElementById("visible-sec-am-insertion"); | ||||
| 
 | ||||
|   if (secAmInsertion) { | ||||
|     secAmInsertion.appendChild(secAmContainer); | ||||
|   } | ||||
| }); | ||||
|  | @ -29,7 +29,7 @@ THE SOFTWARE. | |||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> | ||||
| <l:layout permissions="${app.MANAGE_AND_SYSTEM_READ}" title="${%System}" type="one-column"> | ||||
|   <st:include page="sidepanel.jelly" /> | ||||
|   <f:breadcrumb-config-outline title="${%System}" /> | ||||
|   <l:breadcrumb title="${%System}" /> | ||||
| 
 | ||||
|   <l:main-panel> | ||||
|     <j:set var="readOnlyMode" value="${!h.hasPermission(app.MANAGE)}"/> | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout"> | ||||
|   <div class="jenkins-keyboard-shortcut__tooltip"> | ||||
|     <span class="jenkins-!-margin-right-1"> | ||||
|       ${%Search} | ||||
|     </span> | ||||
|     <l:keyboard-shortcut shortcut="CMD+K" /> | ||||
|   </div> | ||||
| </j:jelly> | ||||
|  | @ -0,0 +1,25 @@ | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:dd="/lib/layout/dropdowns"> | ||||
|   <dd:custom> | ||||
|     <a class="jenkins-dropdown__item" href="${rootURL}/${it.user.url}"> | ||||
|       <div class="jenkins-dropdown__item__icon"> | ||||
|         <l:icon src="${it.iconFileName}" class="jenkins-avatar" /> | ||||
|       </div> | ||||
|       ${it.user.fullName} | ||||
|     </a> | ||||
|   </dd:custom> | ||||
| 
 | ||||
|   <dd:separator /> | ||||
| 
 | ||||
|   <j:forEach var="action" items="${it.actions}"> | ||||
|     <dd:item icon="${action.iconFileName}" | ||||
|              text="${action.displayName}" | ||||
|              href="${rootURL}/${it.user.url}/${action.urlName}" /> | ||||
|   </j:forEach> | ||||
| 
 | ||||
|   <dd:separator /> | ||||
| 
 | ||||
|   <dd:item icon="symbol-log-out" | ||||
|            text="${%Sign out}" | ||||
|            href="${rootURL}/logout" /> | ||||
| </j:jelly> | ||||
|  | @ -1,8 +1,20 @@ | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:h="/lib/layout/header"> | ||||
|     <header id="page-header" class="page-header"> | ||||
|       <h:logo/> | ||||
|       <h:searchbox/> | ||||
|       <h:login/> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:h="/lib/layout/header" xmlns:l="/lib/layout" xmlns:st="jelly:stapler"> | ||||
|   <header id="page-header" class="jenkins-header"> | ||||
|     <st:include page="prefix" optional="true" /> | ||||
| 
 | ||||
|     <div class="jenkins-header__main"> | ||||
|       <div class="jenkins-header__navigation"> | ||||
|         <st:include page="logo" /> | ||||
| 
 | ||||
|         <l:breadcrumbBar> | ||||
|           <j:out value="${breadcrumbs}" /> | ||||
|         </l:breadcrumbBar> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <h:actions /> | ||||
|   </header> | ||||
| 
 | ||||
|   <script src="${resURL}/jsbundles/header.js" type="text/javascript" /> | ||||
| </j:jelly> | ||||
|  |  | |||
|  | @ -0,0 +1,7 @@ | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core"> | ||||
|   <a href="${rootURL}/" class="app-jenkins-logo"> | ||||
|     <img id="jenkins-head-icon" src="${imagesURL}/svgs/logo.svg" aria-hidden="true" /> | ||||
|     <span class="jenkins-mobile-hide">Jenkins</span> | ||||
|   </a> | ||||
| </j:jelly> | ||||
|  | @ -44,13 +44,13 @@ THE SOFTWARE. | |||
| 
 | ||||
|   <j:if test="${mode=='breadcrumbs'}"> | ||||
|     <j:set var="hasLink" value="${attrs.href != null}" /> | ||||
|     <li id="${attrs.id}" class="jenkins-breadcrumbs__list-item" aria-current="${hasLink ? null : 'page'}"> | ||||
|     <li id="${attrs.id}" class="jenkins-breadcrumbs__list-item" data-type="breadcrumb-item" aria-current="${hasLink ? null : 'page'}"> | ||||
|       <j:choose> | ||||
|         <j:when test="${!hasLink}"> | ||||
|           ${attrs.title} | ||||
|           <span>${attrs.title}</span> | ||||
|         </j:when> | ||||
|         <j:otherwise> | ||||
|           <a href="${attrs.href}" class="${attrs.hasMenu ? 'model-link' : ''}"> | ||||
|           <a href="${attrs.href}" class="${attrs.hasMenu ? 'hoverable-model-link' : ''}"> | ||||
|             ${attrs.title} | ||||
|           </a> | ||||
|         </j:otherwise> | ||||
|  |  | |||
|  | @ -35,34 +35,22 @@ THE SOFTWARE. | |||
|     ]]> | ||||
|   </st:documentation> | ||||
| 
 | ||||
|   <j:set var="contents" trim="true"> | ||||
|     <d:invokeBody /> | ||||
|   </j:set> | ||||
| 
 | ||||
|   <div id="breadcrumbBar" class="jenkins-breadcrumbs" aria-label="breadcrumb"> | ||||
|     <ol class="jenkins-breadcrumbs__list" id="breadcrumbs"> | ||||
|       <j:forEach var="anc" items="${request2.ancestors}" indexVar="index"> | ||||
|         <j:if test="${h.isModel(anc.object) and anc.prev.url!=anc.url}"> | ||||
|           <j:set var="mode" value="breadcrumbs" /> | ||||
|           <l:breadcrumb title="${anc.object == app ? '%Dashboard' : anc.object.displayName}" | ||||
| 
 | ||||
|           <j:if test="${anc.object != app}"> | ||||
|             <l:breadcrumb title="${anc.object.displayName}" | ||||
|                           href="${anc.url}/" | ||||
|                           hasMenu="${h.isModelWithContextMenu(anc.object)}" /> | ||||
|           <j:choose> | ||||
|             <j:when test="${h.isModelWithChildren(anc.object)}"> | ||||
|               <li class="children" data-href="${anc.url}/"> | ||||
|                 <!-- shows '>' for rendering children --> | ||||
|               </li> | ||||
|             </j:when> | ||||
|             <j:otherwise> | ||||
|               <li class="separator"> | ||||
|               </li> | ||||
|             </j:otherwise> | ||||
|           </j:choose> | ||||
|           </j:if> | ||||
|         </j:if> | ||||
|       </j:forEach> | ||||
| 
 | ||||
|       <!-- render additional breadcrumb items --> | ||||
|       <j:out value="${contents}" /> | ||||
|       <d:invokeBody /> | ||||
|     </ol> | ||||
|   </div> | ||||
| </j:jelly> | ||||
|  |  | |||
|  | @ -0,0 +1,70 @@ | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:h="/lib/layout/header" xmlns:l="/lib/layout" xmlns:st="jelly:stapler" xmlns:x="jelly:xml"> | ||||
|   <div class="jenkins-header__actions"> | ||||
|     <st:include page="suffix" optional="true" /> | ||||
| 
 | ||||
|     <j:forEach var="action" items="${it.actions}"> | ||||
|       <j:set var="isCurrent" value="${h.hyperlinkMatchesCurrentPage(action.urlName)}" /> | ||||
| 
 | ||||
|       <j:set var="jumplist"> | ||||
|         <st:include it="${action}" page="jumplist.jelly" optional="true" /> | ||||
|       </j:set> | ||||
| 
 | ||||
|       <j:if test="${jumplist.length() == 0}"> | ||||
|         <j:set var="tooltip"> | ||||
|           <st:include it="${action}" page="tooltip.jelly" optional="true" /> | ||||
|         </j:set> | ||||
|       </j:if> | ||||
| 
 | ||||
|       <j:set var="badge" value="${action.badge}" /> | ||||
|       <j:if test="${jumplist.length() == 0 and tooltip.length() == 0}"> | ||||
|         <j:set var="tooltip"> | ||||
|           <div style="text-align: center;">${action.displayName}</div> | ||||
|           <j:if test="${badge != null}"> | ||||
|             <div style="text-align: center; color: var(--text-color-secondary)">${badge.tooltip}</div> | ||||
|           </j:if> | ||||
|         </j:set> | ||||
|       </j:if> | ||||
| 
 | ||||
|       <j:set var="interactive" value="${jumplist.length() gt 0}" /> | ||||
|       <j:set var="icon" value="${action.iconClassName != null ? action.iconClassName : action.iconFileName}"/> | ||||
|       <x:element name="${action.urlName == null ? 'button' : 'a'}"> | ||||
|         <x:attribute name="data-dropdown">${interactive}</x:attribute> | ||||
|         <x:attribute name="id">root-action-${action.class.simpleName}</x:attribute> | ||||
|         <x:attribute name="href">${h.getActionUrl(app.url, action)}</x:attribute> | ||||
|         <j:if test="${interactive}"> | ||||
|           <x:attribute name="data-tippy-offset">[0, 10]</x:attribute> | ||||
|         </j:if> | ||||
|         <j:if test="${!interactive}"> | ||||
|           <x:attribute name="data-html-tooltip" escapeText="false"><j:out value="${tooltip}" /></x:attribute> | ||||
|         </j:if> | ||||
|         <x:attribute name="data-tooltip-interactive">${interactive}</x:attribute> | ||||
|         <x:attribute name="data-tippy-animation">tooltip</x:attribute> | ||||
|         <x:attribute name="data-tippy-theme">${interactive ? 'dropdown' : 'tooltip'}</x:attribute> | ||||
|         <x:attribute name="data-tippy-trigger">mouseenter focus</x:attribute> | ||||
|         <x:attribute name="data-tippy-touch">${interactive}</x:attribute> | ||||
|         <x:attribute name="data-type">header-action</x:attribute> | ||||
|         <x:attribute name="draggable">false</x:attribute> | ||||
|         <x:attribute name="class">jenkins-button ${isCurrent ? '' : 'jenkins-button--tertiary'}</x:attribute> | ||||
|         <l:icon src="${icon}" class="jenkins-avatar" /> | ||||
|         <span class="jenkins-visually-hidden" data-type="action-label">${action.displayName}</span> | ||||
|         <j:if test="${badge != null}"> | ||||
|           <span class="jenkins-badge jenkins-!-${badge.severity}-color" /> | ||||
|         </j:if> | ||||
|       </x:element> | ||||
| 
 | ||||
|       <j:if test="${interactive}"> | ||||
|         <template> | ||||
|           <div class="jenkins-dropdown"> | ||||
|             <j:out value="${jumplist}" /> | ||||
|           </div> | ||||
|         </template> | ||||
|       </j:if> | ||||
| 
 | ||||
|       <j:set var="jumplist" /> | ||||
|       <j:set var="tooltip" /> | ||||
|     </j:forEach> | ||||
| 
 | ||||
|     <h:login/> | ||||
|   </div> | ||||
| </j:jelly> | ||||
|  | @ -1,37 +1,8 @@ | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout"> | ||||
|   <div class="login page-header__hyperlinks"> | ||||
|     <div id="visible-am-insertion" class="page-header__am-wrapper" /> | ||||
|     <div id="visible-sec-am-insertion" class="page-header__am-wrapper" /> | ||||
| 
 | ||||
|     <!-- user icon and login/logout links; only show if authentication is enabled and we're not handling a servlet error --> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler"> | ||||
|   <j:if test="${app.useSecurity}"> | ||||
|       <j:choose> | ||||
|         <j:when test="${!h.isAnonymous()}"> | ||||
|           <j:invokeStatic var="user" className="hudson.model.User" method="current" /> | ||||
|           <j:choose> | ||||
|             <j:when test="${user.fullName == null || user.fullName.trim().isEmpty()}"> | ||||
|               <j:set var="userName" value="${user.id}"/> | ||||
|             </j:when> | ||||
|             <j:otherwise> | ||||
|               <j:set var="userName" value="${user.fullName}"/> | ||||
|             </j:otherwise> | ||||
|           </j:choose> | ||||
|           <a href="${rootURL}/${user.url}" class="model-link"> | ||||
|             <l:icon src="symbol-person-circle" class="icon-md"/> | ||||
|             <span class="hidden-xs hidden-sm">${userName}</span> | ||||
|           </a> | ||||
|           <j:if test="${app.securityRealm.canLogOut()}"> | ||||
|             <a href="${rootURL}/logout"> | ||||
|               <l:icon src="symbol-log-out" class="icon-md" /> | ||||
|               <span class="hidden-xs hidden-sm">${logout}</span> | ||||
|             </a> | ||||
|           </j:if> | ||||
|         </j:when> | ||||
|         <j:otherwise> | ||||
|     <j:if test="${h.isAnonymous()}"> | ||||
|       <st:include it="${app.securityRealm}" page="loginLink.jelly" /> | ||||
|         </j:otherwise> | ||||
|       </j:choose> | ||||
|     </j:if> | ||||
|   </div> | ||||
|   </j:if> | ||||
| </j:jelly> | ||||
|  |  | |||
|  | @ -135,6 +135,10 @@ THE SOFTWARE. | |||
|     </l:hasPermission> | ||||
|     <meta name="ROBOTS" content="INDEX,NOFOLLOW" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <j:set var="breadcrumbs"> | ||||
|       <j:set var="mode" value="breadcrumbs" /> | ||||
|       <d:invokeBody /> | ||||
|     </j:set> | ||||
|     <j:set var="mode" value="header" /> | ||||
|     <d:invokeBody /> | ||||
|     <j:if test="${extensionsAvailable}"> | ||||
|  | @ -149,7 +153,9 @@ THE SOFTWARE. | |||
|     <script src="${resURL}/jsbundles/sortable-drag-drop.js" type="text/javascript"/> | ||||
|     <script src="${resURL}/jsbundles/app.js" type="text/javascript" defer="true" /> | ||||
|   </head> | ||||
|   <body id="jenkins" class="${layoutType} jenkins-${h.version}" data-version="${h.version}" data-model-type="${it.class.name}"> | ||||
|   <body id="jenkins" class="${layoutType} jenkins-${h.version}" data-version="${h.version}" data-model-type="${it.class.name}" | ||||
|         data-search-url="${rootURL + '/search/suggest'}" | ||||
|         data-search-help-url="${%searchBox.url}"> | ||||
|     <l:command-palette /> | ||||
| 
 | ||||
|     <j:if test="${layoutType!='full-screen'}"> | ||||
|  | @ -161,11 +167,6 @@ THE SOFTWARE. | |||
|                     searchPlaceholder="${%search}" | ||||
|                     searchHelpUrl="${%searchBox.url}" | ||||
|                     logout="${%logout}"/> | ||||
| 
 | ||||
|       <l:breadcrumbBar> | ||||
|         <j:set var="mode" value="breadcrumbs" /> | ||||
|         <d:invokeBody/> | ||||
|       </l:breadcrumbBar> | ||||
|     </j:if> | ||||
| 
 | ||||
|     <div id="page-body" class="app-page-body app-page-body--${layoutType} clear"> | ||||
|  |  | |||
|  | @ -27,4 +27,5 @@ | |||
|     </st:documentation> | ||||
|     <j:invokeStatic var="header" className="jenkins.views.Header" method="get"/> | ||||
|     <st:include it="${header}" page="headerContent.jelly"/> | ||||
|     <script src="${resURL}/jsbundles/keyboard-shortcuts.js" type="text/javascript"/> | ||||
| </j:jelly> | ||||
|  |  | |||
|  | @ -2,8 +2,7 @@ | |||
|  * @param {string} searchTerm | ||||
|  */ | ||||
| function search(searchTerm) { | ||||
|   const address = document.getElementById("button-open-command-palette").dataset | ||||
|     .searchUrl; | ||||
|   const address = document.querySelector("body").dataset.searchUrl; | ||||
|   return fetch(`${address}?query=${encodeURIComponent(searchTerm)}`); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ const datasources = [JenkinsSearchSource]; | |||
| function init() { | ||||
|   const i18n = document.getElementById("command-palette-i18n"); | ||||
|   const headerCommandPaletteButton = document.getElementById( | ||||
|     "button-open-command-palette", | ||||
|     "root-action-SearchAction", | ||||
|   ); | ||||
|   if (headerCommandPaletteButton === null) { | ||||
|     return; // no JenkinsHeader, no h:searchbox
 | ||||
|  | @ -67,7 +67,7 @@ function init() { | |||
|           icon: Symbols.HELP, | ||||
|           type: "symbol", | ||||
|           label: i18n.dataset.getHelp, | ||||
|           url: headerCommandPaletteButton.dataset.searchHelpUrl, | ||||
|           url: document.querySelector("body").dataset.searchHelpUrl, | ||||
|           isExternal: true, | ||||
|           group: null, | ||||
|         }), | ||||
|  |  | |||
|  | @ -30,6 +30,43 @@ function generateJumplistAccessors() { | |||
|  * Generates the dropdowns for the jump lists | ||||
|  */ | ||||
| function generateDropdowns() { | ||||
|   behaviorShim.specify( | ||||
|     ".hoverable-model-link", | ||||
|     "-hoverable-dropdown-", | ||||
|     1000, | ||||
|     (element) => | ||||
|       Utils.generateDropdown( | ||||
|         element, | ||||
|         (instance) => { | ||||
|           const href = element.href; | ||||
| 
 | ||||
|           if (element.items) { | ||||
|             instance.setContent(Utils.generateDropdownItems(element.items)); | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           fetch(Path.combinePath(href, "contextMenu")) | ||||
|             .then((response) => response.json()) | ||||
|             .then((json) => | ||||
|               instance.setContent( | ||||
|                 Utils.generateDropdownItems( | ||||
|                   mapChildrenItemsToDropdownItems(json.items), | ||||
|                 ), | ||||
|               ), | ||||
|             ) | ||||
|             .catch((error) => console.log(`Jumplist request failed: ${error}`)) | ||||
|             .finally(() => (instance.loaded = true)); | ||||
|         }, | ||||
|         false, | ||||
|         { | ||||
|           trigger: "mouseenter", | ||||
|           offset: [-16, 10], | ||||
|           animation: "tooltip", | ||||
|           touch: false, | ||||
|         }, | ||||
|       ), | ||||
|   ); | ||||
| 
 | ||||
|   behaviorShim.specify( | ||||
|     "li.children, .jenkins-jumplist-link, #menuSelector, .jenkins-menu-dropdown-chevron", | ||||
|     "-dropdown-", | ||||
|  |  | |||
|  | @ -10,13 +10,21 @@ function init() { | |||
|     "-dropdown-", | ||||
|     1000, | ||||
|     (element) => { | ||||
|       Utils.generateDropdown(element, (instance) => { | ||||
|       Utils.generateDropdown( | ||||
|         element, | ||||
|         (instance) => { | ||||
|           const elements = | ||||
|             element.nextElementSibling.content.children[0].children; | ||||
|           const mappedItems = Utils.convertHtmlToItems(elements); | ||||
| 
 | ||||
|           instance.setContent(Utils.generateDropdownItems(mappedItems)); | ||||
|       }); | ||||
|           instance.loaded = true; | ||||
|         }, | ||||
|         false, | ||||
|         { | ||||
|           appendTo: "parent", | ||||
|         }, | ||||
|       ); | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,26 @@ | |||
| import { createElementFromHtml } from "@/util/dom"; | ||||
| import { xmlEscape } from "@/util/security"; | ||||
| 
 | ||||
| const hideOnPopperBlur = { | ||||
|   name: "hideOnPopperBlur", | ||||
|   defaultValue: true, | ||||
|   fn(instance) { | ||||
|     return { | ||||
|       onCreate() { | ||||
|         instance.popper.addEventListener("focusout", (event) => { | ||||
|           if ( | ||||
|             instance.props.hideOnPopperBlur && | ||||
|             event.relatedTarget && | ||||
|             !instance.popper.contains(event.relatedTarget) | ||||
|           ) { | ||||
|             instance.hide(); | ||||
|           } | ||||
|         }); | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| function dropdown() { | ||||
|   return { | ||||
|     content: "<p class='jenkins-spinner'></p>", | ||||
|  | @ -11,6 +31,7 @@ function dropdown() { | |||
|     arrow: false, | ||||
|     theme: "dropdown", | ||||
|     appendTo: document.body, | ||||
|     plugins: [hideOnPopperBlur], | ||||
|     offset: [0, 0], | ||||
|     animation: "dropdown", | ||||
|     duration: 250, | ||||
|  |  | |||
|  | @ -11,14 +11,17 @@ const SELECTED_ITEM_CLASS = "jenkins-dropdown__item--selected"; | |||
|  * @param element - the element to generate the dropdown for | ||||
|  * @param callback - called to retrieve the list of dropdown items | ||||
|  */ | ||||
| function generateDropdown(element, callback, immediate) { | ||||
| function generateDropdown(element, callback, immediate, options = {}) { | ||||
|   if (element._tippy && element._tippy.props.theme === "dropdown") { | ||||
|     element._tippy.destroy(); | ||||
|   } | ||||
| 
 | ||||
|   tippy( | ||||
|     element, | ||||
|     Object.assign({}, Templates.dropdown(), { | ||||
|     Object.assign( | ||||
|       {}, | ||||
|       Templates.dropdown(), | ||||
|       { | ||||
|         hideOnClick: | ||||
|           element.dataset["hideOnClick"] !== "false" ? "toggle" : false, | ||||
|         onCreate(instance) { | ||||
|  | @ -44,10 +47,14 @@ function generateDropdown(element, callback, immediate) { | |||
|           if (immediate) { | ||||
|             onload(); | ||||
|           } else { | ||||
|           instance.reference.addEventListener("mouseenter", onload); | ||||
|             ["mouseenter", "focus"].forEach((event) => { | ||||
|               instance.reference.addEventListener(event, onload); | ||||
|             }); | ||||
|           } | ||||
|         }, | ||||
|     }), | ||||
|       }, | ||||
|       options, | ||||
|     ), | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,133 @@ | |||
| import Utils from "@/components/dropdowns/utils"; | ||||
| import { createElementFromHtml } from "@/util/dom"; | ||||
| 
 | ||||
| const OVERFLOW_ID = "jenkins-header-actions-overflow"; | ||||
| 
 | ||||
| export default function computeActions() { | ||||
|   document | ||||
|     .querySelectorAll( | ||||
|       ".jenkins-header__actions .jenkins-button[data-type='header-action'].jenkins-hidden", | ||||
|     ) | ||||
|     .forEach((e) => { | ||||
|       e.classList.remove("jenkins-hidden"); | ||||
|     }); | ||||
| 
 | ||||
|   if (!actionsOverflows()) { | ||||
|     removeOverflowButton(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const items = []; | ||||
|   const actions = Array.from( | ||||
|     document.querySelectorAll( | ||||
|       ".jenkins-header__actions .jenkins-button[data-type='header-action']", | ||||
|     ), | ||||
|   ).slice(1, -1); | ||||
| 
 | ||||
|   const overflowButton = generateOverflowButton(); | ||||
| 
 | ||||
|   while (actionsOverflows()) { | ||||
|     const item = actions.pop(); | ||||
| 
 | ||||
|     if (!item) { | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     items.unshift(item); | ||||
|     item.classList.add("jenkins-hidden"); | ||||
|   } | ||||
| 
 | ||||
|   Utils.generateDropdown( | ||||
|     overflowButton, | ||||
|     (instance) => { | ||||
|       const mappedItems = items.map((e) => { | ||||
|         let icon = e.querySelector("img"); | ||||
|         if (icon) { | ||||
|           icon = icon.src; | ||||
|         } | ||||
|         let iconXml = e.querySelector("svg"); | ||||
|         if (iconXml) { | ||||
|           icon = true; | ||||
|           iconXml = iconXml.outerHTML; | ||||
|         } | ||||
| 
 | ||||
|         const span = e.querySelector("[data-type='action-label']"); | ||||
|         let label = e.textContent; | ||||
|         if (span !== null) { | ||||
|           label = span.textContent; | ||||
|         } | ||||
| 
 | ||||
|         return { | ||||
|           type: "link", | ||||
|           icon: icon, | ||||
|           iconXml: iconXml, | ||||
|           label: label, | ||||
|           url: e.href, | ||||
|         }; | ||||
|       }); | ||||
| 
 | ||||
|       instance.setContent(Utils.generateDropdownItems(mappedItems)); | ||||
|     }, | ||||
|     true, | ||||
|     { | ||||
|       trigger: "mouseenter focus", | ||||
|       offset: [0, 10], | ||||
|       animation: "tooltip", | ||||
|     }, | ||||
|   ); | ||||
| 
 | ||||
|   // We want to disable the User action href on touch devices so that they can still activate the overflow menu
 | ||||
|   const link = document.querySelector("#root-action-UserAction"); | ||||
| 
 | ||||
|   if (link) { | ||||
|     const originalHref = link.getAttribute("href"); | ||||
|     const isTouchDevice = window.matchMedia("(hover: none)").matches; | ||||
| 
 | ||||
|     // HTMLUnit doesn't register itself as supporting hover, thus the href is removed when it shouldn't be
 | ||||
|     if (isTouchDevice && !window.isRunAsTest) { | ||||
|       link.removeAttribute("href"); | ||||
|     } else { | ||||
|       link.setAttribute("href", originalHref); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function actionsOverflows() { | ||||
|   const actions = document.querySelector(".jenkins-header__actions"); | ||||
|   return actions.offsetWidth > Math.max(window.innerWidth / 4.5, 150); | ||||
| } | ||||
| 
 | ||||
| function generateOverflowButton() { | ||||
|   // If an overflow menu already exists let's use that
 | ||||
|   const overflowMenu = document.querySelector("#" + OVERFLOW_ID); | ||||
|   if (overflowMenu) { | ||||
|     return overflowMenu; | ||||
|   } | ||||
| 
 | ||||
|   // Generate an overflow menu to store actions
 | ||||
|   const element = | ||||
|     createElementFromHtml(`<button id="${OVERFLOW_ID}" class="jenkins-button jenkins-button--tertiary"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
 | ||||
|     <circle cx="256" cy="256" r="45" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/> | ||||
|     <circle cx="441" cy="256" r="45" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/> | ||||
|     <circle cx="71" cy="256" r="45" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/> | ||||
| </svg> | ||||
| </button>`); | ||||
| 
 | ||||
|   const actionsContainer = document.querySelector(".jenkins-header__actions"); | ||||
| 
 | ||||
|   // Insert the new element before the last child
 | ||||
|   actionsContainer.insertBefore( | ||||
|     element, | ||||
|     actionsContainer.children[actionsContainer.children.length - 2], | ||||
|   ); | ||||
| 
 | ||||
|   return element; | ||||
| } | ||||
| 
 | ||||
| function removeOverflowButton() { | ||||
|   const overflowButton = document.querySelector("#" + OVERFLOW_ID); | ||||
| 
 | ||||
|   if (overflowButton) { | ||||
|     overflowButton.remove(); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,96 @@ | |||
| import Utils from "@/components/dropdowns/utils"; | ||||
| import { createElementFromHtml } from "@/util/dom"; | ||||
| 
 | ||||
| export default function computeBreadcrumbs() { | ||||
|   document | ||||
|     .querySelectorAll(".jenkins-breadcrumbs__list-item.jenkins-hidden") | ||||
|     .forEach((e) => { | ||||
|       e.classList.remove("jenkins-hidden"); | ||||
|     }); | ||||
| 
 | ||||
|   if (!breadcrumbsBarOverflows()) { | ||||
|     removeOverflowButton(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const items = []; | ||||
|   const breadcrumbs = Array.from( | ||||
|     document.querySelectorAll(`[data-type="breadcrumb-item"]`), | ||||
|   ); | ||||
| 
 | ||||
|   const breadcrumbsOverflow = generateOverflowButton().querySelector("button"); | ||||
| 
 | ||||
|   while (breadcrumbsBarOverflows()) { | ||||
|     const item = breadcrumbs.shift(); | ||||
| 
 | ||||
|     if (!item) { | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     items.push(item); | ||||
|     item.classList.add("jenkins-hidden"); | ||||
|   } | ||||
| 
 | ||||
|   Utils.generateDropdown( | ||||
|     breadcrumbsOverflow, | ||||
|     (instance) => { | ||||
|       const mappedItems = items.map((e) => { | ||||
|         let href = e.querySelector("a"); | ||||
|         if (href) { | ||||
|           href = href.href; | ||||
|         } | ||||
| 
 | ||||
|         return { | ||||
|           type: "link", | ||||
|           label: e.textContent, | ||||
|           url: href, | ||||
|         }; | ||||
|       }); | ||||
| 
 | ||||
|       instance.setContent(Utils.generateDropdownItems(mappedItems)); | ||||
|     }, | ||||
|     true, | ||||
|     { | ||||
|       trigger: "mouseenter focus", | ||||
|       offset: [0, 10], | ||||
|       animation: "tooltip", | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function breadcrumbsBarOverflows() { | ||||
|   const breadcrumbsBar = document.querySelector("#breadcrumbBar"); | ||||
|   return breadcrumbsBar.scrollWidth > breadcrumbsBar.offsetWidth; | ||||
| } | ||||
| 
 | ||||
| function generateOverflowButton() { | ||||
|   // If an overflow menu already exists let's use that
 | ||||
|   const overflowMenu = document.querySelector( | ||||
|     ".jenkins-breadcrumbs__list-item .jenkins-button", | ||||
|   ); | ||||
|   if (overflowMenu) { | ||||
|     return overflowMenu.parentNode; | ||||
|   } | ||||
| 
 | ||||
|   // Generate an overflow menu to store breadcrumbs
 | ||||
|   const logo = document.querySelector(".jenkins-breadcrumbs__list-item"); | ||||
|   const element = | ||||
|     createElementFromHtml(`<li class="jenkins-breadcrumbs__list-item"><button class="jenkins-button jenkins-button--tertiary"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
 | ||||
|     <circle cx="256" cy="256" r="45" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/> | ||||
|     <circle cx="441" cy="256" r="45" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/> | ||||
|     <circle cx="71" cy="256" r="45" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/> | ||||
| </svg> | ||||
| </button></li>`); | ||||
|   logo.after(element); | ||||
|   return element; | ||||
| } | ||||
| 
 | ||||
| function removeOverflowButton() { | ||||
|   const breadcrumbsOverflow = document.querySelector( | ||||
|     ".jenkins-breadcrumbs__list-item .jenkins-button", | ||||
|   ); | ||||
| 
 | ||||
|   if (breadcrumbsOverflow) { | ||||
|     breadcrumbsOverflow.parentNode.remove(); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,66 @@ | |||
| import computeActions from "@/components/header/actions-overflow"; | ||||
| import computeBreadcrumbs from "@/components/header/breadcrumbs-overflow"; | ||||
| 
 | ||||
| function init() { | ||||
|   // Recompute what actions and breadcrumbs should be visible when the viewport size is changed
 | ||||
|   computeOverflow(); | ||||
|   let lastWidth = window.innerWidth; | ||||
|   window.addEventListener("resize", () => { | ||||
|     if (window.innerWidth !== lastWidth) { | ||||
|       lastWidth = window.innerWidth; | ||||
|       computeOverflow(); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   // Fade in the page header on scroll, increasing opacity and intensity of the backdrop blur
 | ||||
|   window.addEventListener("scroll", () => { | ||||
|     const navigation = document.querySelector("#page-header"); | ||||
|     const scrollY = Math.max(0, window.scrollY); | ||||
|     navigation.style.setProperty( | ||||
|       "--background-opacity", | ||||
|       Math.min(70, scrollY) + "%", | ||||
|     ); | ||||
|     navigation.style.setProperty( | ||||
|       "--background-blur", | ||||
|       Math.min(40, scrollY) + "px", | ||||
|     ); | ||||
|     if ( | ||||
|       !document.querySelector(".jenkins-search--app-bar") && | ||||
|       !document.querySelector(".app-page-body__sidebar--sticky") | ||||
|     ) { | ||||
|       const prefersContrast = window.matchMedia( | ||||
|         "(prefers-contrast: more)", | ||||
|       ).matches; | ||||
|       navigation.style.setProperty( | ||||
|         "--border-opacity", | ||||
|         Math.min( | ||||
|           prefersContrast ? 100 : 15, | ||||
|           prefersContrast ? scrollY * 3 : scrollY, | ||||
|         ) + "%", | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   window.addEventListener("load", () => { | ||||
|     // We can't use :has due to HtmlUnit CSS Parser not supporting it, so
 | ||||
|     // these are workarounds for that same behaviour
 | ||||
|     if (document.querySelector(".jenkins-app-bar--sticky")) { | ||||
|       document | ||||
|         .querySelector(".jenkins-header") | ||||
|         .classList.add("jenkins-header--has-sticky-app-bar"); | ||||
|     } | ||||
| 
 | ||||
|     if (!document.querySelector(".jenkins-breadcrumbs__list-item")) { | ||||
|       document | ||||
|         .querySelector(".jenkins-header") | ||||
|         .classList.add("jenkins-header--no-breadcrumbs"); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function computeOverflow() { | ||||
|   computeActions(); | ||||
|   computeBreadcrumbs(); | ||||
| } | ||||
| 
 | ||||
| init(); | ||||
|  | @ -5,6 +5,23 @@ const TOOLTIP_BASE = { | |||
|   arrow: false, | ||||
|   theme: "tooltip", | ||||
|   animation: "tooltip", | ||||
|   touch: false, | ||||
|   popperOptions: { | ||||
|     modifiers: [ | ||||
|       { | ||||
|         name: "preventOverflow", | ||||
|         options: { | ||||
|           boundary: "viewport", | ||||
|           padding: | ||||
|             parseFloat( | ||||
|               getComputedStyle(document.documentElement).getPropertyValue( | ||||
|                 "--section-padding", | ||||
|               ), | ||||
|             ) * 16, | ||||
|         }, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   duration: 250, | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import hotkeys from "hotkeys-js"; | |||
| 
 | ||||
| window.addEventListener("load", () => { | ||||
|   const openCommandPaletteButton = document.querySelector( | ||||
|     "#button-open-command-palette", | ||||
|     "#root-action-SearchAction", | ||||
|   ); | ||||
|   if (openCommandPaletteButton) { | ||||
|     hotkeys(translateModifierKeysForUsersPlatform("CMD+K"), () => { | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ $colors: ( | |||
|   "yellow": oklch(80% 0.17 76), | ||||
|   "teal": oklch(60% 0.1122 216.72), | ||||
|   "white": #fff, | ||||
|   "black": oklch(from var(--accent-color) 2% 0.075 h), | ||||
|   "black": oklch(from var(--accent-color) 5% 0.075 h), | ||||
| ); | ||||
| $semantics: ( | ||||
|   "accent": var(--blue), | ||||
|  | @ -71,16 +71,10 @@ $semantics: ( | |||
|   --background: var(--white); | ||||
| 
 | ||||
|   // Header | ||||
|   --brand-link-color: var(--secondary); | ||||
|   --header-link-color: var(--white); | ||||
|   --header-bg-classic: var(--black); | ||||
|   --header-link-bg-classic-hover: #404040; | ||||
|   --header-link-bg-classic-active: #404040; | ||||
| 
 | ||||
|   // Breadcrumbs bar | ||||
|   --breadcrumbs-bar-background: oklch( | ||||
|     from var(--text-color) 96.8% 0.005 h / 0.8 | ||||
|   ); | ||||
|   --header-background: var(--background); | ||||
|   --header-border: var(--text-color-secondary); | ||||
|   --header-color: var(--text-color); | ||||
|   --header-height: 4.125rem; | ||||
| 
 | ||||
|   // App bar | ||||
|   --bottom-app-bar-shadow: color-mix( | ||||
|  | @ -162,6 +156,7 @@ $semantics: ( | |||
|   } | ||||
| 
 | ||||
|   @media (prefers-contrast: more) { | ||||
|     --header-border: var(--text-color); | ||||
|     --focus-input-border: var(--text-color); | ||||
|     --jenkins-border-color: var(--text-color); | ||||
|     --jenkins-border-color--subtle: var(--text-color); | ||||
|  |  | |||
|  | @ -1,28 +1,5 @@ | |||
| @use "../base/breakpoints"; | ||||
| 
 | ||||
| /* --------------- header --------------- */ | ||||
| 
 | ||||
| #page-header .logo { | ||||
|   margin-left: 1.2rem; | ||||
|   display: inline-flex; | ||||
|   align-items: center; | ||||
|   position: relative; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| #jenkins-home-link { | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| #jenkins-head-icon { | ||||
|   height: 2.5rem; | ||||
| } | ||||
| 
 | ||||
| #jenkins-name-icon { | ||||
|   margin-left: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| /* -------------------------------------- */ | ||||
| 
 | ||||
| .app-page-body { | ||||
|  | @ -36,7 +13,7 @@ | |||
|   @media (min-width: breakpoints.$tablet-breakpoint) { | ||||
|     &--sticky { | ||||
|       position: sticky; | ||||
|       top: 44px; | ||||
|       top: var(--header-height); | ||||
|       align-self: flex-start; | ||||
|     } | ||||
|   } | ||||
|  | @ -53,7 +30,8 @@ | |||
| } | ||||
| 
 | ||||
| #main-panel { | ||||
|   padding: var(--section-padding); | ||||
|   padding: 0 var(--section-padding) var(--section-padding) | ||||
|     var(--section-padding); | ||||
|   display: inline-block; | ||||
|   width: 100%; | ||||
| } | ||||
|  |  | |||
|  | @ -51,16 +51,14 @@ | |||
| 
 | ||||
|   &--sticky { | ||||
|     position: sticky; | ||||
|     top: 40px; | ||||
|     padding-top: var(--section-padding); | ||||
|     margin-top: calc(var(--section-padding) * -1); | ||||
|     top: var(--header-height); | ||||
|     z-index: 2; | ||||
| 
 | ||||
|     &::before, | ||||
|     &::after { | ||||
|       content: ""; | ||||
|       position: absolute; | ||||
|       inset: 0 calc(var(--section-padding) * -1) | ||||
|       inset: calc(var(--header-height) * -1) calc(var(--section-padding) * -1) | ||||
|         calc(var(--section-padding) * -1); | ||||
|       z-index: -1; | ||||
|       pointer-events: none; | ||||
|  | @ -70,14 +68,10 @@ | |||
|       background: var(--background); | ||||
|       mask-image: linear-gradient(black 70%, transparent); | ||||
|       opacity: 0.55; | ||||
| 
 | ||||
|       @supports not (backdrop-filter: blur(15px)) { | ||||
|         opacity: 1; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &::after { | ||||
|       backdrop-filter: blur(15px); | ||||
|       backdrop-filter: blur(10px); | ||||
|       mask-image: linear-gradient(black 50%, transparent); | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -1,130 +1,91 @@ | |||
| @use "../abstracts/mixins"; | ||||
| 
 | ||||
| .jenkins-breadcrumbs { | ||||
|   position: sticky; | ||||
|   top: 0; | ||||
|   z-index: 999; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding: 0.55rem 0.7rem 0.55rem 0.75rem; | ||||
|   backdrop-filter: blur(15px); | ||||
|   overflow-x: auto; | ||||
|   background: var(--breadcrumbs-bar-background); | ||||
|   gap: 0.625rem; | ||||
|   margin-left: 0.625rem; | ||||
|   overflow: hidden; | ||||
| 
 | ||||
|   &__list { | ||||
|     display: contents; | ||||
|     list-style-type: none; | ||||
| 
 | ||||
|     & > * { | ||||
|       flex-shrink: 0; | ||||
|       z-index: 1; | ||||
|     } | ||||
| 
 | ||||
|     &-item { | ||||
|       position: relative; | ||||
|       display: inline-flex; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       color: var(--text-color); | ||||
|       font-weight: normal; | ||||
|       font-size: var(--font-size-sm); | ||||
|       padding: 0.2rem 0.4rem; | ||||
|       display: contents; | ||||
| 
 | ||||
|       a::before { | ||||
|         content: ""; | ||||
|         position: absolute; | ||||
|         inset: -0.5rem -0.75rem !important; | ||||
|         pointer-events: all !important; | ||||
|       } | ||||
| 
 | ||||
|       & > a { | ||||
|         @include mixins.item($border: false); | ||||
| 
 | ||||
|         display: inline-flex; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         font-weight: inherit; | ||||
|         font-size: inherit; | ||||
|         margin: 0; | ||||
|         padding: 0; | ||||
|         color: var(--text-color); | ||||
|         margin-right: 0 !important; | ||||
|         transition: var(--standard-transition); | ||||
|         --item-background: transparent; | ||||
|         --item-background--hover: transparent; | ||||
|         --item-background--active: transparent; | ||||
|         --item-box-shadow--focus: transparent; | ||||
| 
 | ||||
|         transition: opacity var(--standard-transition); | ||||
| 
 | ||||
|         &::before, | ||||
|         &::after { | ||||
|           inset: -0.25rem -0.6rem; | ||||
|           inset: -2px -6px; | ||||
|           border: none; | ||||
|         } | ||||
| 
 | ||||
|         &:hover, | ||||
|         &:active, | ||||
|         &:focus, | ||||
|         &:focus-visible { | ||||
|           color: var(--text-color); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       & > .model-link { | ||||
|         @media (hover: none) { | ||||
|           margin-right: 30px !important; | ||||
|         } | ||||
| 
 | ||||
|         &:hover, | ||||
|         &--open { | ||||
|           margin-right: 30px !important; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // '>' separator between two items | ||||
|     .children, | ||||
|     .separator { | ||||
|       position: relative; | ||||
|       width: 1rem; | ||||
|       height: 1rem; | ||||
|       margin: 0.375rem 0.2rem; | ||||
| 
 | ||||
|       &::after { | ||||
|         content: ""; | ||||
|         position: absolute; | ||||
|         inset: 0; | ||||
|         transition: var(--standard-transition); | ||||
|         background: var(--text-color-secondary); | ||||
|         mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'%3E%3Ctitle%3EChevron Forward%3C/title%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='48' d='M184 112l144 144-144 144'/%3E%3C/svg%3E"); | ||||
|         opacity: 0.6; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .separator { | ||||
|       &:last-of-type { | ||||
|         display: none; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .children { | ||||
|       cursor: pointer; | ||||
| 
 | ||||
|         &:hover { | ||||
|         &::after { | ||||
|           opacity: 1; | ||||
|           transform: rotate(90deg); | ||||
|         } | ||||
|           opacity: 0.7; | ||||
|         } | ||||
| 
 | ||||
|         &:active { | ||||
|         &::after { | ||||
|           transform: translateY(2px) rotate(90deg); | ||||
|           opacity: 0.5; | ||||
|           opacity: 0.4; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:active, | ||||
|       &:focus, | ||||
|       &:focus-visible { | ||||
|         &::after { | ||||
|           background: var(--text-color); | ||||
|         } | ||||
|       & > a, | ||||
|       span { | ||||
|         position: relative; | ||||
|         display: inline-flex; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         gap: 1rem; | ||||
|         font-size: var(--font-size-sm); | ||||
|         font-weight: var(--font-bold-weight); | ||||
|         margin: 0; | ||||
|         padding: 0; | ||||
|         color: inherit; | ||||
|         text-decoration: none; | ||||
|         text-wrap: nowrap; | ||||
|         flex-shrink: 0; | ||||
|       } | ||||
| 
 | ||||
|       // Increase the hit target | ||||
|       // '/' separator between two items | ||||
|       &::before { | ||||
|         content: ""; | ||||
|         position: absolute; | ||||
|         inset: -14px -5px; | ||||
|         background: transparent; | ||||
|         position: relative; | ||||
|         width: 1rem; | ||||
|         height: 1.25rem; | ||||
|         flex-shrink: 0; | ||||
|         mask-size: contain; | ||||
|         mask-position: center; | ||||
|         mask-repeat: no-repeat; | ||||
|         mask-image: url("data:image/svg+xml,%3Csvg width='10' height='20' viewBox='0 0 10 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 18L8 2' stroke='black' stroke-width='1.5px' stroke-linecap='round'/%3E%3C/svg%3E%0A"); | ||||
|         background: color-mix( | ||||
|           in sRGB, | ||||
|           var(--text-color-secondary) 30%, | ||||
|           transparent | ||||
|         ); | ||||
| 
 | ||||
|         @media (prefers-contrast: more) { | ||||
|           background: var(--text-color); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | @ -305,6 +266,7 @@ | |||
|       .jenkins-menu-dropdown-chevron { | ||||
|         &::after { | ||||
|           opacity: 0.5; | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ $dropdown-padding: 0.375rem; | |||
|   background: color-mix(in sRGB, var(--card-background) 85%, transparent); | ||||
|   backdrop-filter: var(--dropdown-backdrop-filter); | ||||
|   max-width: unset !important; | ||||
|   max-height: 75vh; | ||||
|   max-height: 60vh; | ||||
|   overflow-y: auto; | ||||
| 
 | ||||
|   .tippy-content { | ||||
|  |  | |||
|  | @ -1,94 +1,147 @@ | |||
| @use "../abstracts/mixins"; | ||||
| 
 | ||||
| .page-header { | ||||
| .jenkins-header { | ||||
|   --background-opacity: 0%; | ||||
|   --background-blur: 0; | ||||
|   --border-opacity: 0%; | ||||
| 
 | ||||
|   position: sticky; | ||||
|   top: 0; | ||||
|   z-index: 20; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   height: 3.5rem; | ||||
|   gap: var(--section-padding); | ||||
|   font-size: var(--font-size-base); | ||||
|   line-height: var(--line-height-base); | ||||
|   background-color: var(--header-bg-classic); | ||||
|   min-height: var(--header-height); | ||||
|   color: var(--header-color); | ||||
| 
 | ||||
|   &::before { | ||||
|     content: ""; | ||||
|     position: absolute; | ||||
|     inset: 0; | ||||
|     backdrop-filter: blur(var(--background-blur)); | ||||
|     background: color-mix( | ||||
|       in sRGB, | ||||
|       var(--header-background) var(--background-opacity), | ||||
|       transparent | ||||
|     ); | ||||
|     border-bottom: var(--jenkins-border-width) solid | ||||
|       color-mix( | ||||
|         in sRGB, | ||||
|         var(--header-border) var(--border-opacity), | ||||
|         transparent | ||||
|       ); | ||||
|     background-clip: padding-box; | ||||
|     z-index: -1; | ||||
|   } | ||||
| 
 | ||||
| .page-header > * { | ||||
|   margin-right: 0.75rem; | ||||
| } | ||||
|   .app-jenkins-logo { | ||||
|     @include mixins.item($border: false); | ||||
| 
 | ||||
| .page-header__brand { | ||||
|   display: inline-block; | ||||
|   height: 3.5rem; | ||||
|   position: relative; | ||||
|   flex: 1; // push controls to the end of the block | ||||
| } | ||||
|     --item-background: transparent; | ||||
|     --item-background--hover: transparent; | ||||
|     --item-background--active: transparent; | ||||
|     --item-box-shadow--focus: transparent; | ||||
| 
 | ||||
| // Need to use the element selector to increase weight otherwise it will be overriden by the | ||||
| // a:visited selector if it is declared later | ||||
| // Only styled by the overrides with the new UI enabled | ||||
| a.page-header__brand-link { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .page-header__brand-name { | ||||
|   color: inherit; | ||||
| } | ||||
| 
 | ||||
| .page-header__brand-image { | ||||
|   height: 2rem; | ||||
|   width: 1.5rem; | ||||
|   margin-right: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .page-header__am-wrapper { | ||||
|   display: contents; | ||||
| } | ||||
| 
 | ||||
| .page-header__hyperlinks { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     align-self: center; | ||||
|     gap: 1rem; | ||||
|     color: var(--header-color); | ||||
|     transition: opacity var(--standard-transition); | ||||
| 
 | ||||
|     &::before, | ||||
|     &::after { | ||||
|       inset: -2px -6px; | ||||
|       border: none; | ||||
|     } | ||||
| 
 | ||||
| .page-header__hyperlinks > a, | ||||
| .page-header__hyperlinks > button, | ||||
| .am-container > a { | ||||
|   @include mixins.item; | ||||
|     &:hover { | ||||
|       opacity: 0.7; | ||||
|     } | ||||
| 
 | ||||
|   --text-color: var(--header-link-color); | ||||
|     &:active { | ||||
|       opacity: 0.4; | ||||
|     } | ||||
| 
 | ||||
|   display: inline-flex; | ||||
|   align-items: center; | ||||
|   appearance: none; | ||||
|   background: transparent; | ||||
|   outline: none; | ||||
|   border: none; | ||||
|   cursor: pointer; | ||||
|   color: var(--text-color); | ||||
|   text-decoration: none; | ||||
|   padding: 0.5rem; | ||||
|   margin-right: 0 !important; | ||||
|     // Increase the hit-box for the logo for Fitts’s Law | ||||
|     &::before { | ||||
|       inset: -30px -1rem -1rem -100px !important; | ||||
|       pointer-events: all; | ||||
|     } | ||||
| 
 | ||||
|     span { | ||||
|       font-family: Georgia, serif; | ||||
|       font-weight: 600; | ||||
|       font-size: 1.125rem; | ||||
|     } | ||||
| 
 | ||||
|     #jenkins-head-icon { | ||||
|       height: 2.125rem; | ||||
|       margin-left: 0.15rem; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .jenkins-button { | ||||
|     min-width: 2.375rem; | ||||
|     min-height: 2.375rem; | ||||
|     padding: 0; | ||||
| 
 | ||||
|     // For customizable-header-plugin | ||||
|     color: inherit !important; | ||||
| 
 | ||||
|     svg { | ||||
|       width: 1.25rem; | ||||
|       height: 1.25rem; | ||||
|     } | ||||
| 
 | ||||
|   &::before, | ||||
|   &::after { | ||||
|     inset: 0 !important; | ||||
|     img { | ||||
|       width: 1.625rem; | ||||
|       height: 1.625rem; | ||||
|     } | ||||
| 
 | ||||
|   .jenkins-menu-dropdown-chevron { | ||||
|     position: relative; | ||||
|     top: unset !important; | ||||
|     right: unset !important; | ||||
|     margin-left: 0.5rem; | ||||
| 
 | ||||
|     &::after { | ||||
|       opacity: 1; | ||||
|     } | ||||
|     .jenkins-badge { | ||||
|       position: absolute; | ||||
|       top: calc(16%); | ||||
|       right: calc(16%); | ||||
|       min-width: 5px; | ||||
|       min-height: 5px; | ||||
|       padding: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| .page-header__hyperlinks a span { | ||||
|   &:not(:first-child) { | ||||
|     margin-left: 0.25rem; | ||||
|   &__main { | ||||
|     display: grid; | ||||
|     grid-template-columns: 1fr auto; | ||||
|     padding-left: var(--section-padding); | ||||
|     width: 100%; | ||||
|   } | ||||
| 
 | ||||
|   &__navigation { | ||||
|     display: grid; | ||||
|     grid-template-columns: auto 1fr; | ||||
|     align-items: stretch; | ||||
|   } | ||||
| 
 | ||||
|   &__actions { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     gap: 0.5rem; | ||||
|     padding-right: var(--section-padding); | ||||
|   } | ||||
| 
 | ||||
|   &--has-sticky-app-bar { | ||||
|     &::before { | ||||
|       mask-image: linear-gradient(black 50%, transparent); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &--no-breadcrumbs { | ||||
|     .app-jenkins-logo span { | ||||
|       display: block !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ $background-outset: 0.7rem; | |||
| .subtasks { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   margin: var(--section-padding); | ||||
|   margin: 0 var(--section-padding) var(--section-padding) var(--section-padding); | ||||
|   gap: 0.125rem; | ||||
| 
 | ||||
|   @media (min-width: breakpoints.$tablet-breakpoint) { | ||||
|  | @ -26,15 +26,12 @@ $background-outset: 0.7rem; | |||
| 
 | ||||
| #side-panel { | ||||
|   .jenkins-app-bar { | ||||
|     margin-top: var(--section-padding); | ||||
|     margin-left: var(--section-padding); | ||||
|     margin-right: var(--section-padding); | ||||
|   } | ||||
| 
 | ||||
|   & > #tasks > .jenkins-search-container { | ||||
|     margin-left: -$background-outset; | ||||
|     margin-right: -$background-outset; | ||||
|     margin-bottom: calc(var(--section-padding) / 2); | ||||
|     margin: calc(var(--section-padding) * -0.5) #{-$background-outset} 1rem; | ||||
| 
 | ||||
|     .jenkins-search__icon { | ||||
|       width: 2.8rem; | ||||
|  | @ -47,10 +44,6 @@ $background-outset: 0.7rem; | |||
|   } | ||||
| } | ||||
| 
 | ||||
| #side-panel .jenkins-search__results-container--visible .task-link { | ||||
|   opacity: 0.3; | ||||
| } | ||||
| 
 | ||||
| #tasks .task { | ||||
|   margin: 0 calc($background-outset * -1); | ||||
| } | ||||
|  |  | |||
|  | @ -145,13 +145,13 @@ public class HudsonTest { | |||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Top page should only have one item in the breadcrumb. | ||||
|      * Top page should have zero items in the breadcrumb. | ||||
|      */ | ||||
|     @Test | ||||
|     public void breadcrumb() throws Exception { | ||||
|         HtmlPage root = j.createWebClient().goTo(""); | ||||
|         DomElement navbar = root.getElementById("breadcrumbs"); | ||||
|         assertEquals(1, navbar.querySelectorAll(".jenkins-breadcrumbs__list-item").size()); | ||||
|         assertEquals(0, navbar.querySelectorAll(".jenkins-breadcrumbs__list-item").size()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -52,13 +52,13 @@ public class Security3349Test { | |||
|             assertEquals(403, adminViews.getWebResponse().getStatusCode()); | ||||
| 
 | ||||
|             HtmlPage adminUserPage = wc.goTo("user/admin/"); | ||||
|             assertFalse(adminUserPage.getWebResponse().getContentAsString().contains("My Views")); | ||||
|             assertFalse(adminUserPage.getVisibleText().contains("My Views")); | ||||
| 
 | ||||
|             HtmlPage userViews = wc.goTo("user/user/my-views/view/all/"); | ||||
|             assertEquals(200, userViews.getWebResponse().getStatusCode()); | ||||
| 
 | ||||
|             HtmlPage userUserPage = wc.goTo("user/user/"); | ||||
|             assertTrue(userUserPage.getWebResponse().getContentAsString().contains("My Views")); | ||||
|             assertTrue(userUserPage.getVisibleText().contains("My Views")); | ||||
| 
 | ||||
|             wc.login("admin"); | ||||
| 
 | ||||
|  | @ -73,7 +73,7 @@ public class Security3349Test { | |||
|             adminViews = wc.goTo("user/admin/my-views/view/all/"); | ||||
|             assertEquals(200, adminViews.getWebResponse().getStatusCode()); | ||||
|             adminUserPage = wc.goTo("user/admin/"); | ||||
|             assertTrue(adminUserPage.getWebResponse().getContentAsString().contains("My Views")); | ||||
|             assertTrue(adminUserPage.getVisibleText().contains("My Views")); | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -67,7 +67,7 @@ public class SearchTest { | |||
|     @Rule public JenkinsRule j = new JenkinsRule(); | ||||
| 
 | ||||
|     private void searchWithoutNavigating(HtmlPage page, String query) throws IOException { | ||||
|         HtmlButton button = page.querySelector("#button-open-command-palette"); | ||||
|         HtmlButton button = page.querySelector("#root-action-SearchAction"); | ||||
|         button.click(); | ||||
| 
 | ||||
|         HtmlInput search = page.querySelector("#command-bar"); | ||||
|  |  | |||
|  | @ -1,6 +0,0 @@ | |||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core"> | ||||
|   <div> | ||||
|     <b id='bravo'>Bravo</b> | ||||
|   </div> | ||||
| </j:jelly> | ||||
|  | @ -1 +1,5 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Person Circle</title><path fill="currentColor" d="M258.9 48C141.92 46.42 46.42 141.92 48 258.9c1.56 112.19 92.91 203.54 205.1 205.1 117 1.6 212.48-93.9 210.88-210.88C462.44 140.91 371.09 49.56 258.9 48zm126.42 327.25a4 4 0 01-6.14-.32 124.27 124.27 0 00-32.35-29.59C321.37 329 289.11 320 256 320s-65.37 9-90.83 25.34a124.24 124.24 0 00-32.35 29.58 4 4 0 01-6.14.32A175.32 175.32 0 0180 259c-1.63-97.31 78.22-178.76 175.57-179S432 158.81 432 256a175.32 175.32 0 01-46.68 119.25z"/><path fill="currentColor" d="M256 144c-19.72 0-37.55 7.39-50.22 20.82s-19 32-17.57 51.93C191.11 256 221.52 288 256 288s64.83-32 67.79-71.24c1.48-19.74-4.8-38.14-17.68-51.82C293.39 151.44 275.59 144 256 144z"/></svg> | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"> | ||||
|   <path fill="currentColor" stroke="none" d="M 259.24942 23.021637 C 128.221512 21.251862 21.253086 128.220276 23.022827 259.24823 C 24.770166 384.910919 127.090225 487.230988 252.75293 488.978302 C 383.803253 490.770447 490.749237 383.802032 488.957123 252.774109 C 487.232178 127.08902 384.912109 24.768951 259.24942 23.021637 Z M 400.850983 389.570496 C 399.947693 390.547302 398.656952 391.072144 397.328308 391.002899 C 395.999664 390.933685 394.770477 390.277466 393.973633 389.212036 C 383.955017 376.104187 371.685822 364.881744 357.73877 356.068665 C 329.221344 337.766418 293.08728 327.685608 256.00116 327.685608 C 218.915054 327.685608 182.781006 337.766418 154.263565 356.068665 C 140.317032 364.877808 128.047821 376.096497 118.028717 389.200867 C 117.231857 390.266296 116.002693 390.922485 114.674026 390.99173 C 113.345375 391.060944 112.054665 390.536102 111.151367 389.559265 C 78.284332 354.078979 59.666458 307.717773 58.86565 259.360229 C 57.039909 150.364441 146.478943 59.13327 255.519547 58.864441 C 364.56012 58.595642 453.136688 147.13858 453.136688 255.999969 C 453.174255 305.523621 434.498657 353.232788 400.850983 389.570496 Z"/> | ||||
|   <path fill="currentColor" stroke="none" d="M 256 144 C 236.279999 144 218.449997 151.390015 205.779999 164.820007 C 193.110001 178.25 186.779999 196.820007 188.210007 216.75 C 191.110001 256 221.520004 288 256 288 C 290.480011 288 320.829987 256 323.790009 216.76001 C 325.269989 197.02002 318.98999 178.619995 306.109985 164.940002 C 293.390015 151.440002 275.589996 144 256 144 Z"/> | ||||
| </svg> | ||||
|  |  | |||
| Before Width: | Height: | Size: 780 B After Width: | Height: | Size: 1.6 KiB | 
|  | @ -29,6 +29,7 @@ module.exports = (env, argv) => ({ | |||
|       ), | ||||
|     ], | ||||
|     app: [path.join(__dirname, "src/main/js/app.js")], | ||||
|     header: [path.join(__dirname, "src/main/js/components/header/index.js")], | ||||
|     "pages/cloud-set": [ | ||||
|       path.join(__dirname, "src/main/js/pages/cloud-set/index.js"), | ||||
|       path.join(__dirname, "src/main/js/pages/cloud-set/index.scss"), | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue