Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign the Jenkins header #10245

Draft
wants to merge 111 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 93 commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
5a0eee2
WB
janfaracik Jan 16, 2025
c9c8669
Push
janfaracik Jan 16, 2025
82b85a2
More responsive
janfaracik Jan 16, 2025
3d08ab8
Update headerContent.jelly
janfaracik Jan 16, 2025
f04343b
Push
janfaracik Jan 16, 2025
6fbb8e8
Update logo.jelly
janfaracik Jan 16, 2025
6fbabad
Update _page-header.scss
janfaracik Jan 16, 2025
363b63a
Update _page-header.scss
janfaracik Jan 16, 2025
278f8c5
Update header
janfaracik Jan 16, 2025
0b1eb50
Merge branch 'master' into new-header
janfaracik Jan 16, 2025
46b4296
Merge branch 'master' into new-header
janfaracik Jan 18, 2025
53d1f72
Tidy up breadcrumbs
janfaracik Jan 18, 2025
ae239df
Tidy up focus
janfaracik Jan 18, 2025
d85458f
Update _breadcrumbs.scss
janfaracik Jan 18, 2025
cd3c384
Update configure.jelly
janfaracik Jan 18, 2025
a5e3d54
push
janfaracik Jan 18, 2025
5b340a8
Add badges
janfaracik Jan 18, 2025
73b81a3
Tidy
janfaracik Jan 18, 2025
a04b8f2
Push
janfaracik Jan 18, 2025
82a3d80
Update headerContent.jelly
janfaracik Jan 18, 2025
1123309
Update ManageJenkinsAction.java
janfaracik Jan 18, 2025
d5c986f
Update headerContent.jelly
janfaracik Jan 18, 2025
25f04d9
Working!
janfaracik Jan 18, 2025
244f48d
WB
janfaracik Jan 18, 2025
abe4770
Tidy up
janfaracik Jan 18, 2025
3970e8d
Fixes
janfaracik Jan 18, 2025
be07296
Update sidepanel.jelly
janfaracik Jan 18, 2025
7134580
Lint
janfaracik Jan 19, 2025
cf56f45
Tidy up
janfaracik Jan 19, 2025
21c74a7
Update ManageJenkinsAction.java
janfaracik Jan 19, 2025
aab79d0
Simplify
janfaracik Jan 19, 2025
5ddd415
Update _side-panel-tasks.scss
janfaracik Jan 19, 2025
5c8f872
Merge branch 'simplify-search' into new-header
janfaracik Jan 19, 2025
181d9d2
Update _side-panel-tasks.scss
janfaracik Jan 19, 2025
46a9c35
Update UserAction.java
janfaracik Jan 19, 2025
bb7b73f
Update Jenkins.java
janfaracik Jan 19, 2025
7131e9a
Add border to account image
janfaracik Jan 19, 2025
15fc210
Tidy up avatars
janfaracik Jan 19, 2025
9b5dd43
Merge branch 'master' into new-header
janfaracik Jan 20, 2025
a309687
Update _side-panel-tasks.scss
janfaracik Jan 20, 2025
564f050
Init
janfaracik Jan 20, 2025
b447e27
Tidy up
janfaracik Jan 20, 2025
0ebc7f1
Merge branch 'circle-avatar' into new-header
janfaracik Jan 20, 2025
77b0baa
Merge branch 'improve-dialog' into new-header
janfaracik Jan 20, 2025
c2fb2ad
Merge branch 'rename-jenkins-in-tab-title' into new-header
janfaracik Jan 20, 2025
a800200
Merge branch 'master' into new-header
janfaracik Jan 22, 2025
60e0a00
Hide behind flag
janfaracik Jan 23, 2025
9cc86bd
Update sidepanel.jelly
janfaracik Jan 23, 2025
b403502
Push
janfaracik Jan 23, 2025
d89c24c
Tidy up
janfaracik Jan 23, 2025
6bacea0
Update logo.jelly
janfaracik Jan 23, 2025
ac72328
Merge branch 'master' into new-header
janfaracik Jan 25, 2025
1becb26
Accessibility
janfaracik Jan 26, 2025
7a42953
Update _breadcrumbs-new.scss
janfaracik Jan 26, 2025
f76088b
Fix dropdown theme
janfaracik Jan 27, 2025
7c16c18
Update _breadcrumbs.scss
janfaracik Jan 29, 2025
dc7a46d
Update _header.scss
janfaracik Jan 29, 2025
e5c3670
Update ManageJenkinsAction.java
janfaracik Jan 30, 2025
3397f39
Remove flag
janfaracik Jan 30, 2025
79c1280
Tidy up
janfaracik Jan 30, 2025
2ea0b1f
Update with HeaderAction
janfaracik Jan 31, 2025
bee9422
Merge branch 'master' into new-header
janfaracik Feb 1, 2025
19b4f27
Revert "Update with HeaderAction"
janfaracik Feb 1, 2025
0875038
Tidy
janfaracik Feb 1, 2025
a9c648d
Update RootAction.java
janfaracik Feb 1, 2025
9feda44
Update _breadcrumbs.scss
janfaracik Feb 1, 2025
56a808f
Push
janfaracik Feb 1, 2025
025a596
Update _header.scss
janfaracik Feb 1, 2025
1e67e70
Update _header.scss
janfaracik Feb 1, 2025
4a60bb7
Fix invisible actions not actually being invisible, make avatar huge …
janfaracik Feb 1, 2025
4a3f71a
Tidy
janfaracik Feb 1, 2025
abdb235
Push
janfaracik Feb 1, 2025
950cdee
Fix breadcrumbs + notification
janfaracik Feb 2, 2025
5bd87dc
Update jumplist.jelly
janfaracik Feb 2, 2025
ef847c9
Getting there 🚀
janfaracik Feb 2, 2025
49cb8cc
WB
janfaracik Feb 2, 2025
18677f1
Update index.jelly
janfaracik Feb 3, 2025
eed89b7
Update headerContent.jelly
janfaracik Feb 3, 2025
c6324ca
Responsive
janfaracik Feb 3, 2025
76b9bcf
Push
janfaracik Feb 3, 2025
857885c
Push
janfaracik Feb 3, 2025
6752284
Push
janfaracik Feb 3, 2025
062636c
Update index.js
janfaracik Feb 3, 2025
d586b51
Push
janfaracik Feb 3, 2025
e2d75a5
Tidy up
janfaracik Feb 3, 2025
4a2cd7e
Tidy up
janfaracik Feb 3, 2025
668871b
Tidy
janfaracik Feb 3, 2025
575632f
Update logo.jelly
janfaracik Feb 3, 2025
a222707
Delete NewHeaderUserExperimentalFlag.java
janfaracik Feb 3, 2025
a8a8af7
Merge branch 'master' into new-header
janfaracik Feb 6, 2025
def576a
Lint
janfaracik Feb 6, 2025
6066472
Update index.js
janfaracik Feb 6, 2025
32ac982
Update index.js
janfaracik Feb 6, 2025
e8903b8
Fix some tests
janfaracik Feb 6, 2025
531e1b8
Update headerContent.jelly
janfaracik Feb 6, 2025
540ae4b
Update headerContent.jelly
janfaracik Feb 6, 2025
770f5d9
Remove bravo test - need to confirm this
janfaracik Feb 6, 2025
615e5a0
Update Security3349Test.java
janfaracik Feb 6, 2025
ff33e12
Update pom.xml
janfaracik Feb 7, 2025
e89f74a
Merge branch 'master' into new-header
janfaracik Feb 7, 2025
90dd49f
Fix SpotBugs + i18n
janfaracik Feb 8, 2025
c493a41
Add doc for header scroll, support prefers contrast
janfaracik Feb 8, 2025
cadf01e
Add overflow menu for actions, improve accessibility
janfaracik Feb 8, 2025
f740741
Update actions-overflow.js
janfaracik Feb 8, 2025
dbb67ca
Fix tests + accessibility
janfaracik Feb 8, 2025
2fdb00b
Merge branch 'master' into new-header
janfaracik Feb 8, 2025
8eda9d1
Fix JS
janfaracik Feb 10, 2025
57e67af
Update breadcrumbs-overflow.js
janfaracik Feb 11, 2025
ddc968a
Merge branch 'master' into new-header
janfaracik Feb 11, 2025
1f8cc8b
Update breadcrumbs-overflow.js
janfaracik Feb 12, 2025
ee5c7fe
Merge branch 'master' into new-header
janfaracik Feb 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions core/src/main/java/hudson/model/ManageJenkinsAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
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;
Expand All @@ -40,11 +44,11 @@
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() {
Expand Down Expand Up @@ -88,4 +92,24 @@
// 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);
Collection<AdministrativeMonitor> activeAdministrativeMonitors = Optional.ofNullable(decorator.getMonitorsToDisplay()).orElse(Collections.emptyList());

Check warning on line 101 in core/src/main/java/hudson/model/ManageJenkinsAction.java

View check run for this annotation

ci.jenkins.io / SpotBugs

NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE

NORMAL: Possible null pointer dereference in hudson.model.ManageJenkinsAction.getBadge() due to return value of called method
Raw output
<p> The return value from a method is dereferenced without a null check, and the return value of that method is one that should generally be checked for null. This may lead to a <code>NullPointerException</code> when the code is executed. </p>
boolean anySecurity = activeAdministrativeMonitors.stream().anyMatch(AdministrativeMonitor::isSecurity);

if (activeAdministrativeMonitors.isEmpty()) {
return null;
}

int size = activeAdministrativeMonitors.size();
String suffix = size > 1 ? "notifications" : "notification";

return new Badge(String.valueOf(size),
size + " " + suffix,
anySecurity ? Badge.Severity.DANGER : Badge.Severity.WARNING);
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/model/MyViewsProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ public MyViewsTabBar getMyViewsTabBar() {
return Jenkins.get().getMyViewsTabBar();
}

@Extension @Symbol("myView")
@Symbol("myView")
public static class GlobalAction implements RootAction {

@Override
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/hudson/model/RootAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -38,4 +40,14 @@
* @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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -163,11 +159,6 @@ public Collection<AdministrativeMonitor> getMonitorsToDisplay() {
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();
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/jenkins/model/Jenkins.java
Original file line number Diff line number Diff line change
Expand Up @@ -2350,7 +2350,7 @@ public String getSearchUrl() {
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();
Expand Down
50 changes: 50 additions & 0 deletions core/src/main/java/jenkins/model/navigation/SearchAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 hudson.Extension;
import hudson.model.RootAction;

/**
* TODO
*/
@Extension(ordinal = 999)
public class SearchAction implements RootAction {

@Override
public String getIconFileName() {
return "symbol-search";
}

@Override
public String getDisplayName() {
return "Search";
}

@Override
public String getUrlName() {
return null;
}
}
96 changes: 96 additions & 0 deletions core/src/main/java/jenkins/model/navigation/UserAction.java
Original file line number Diff line number Diff line change
@@ -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) {

Check warning on line 60 in core/src/main/java/jenkins/model/navigation/UserAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 60 is only partially covered, one branch is missing
return null;

Check warning on line 61 in core/src/main/java/jenkins/model/navigation/UserAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 61 is not covered by tests
}

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) {

Check warning on line 87 in core/src/main/java/jenkins/model/navigation/UserAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 87 is only partially covered, one branch is missing
return null;

Check warning on line 88 in core/src/main/java/jenkins/model/navigation/UserAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 88 is not covered by tests
}

List<Action> actions = new ArrayList<>();
actions.addAll(current.getPropertyActions());
actions.addAll(current.getTransientActions());
return actions.stream().filter(e -> e.getIconFileName() != null).toList();

Check warning on line 94 in core/src/main/java/jenkins/model/navigation/UserAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 94 is only partially covered, one branch is missing
}
}
18 changes: 18 additions & 0 deletions core/src/main/java/jenkins/views/FullHeader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package jenkins.views;

import hudson.model.Action;
import java.util.List;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
* {@link Header} that provides its own resources as full replacement. It does not
* depends on any core resource (images, CSS, JS, etc.)
Expand All @@ -13,4 +19,16 @@ public abstract class FullHeader extends Header {
public boolean isCompatible() {
return true;
}

/**
* @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() {
return Jenkins.get()
.getActions()
.stream()
.filter(e -> e.getIconFileName() != null)
.toList();
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/jenkins/views/PartialHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand All @@ -50,7 +45,6 @@ THE SOFTWARE.
<j:forEach var="category" items="${app.categorizedManagementLinks.entrySet()}">
<section class="jenkins-section jenkins-section--bottom-padding">
<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}">
Expand All @@ -61,7 +55,6 @@ THE SOFTWARE.
<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}">
Expand Down
1 change: 0 additions & 1 deletion core/src/main/resources/hudson/model/View/sidepanel.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ 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" href="${rootURL}/${app.securityRealm.loginUrl}?from=${app.securityRealm.from}" style="aspect-ratio: unset; padding: 0.5rem 1rem" tooltip="Sign in to access and manage your Jenkins projects">
${%Sign in}
</a>
</j:jelly>
Loading
Loading