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 commit 2ea0b1f867c3f3ae446c81ae780a8eb8a28c3d88.
* 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:
commit 847981ebcb6ae6aaac5463602cb185e2ade40200
Merge: 0ea6dcff0e 23f2b9ef59
Author: Kris Stern <krisstern@outlook.com>
Date: Wed Feb 26 09:16:52 2025 +0800
Merge branch 'master' into add-groups-to-command-palette
commit 0ea6dcff0e791c2e63d3da5b942e0524e1dc9620
Merge: c0777dbe79 68425e2cd4
Author: Kris Stern <krisstern@outlook.com>
Date: Wed Feb 26 01:06:33 2025 +0800
Merge branch 'master' into add-groups-to-command-palette
commit c0777dbe79b022dda8eb6f4fff117cf39064ee2b
Merge: 1638afe17e c37293c52d
Author: 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
commit 1638afe17eaabc2d0cfe2a1b9240cd40df8ce4d6
Merge: c987a9e536 b97764d3fd
Author: 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
commit c987a9e536a56c5815d868def8c5c6a3dbf1f2d6
Merge: f909eec0d4 16748f4413
Author: 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
commit f909eec0d49fb71e81ceeecf5a81f4d5159a16d3
Merge: 85eedb7e88 217b0f5742
Author: 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
commit 85eedb7e88c38f0ae872f2f0a0c154162cf5c455
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Feb 19 16:11:24 2025 +0000
Move to Item
commit 8f4f117bac52988dc2a3e11602159304bb43fd2c
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Feb 19 15:43:50 2025 +0000
Tighten up animations + improve contrast
commit d7b7d6388deb477ba0cfb07588cff9f87e144c1d
Merge: 8750f7cb92 4fa61274f9
Author: 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
commit 8750f7cb922e97fdb621fec6bff376786863db14
Merge: 7b527340a2 a05c33f797
Author: 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
commit 7b527340a27e81af95343e70fce8ad1a610f0850
Merge: e2c133d128 3505fb3540
Author: 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
commit e2c133d1283e7e444fd49e21a26753f2085e925c
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sun Feb 16 18:23:14 2025 +0000
Update require-changelog-label.yml
commit d32a61c1eabdb66b0d8d5e3300c714de484c8989
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sun Feb 16 17:11:18 2025 +0000
Update _theme.scss
commit 42ecfcac5c555800d3b24fe6af6705e62073ddec
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sun Feb 16 17:10:48 2025 +0000
Rename to Items
commit cc3779171a89988d2755092cd6b490be9b3a9b86
Merge: 0f1cb2187c 2b9d4d62a6
Author: 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
commit 0f1cb2187c66967a209e7cca34a6faf2775e6690
Merge: 04dc6cd222 9474c89bf1
Author: 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
commit 04dc6cd2225f4635a5f91c648d9e3dbbb0a7350d
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Tue Feb 11 17:06:31 2025 +0000
Reduce spacing a touch, fix icon spacing
commit 0ab3665587aec8a2c6d458bc394594299fecbbf2
Merge: 7c9e172b2f 848ac9b66a
Author: 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
commit 7c9e172b2f427ffd9b1a85594615261bea327477
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Tue Feb 11 11:23:27 2025 +0000
Update Messages.properties
commit ec6a5e5ee01879fe817a6827ed42e238f6d46217
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Tue Feb 11 08:51:16 2025 +0000
Fix test
commit 14a64885a2284883d86f2af8f7ce4745d34511db
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Mon Feb 10 16:44:39 2025 +0000
Tidy up
commit 46a9e5681a98d86914123c50cec31ea6d3d01d0c
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Mon Feb 10 16:28:15 2025 +0000
Tidy
commit d7270b1fa49b27c2005e86807400f033969d7a41
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Mon Feb 10 16:06:04 2025 +0000
Tidy
commit b2da3f8d39c2c606c293919562528732390f386c
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Mon Feb 10 16:03:37 2025 +0000
Tidy up
commit b746fba008faf4784747c7494a4995628c02726e
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Mon Feb 10 15:53:53 2025 +0000
Move to extensionpoint
commit 7827304ae14cafbfc1f1500dea16881d6e4dec6a
Merge: cac127d119 d03a2e11c9
Author: 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
commit cac127d1196eeda50d15457384bf57009e7a0f41
Merge: add75bf6a9 e3e3c45270
Author: 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
commit add75bf6a9605ab61bf66b9e5e736803cb836d93
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sat Jan 11 19:02:52 2025 +0000
Update _command-palette.scss
commit eb4073f4fb74217d6aaf1ad960151e10318b67f0
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sat Jan 11 18:59:37 2025 +0000
Tidy up
commit 323e48fddfa079550c040066272a50efdcc0ede6
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sat Jan 11 18:47:34 2025 +0000
Update Job.java
commit 3cbdfbc4b5d3981c7c69c3c742127d11972e13c0
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sat Jan 11 18:47:13 2025 +0000
Update _command-palette.scss
commit 8fecf0d88053b43953d25d0430e2662b8e4ec02e
Merge: 428e826fcd 331c7685ca
Author: 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
commit 428e826fcd1597f216b8534414aba45556e3ac03
Merge: 5657369d95 f1b6d31272
Author: 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
commit 5657369d9556231595c20aa2c7c8e9b3d0ae0f7c
Merge: 26f17a277f 674d5085c3
Author: 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
commit 674d5085c333e3f580279ae0dbb8ce032b45c1b9
Merge: 809d2e6120 7020e80af8
Author: 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
commit 26f17a277f39cb27528bd647db266174a95b356e
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Dec 11 22:10:56 2024 +0000
Update _command-palette.scss
commit 2b6ffc85f33c17a095dffc770b5c383293d5d261
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Dec 11 22:09:09 2024 +0000
Init
commit 809d2e6120b36093c60ab88e2794d14fb3aaa5ea
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Dec 11 21:37:47 2024 +0000
Make iconXml private, rename to icon
commit 3d45ca7c3926586e17f8aed1f31f556109e63ea2
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Dec 11 21:29:27 2024 +0000
Add group field
commit 80f24cbfdcbadd52801c987e75cffec761df8fc6
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Dec 11 21:25:23 2024 +0000
Init
commit 1b9faa8fb0c129a1543b97aae2de55f676f2cecc
Merge: d6868c970a 26738449cd
Author: 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
commit d6868c970a06a17633cf7d145beef28598730a0b
Author: Tim Jacomb <timjacomb1@gmail.com>
Date: Wed Dec 11 21:11:41 2024 +0000
Reword javadoc
commit 26738449cd62ea3c007503c0bebff4872b1fc14b
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Wed Dec 11 21:03:29 2024 +0000
Implement IconSpec in IComputer
commit 57910109f32aa4cb2cf81188c12534f676e5a4ac
Merge: 661f994783 05ed7560fd
Author: 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
commit 661f994783b96c867b2b0a618e66c603d5384b40
Merge: 23570203ea dad5ef3266
Author: 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
commit 23570203ea4eace9656825f7fb2e7233cdd59592
Merge: 436a02b9d3 788ae63c50
Author: 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
commit 436a02b9d307183e6cdedca66ec565f639896474
Author: Tim Jacomb <timjacomb1@gmail.com>
Date: Wed Dec 11 16:35:24 2024 +0000
Add support for images
commit a3fdb3e0c7df55fdf2876dc71ad0e3cda13289c5
Merge: ea67d6a554 d22cc2fa3c
Author: Tim Jacomb <timjacomb1@gmail.com>
Date: Wed Dec 11 15:27:12 2024 +0000
Merge branch 'master' into add-icons-to-command-palette
commit 788ae63c5077057f5c53cf29cb61e2bbda5facb5
Merge: ea67d6a554 d22cc2fa3c
Author: 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
commit dad5ef3266cf3a188a78f42c36eaa4c381e5bbf4
Merge: cc63c9c8e5 d22cc2fa3c
Author: Tim Jacomb <21194782+timja@users.noreply.github.com>
Date: Wed Dec 11 09:07:05 2024 +0000
Merge branch 'master' into refine-command-palette
commit ea67d6a554417f3a976918b242b78ee3a816b2a9
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Tue Dec 10 22:26:16 2024 +0000
Update Search.java
commit a9aadbab3068e2f456866523f94ff88fa9052184
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Tue Dec 10 22:25:40 2024 +0000
Revert "Update Search.java"
This reverts commit 24837ea667183f189ee0ab73e86cb8cda58c8fe2.
commit 24837ea667183f189ee0ab73e86cb8cda58c8fe2
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Tue Dec 10 21:59:03 2024 +0000
Update Search.java
commit d43a8d3b2f4121e234ef16a7b4723e271cd9c43e
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Tue Dec 10 21:54:23 2024 +0000
Init
commit cc63c9c8e50a27b78874ac67f48584e232c4e1b5
Author: 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 commit 25647d6a040fee15378ed4821cfde2cff29a015a.
* 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:
commit 5060044fcd4a0edf7083906a542f7ed5d4066fc3
Merge: 0ea3e49fa1 2fb523ffe3
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sat Mar 22 17:56:53 2025 +0000
Merge branch 'master' into improve-tooltips-dropdowns
commit 0ea3e49fa11dfe53d21fbef283d3125dcc6f521c
Merge: 3dd0b9f421 a1f9d3e7e2
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Sat Mar 22 12:24:49 2025 +0000
Merge branch 'master' into improve-tooltips-dropdowns
commit 3dd0b9f421b8921a4c5624af8596d6c2f6434441
Merge: 7f5f814aa5 73185b257d
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Fri Mar 21 09:57:52 2025 +0000
Merge branch 'master' into improve-tooltips-dropdowns
commit 7f5f814aa543ca93e49cc95bd7c0281d844714c5
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Thu Mar 20 10:26:32 2025 +0000
Update _dropdowns.scss
commit e9eee3c0a4548de82832c8ea45c72db4eab4418e
Author: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Thu Mar 20 10:25:35 2025 +0000
Update _theme.scss
commit ce11fd1fb33492fdb8ced65c8ce74821eb8f5e6f
Author: 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;
|
||||
}
|
||||
}
|
96
core/src/main/java/jenkins/model/navigation/UserAction.java
Normal file
96
core/src/main/java/jenkins/model/navigation/UserAction.java
Normal file
@ -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" />
|
||||
@ -52,58 +47,58 @@ THE SOFTWARE.
|
||||
<h2 class="jenkins-section__title">${category.key.label}</h2>
|
||||
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.addHeader(category.key.label) : null}
|
||||
<div class="jenkins-section__items">
|
||||
<j:forEach var="m" items="${category.value}">
|
||||
<j:if test="${m.iconFileName != null}">
|
||||
<div class="jenkins-section__item">
|
||||
<j:set var="alt" value="${icon.replaceAll('\\d*\\.[^.]+$', '')}"/>
|
||||
<j:set var="iconSrc" value="${h.tryGetIconPath(m.iconFileName, context)}"/>
|
||||
<j:set var="sure" value="${%sure}"/>
|
||||
<j:set var="iconXml">
|
||||
<l:icon src="${m.iconFileName}" />
|
||||
</j:set>
|
||||
${taskTags!=null and attrs.contextMenu!='false' ? it.addContextMenuItem(taskTags, m.urlName, iconSrc, iconXml, m.displayName, m.requiresPOST, m.requiresConfirmation, m.badge, sure) : null}
|
||||
<j:choose>
|
||||
<j:when test="${m.requiresConfirmation}">
|
||||
<l:confirmationLink href="${m.urlName}" post="${m.requiresPOST}" message="${%sure}" title="${m.displayName}">
|
||||
<div class="jenkins-section__item__icon" aria-hidden="true">
|
||||
<l:icon src="${m.iconFileName}" class="icon" />
|
||||
</div>
|
||||
<dl>
|
||||
<dt>${m.displayName}</dt>
|
||||
<dd><j:out value="${m.description}"/></dd>
|
||||
<dd><st:include it="${m}" page="info.jelly" optional="true"/></dd>
|
||||
</dl>
|
||||
</l:confirmationLink>
|
||||
</j:when>
|
||||
<j:when test="${m.requiresPOST}">
|
||||
<f:link href="${m.urlName}" post="${m.requiresPOST}">
|
||||
<div class="jenkins-section__item__icon" aria-hidden="true">
|
||||
<l:icon src="${m.iconFileName}" class="icon" />
|
||||
</div>
|
||||
<dl>
|
||||
<dt>${m.displayName}</dt>
|
||||
<dd><j:out value="${m.description}"/></dd>
|
||||
<dd><st:include it="${m}" page="info.jelly" optional="true"/></dd>
|
||||
</dl>
|
||||
</f:link>
|
||||
</j:when>
|
||||
<j:otherwise>
|
||||
<a href="${m.urlName}">
|
||||
<div class="jenkins-section__item__icon" aria-hidden="true">
|
||||
<l:icon src="${m.iconFileName}" class="icon" />
|
||||
<l:badge badge="${m.badge}" class="jenkins-section__item__icon__badge"/>
|
||||
</div>
|
||||
<dl>
|
||||
<dt>${m.displayName}</dt>
|
||||
<dd><j:out value="${m.description}"/></dd>
|
||||
<dd><st:include it="${m}" page="info.jelly" optional="true"/></dd>
|
||||
</dl>
|
||||
</a>
|
||||
</j:otherwise>
|
||||
</j:choose>
|
||||
</div>
|
||||
</j:if>
|
||||
</j:forEach>
|
||||
<j:forEach var="m" items="${category.value}">
|
||||
<j:if test="${m.iconFileName != null}">
|
||||
<div class="jenkins-section__item">
|
||||
<j:set var="alt" value="${icon.replaceAll('\\d*\\.[^.]+$', '')}"/>
|
||||
<j:set var="iconSrc" value="${h.tryGetIconPath(m.iconFileName, context)}"/>
|
||||
<j:set var="sure" value="${%sure}"/>
|
||||
<j:set var="iconXml">
|
||||
<l:icon src="${m.iconFileName}" />
|
||||
</j:set>
|
||||
${taskTags!=null and attrs.contextMenu!='false' ? it.addContextMenuItem(taskTags, m.urlName, iconSrc, iconXml, m.displayName, m.requiresPOST, m.requiresConfirmation, m.badge, sure) : null}
|
||||
<j:choose>
|
||||
<j:when test="${m.requiresConfirmation}">
|
||||
<l:confirmationLink href="${m.urlName}" post="${m.requiresPOST}" message="${%sure}" title="${m.displayName}">
|
||||
<div class="jenkins-section__item__icon" aria-hidden="true">
|
||||
<l:icon src="${m.iconFileName}" class="icon" />
|
||||
</div>
|
||||
<dl>
|
||||
<dt>${m.displayName}</dt>
|
||||
<dd><j:out value="${m.description}"/></dd>
|
||||
<dd><st:include it="${m}" page="info.jelly" optional="true"/></dd>
|
||||
</dl>
|
||||
</l:confirmationLink>
|
||||
</j:when>
|
||||
<j:when test="${m.requiresPOST}">
|
||||
<f:link href="${m.urlName}" post="${m.requiresPOST}">
|
||||
<div class="jenkins-section__item__icon" aria-hidden="true">
|
||||
<l:icon src="${m.iconFileName}" class="icon" />
|
||||
</div>
|
||||
<dl>
|
||||
<dt>${m.displayName}</dt>
|
||||
<dd><j:out value="${m.description}"/></dd>
|
||||
<dd><st:include it="${m}" page="info.jelly" optional="true"/></dd>
|
||||
</dl>
|
||||
</f:link>
|
||||
</j:when>
|
||||
<j:otherwise>
|
||||
<a href="${m.urlName}">
|
||||
<div class="jenkins-section__item__icon" aria-hidden="true">
|
||||
<l:icon src="${m.iconFileName}" class="icon" />
|
||||
<l:badge badge="${m.badge}" class="jenkins-section__item__icon__badge"/>
|
||||
</div>
|
||||
<dl>
|
||||
<dt>${m.displayName}</dt>
|
||||
<dd><j:out value="${m.description}"/></dd>
|
||||
<dd><st:include it="${m}" page="info.jelly" optional="true"/></dd>
|
||||
</dl>
|
||||
</a>
|
||||
</j:otherwise>
|
||||
</j:choose>
|
||||
</div>
|
||||
</j:if>
|
||||
</j:forEach>
|
||||
</div>
|
||||
</section>
|
||||
</j:forEach>
|
||||
|
@ -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/>
|
||||
</header>
|
||||
<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}"
|
||||
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 test="${anc.object != app}">
|
||||
<l:breadcrumb title="${anc.object.displayName}"
|
||||
href="${anc.url}/"
|
||||
hasMenu="${h.isModelWithContextMenu(anc.object)}" />
|
||||
</j:if>
|
||||
</j:if>
|
||||
</j:forEach>
|
||||
|
||||
<!-- render additional breadcrumb items -->
|
||||
<j:out value="${contents}" />
|
||||
<d:invokeBody />
|
||||
</ol>
|
||||
</div>
|
||||
</j:jelly>
|
||||
|
70
core/src/main/resources/lib/layout/header/actions.jelly
Normal file
70
core/src/main/resources/lib/layout/header/actions.jelly
Normal file
@ -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: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>
|
||||
<st:include it="${app.securityRealm}" page="loginLink.jelly" />
|
||||
</j:otherwise>
|
||||
</j:choose>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
|
||||
<j:if test="${app.useSecurity}">
|
||||
<j:if test="${h.isAnonymous()}">
|
||||
<st:include it="${app.securityRealm}" page="loginLink.jelly" />
|
||||
</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) => {
|
||||
const elements =
|
||||
element.nextElementSibling.content.children[0].children;
|
||||
const mappedItems = Utils.convertHtmlToItems(elements);
|
||||
Utils.generateDropdown(
|
||||
element,
|
||||
(instance) => {
|
||||
const elements =
|
||||
element.nextElementSibling.content.children[0].children;
|
||||
const mappedItems = Utils.convertHtmlToItems(elements);
|
||||
|
||||
instance.setContent(Utils.generateDropdownItems(mappedItems));
|
||||
});
|
||||
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,43 +11,50 @@ 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(), {
|
||||
hideOnClick:
|
||||
element.dataset["hideOnClick"] !== "false" ? "toggle" : false,
|
||||
onCreate(instance) {
|
||||
const onload = () => {
|
||||
if (instance.loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const isClickInAnyDropdown =
|
||||
!!event.target.closest("[data-tippy-root]");
|
||||
const isClickOnReference = instance.reference.contains(
|
||||
event.target,
|
||||
);
|
||||
|
||||
if (!isClickInAnyDropdown && !isClickOnReference) {
|
||||
instance.hide();
|
||||
Object.assign(
|
||||
{},
|
||||
Templates.dropdown(),
|
||||
{
|
||||
hideOnClick:
|
||||
element.dataset["hideOnClick"] !== "false" ? "toggle" : false,
|
||||
onCreate(instance) {
|
||||
const onload = () => {
|
||||
if (instance.loaded) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
callback(instance);
|
||||
};
|
||||
if (immediate) {
|
||||
onload();
|
||||
} else {
|
||||
instance.reference.addEventListener("mouseenter", onload);
|
||||
}
|
||||
document.addEventListener("click", (event) => {
|
||||
const isClickInAnyDropdown =
|
||||
!!event.target.closest("[data-tippy-root]");
|
||||
const isClickOnReference = instance.reference.contains(
|
||||
event.target,
|
||||
);
|
||||
|
||||
if (!isClickInAnyDropdown && !isClickOnReference) {
|
||||
instance.hide();
|
||||
}
|
||||
});
|
||||
|
||||
callback(instance);
|
||||
};
|
||||
if (immediate) {
|
||||
onload();
|
||||
} else {
|
||||
["mouseenter", "focus"].forEach((event) => {
|
||||
instance.reference.addEventListener(event, onload);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
133
src/main/js/components/header/actions-overflow.js
Normal file
133
src/main/js/components/header/actions-overflow.js
Normal file
@ -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();
|
||||
}
|
||||
}
|
96
src/main/js/components/header/breadcrumbs-overflow.js
Normal file
96
src/main/js/components/header/breadcrumbs-overflow.js
Normal file
@ -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();
|
||||
}
|
||||
}
|
66
src/main/js/components/header/index.js
Normal file
66
src/main/js/components/header/index.js
Normal file
@ -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);
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
& > .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);
|
||||
}
|
||||
& > 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;
|
||||
}
|
||||
|
||||
&:active {
|
||||
&::after {
|
||||
transform: translateY(2px) rotate(90deg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
&::after {
|
||||
background: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
.page-header > * {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.page-header__brand {
|
||||
display: inline-block;
|
||||
height: 3.5rem;
|
||||
position: relative;
|
||||
flex: 1; // push controls to the end of the block
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
.page-header__hyperlinks > a,
|
||||
.page-header__hyperlinks > button,
|
||||
.am-container > a {
|
||||
@include mixins.item;
|
||||
|
||||
--text-color: var(--header-link-color);
|
||||
|
||||
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;
|
||||
|
||||
svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
&::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;
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
inset: 0 !important;
|
||||
}
|
||||
.app-jenkins-logo {
|
||||
@include mixins.item($border: false);
|
||||
|
||||
.jenkins-menu-dropdown-chevron {
|
||||
position: relative;
|
||||
top: unset !important;
|
||||
right: unset !important;
|
||||
margin-left: 0.5rem;
|
||||
--item-background: transparent;
|
||||
--item-background--hover: transparent;
|
||||
--item-background--active: transparent;
|
||||
--item-box-shadow--focus: transparent;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-self: center;
|
||||
gap: 1rem;
|
||||
color: var(--header-color);
|
||||
transition: opacity var(--standard-transition);
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
opacity: 1;
|
||||
inset: -2px -6px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 1.625rem;
|
||||
height: 1.625rem;
|
||||
}
|
||||
|
||||
.jenkins-badge {
|
||||
position: absolute;
|
||||
top: calc(16%);
|
||||
right: calc(16%);
|
||||
min-width: 5px;
|
||||
min-height: 5px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-header__hyperlinks a span {
|
||||
&:not(:first-child) {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
@ -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…
x
Reference in New Issue
Block a user