Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e71abf9
Add experimental job page UI
janfaracik Oct 11, 2025
00daf6c
Add experimental dashboard page
janfaracik Oct 11, 2025
21b0d9c
Update View.java
janfaracik Oct 11, 2025
b895f85
Tidy up
janfaracik Oct 11, 2025
b498f16
Merge branch 'master' into experimental-dashboard-page
janfaracik Oct 12, 2025
3d6bf8e
Push
janfaracik Oct 12, 2025
5c1133f
Push
janfaracik Oct 12, 2025
ac46f62
Merge branch 'master' into experimental-build-page
janfaracik Oct 12, 2025
44caa3c
Support tabs for jobs
janfaracik Oct 12, 2025
6f613a9
Update new-view-page.jelly
janfaracik Oct 12, 2025
c6045d8
Update index.jelly
janfaracik Oct 12, 2025
fccb47e
Update new-view-page.jelly
janfaracik Oct 13, 2025
45cfced
Add Build buttons/configure to app bar
janfaracik Oct 14, 2025
75fe528
Merge branch 'master' into experimental-build-page
janfaracik Oct 14, 2025
f832bc9
Make Build with parameters a dialog
janfaracik Oct 14, 2025
232c791
Update _build.scss
janfaracik Oct 15, 2025
77b48d0
Fix alignment of floating widgets
janfaracik Oct 15, 2025
4c8397e
Merge branch 'experimental-build-page' into experimental-dashboard-page
janfaracik Oct 15, 2025
f830ae2
Update new-view-page.jelly
janfaracik Oct 15, 2025
776c184
Update styles.scss
janfaracik Oct 15, 2025
7ced252
Refine
janfaracik Oct 15, 2025
3901818
Update _dashboard.scss
janfaracik Oct 16, 2025
55a434a
Include sidepanel in overflow
janfaracik Oct 16, 2025
1641413
Merge branch 'master' into experimental-dashboard-page
janfaracik Oct 16, 2025
1c7bc72
Fix legend
janfaracik Oct 16, 2025
6b77f0e
Merge branch 'master' into experimental-dashboard-page
timja Oct 16, 2025
3982dd1
Merge branch 'master' into experimental-dashboard-page
janfaracik Oct 18, 2025
aeed730
Make tabs wrap + improve on mobile
janfaracik Oct 21, 2025
6a698ce
Add Javadoc for View#getIconFileName
janfaracik Oct 25, 2025
cd4b39a
Add note that getIconFileName only shows if the flag is enabled
janfaracik Oct 25, 2025
c3071e9
Fix experimental build controls being in the wrong spot
janfaracik Oct 25, 2025
85ca24d
Merge branch 'master' into experimental-dashboard-page
janfaracik Oct 25, 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
7 changes: 6 additions & 1 deletion core/src/main/java/hudson/model/View.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import jenkins.model.Badgeable;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
Expand Down Expand Up @@ -144,7 +145,7 @@
* @see ViewGroup
*/
@ExportedBean
public abstract class View extends AbstractModelObject implements AccessControlled, Describable<View>, ExtensionPoint, Saveable, ModelObjectWithChildren, DescriptorByNameOwner, HasWidgets {
public abstract class View extends AbstractModelObject implements AccessControlled, Describable<View>, ExtensionPoint, Saveable, ModelObjectWithChildren, DescriptorByNameOwner, HasWidgets, Badgeable {

/**
* Container of this view. Set right after the construction
Expand Down Expand Up @@ -368,6 +369,10 @@
return getViewName();
}

public String getIconFileName() {
return null;

Check warning on line 373 in core/src/main/java/hudson/model/View.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 373 is not covered by tests
}

public String getNewPronoun() {
return AlternativeUiTextProvider.get(NEW_PRONOUN, this, Messages.AbstractItem_Pronoun());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.experimentalflags;

import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

@Extension
@Restricted(NoExternalUse.class)
public class NewDashboardPageUserExperimentalFlag extends BooleanUserExperimentalFlag {
public NewDashboardPageUserExperimentalFlag() {
super("new-dashboard-page.flag");
}

@Override
public String getDisplayName() {
return "New dashboard page";
}

@Nullable
@Override
public String getShortDescription() {
return "Enables a revamped dashboard page. This feature is still a work in progress, so some things might not work perfectly yet.";

Check warning on line 47 in core/src/main/java/jenkins/model/experimentalflags/NewDashboardPageUserExperimentalFlag.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 41-47 are not covered by tests
}
}
21 changes: 15 additions & 6 deletions core/src/main/resources/hudson/model/View/index.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,24 @@ 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" xmlns:i="jelly:fmt">
<l:layout title="${(it.class.name=='hudson.model.AllView' and it.ownerItemGroup == app) ? '%Dashboard' : it.displayName}${not empty it.ownerItemGroup.fullDisplayName?' ['+it.ownerItemGroup.fullDisplayName+']':''}">
<j:set var="view" value="${it}"/> <!-- expose view to the scripts we include from owner -->
<l:userExperimentalFlag var="newDashboardPage" flagClassName="jenkins.model.experimentalflags.NewDashboardPageUserExperimentalFlag" />

<j:choose>
<j:when test="${newDashboardPage}">
<st:include page="new-view-page.jelly" />
</j:when>
<j:otherwise>
<l:layout title="${(it.class.name=='hudson.model.AllView' and it.ownerItemGroup == app) ? '%Dashboard' : it.displayName}${not empty it.ownerItemGroup.fullDisplayName?' ['+it.ownerItemGroup.fullDisplayName+']':''}">
<j:set var="view" value="${it}"/> <!-- expose view to the scripts we include from owner -->
<st:include page="sidepanel.jelly" />
<!-- no need for additional breadcrumb here as we're on an index page already including breadcrumb -->
<l:main-panel>
<st:include page="view-index-top.jelly" it="${it.owner}" optional="true">
<!-- allow the owner to take over the top section, but we also need the default to be backward compatible -->
<div id="view-message">
<div id="systemmessage">
<j:out value="${app.systemMessage!=null ? app.markupFormatter.translate(app.systemMessage) : ''}" />
</div>
<div id="systemmessage">
<j:out value="${app.systemMessage!=null ? app.markupFormatter.translate(app.systemMessage) : ''}" />
</div>
<t:editableDescription permission="${it.CONFIGURE}"/>
</div>
</st:include>
Expand All @@ -47,5 +54,7 @@ THE SOFTWARE.
<script id="screenResolution-script" data-use-secure-cookie="${request2.secure}"/>
<st:adjunct includes="hudson.model.View.screen-resolution"/>
</l:header>
</l:layout>
</l:layout>
</j:otherwise>
</j:choose>
</j:jelly>
31 changes: 0 additions & 31 deletions core/src/main/resources/hudson/model/View/main.groovy

This file was deleted.

78 changes: 78 additions & 0 deletions core/src/main/resources/hudson/model/View/main.jelly
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:t="/lib/hudson" xmlns:l="/lib/layout" xmlns:d="jelly:define" xmlns:local="local">
<l:userExperimentalFlag var="newDashboardPage" flagClassName="jenkins.model.experimentalflags.NewDashboardPageUserExperimentalFlag" />

<d:taglib uri="local">
<d:tag name="view">
<j:choose>
<j:when test="${items == null}">
<p>${%broken}</p>
</j:when>

<j:when test="${items.isEmpty()}">
<j:if test="${!app.items.isEmpty()}">
<j:set var="views" value="${it.owner.views}" />
<j:set var="currentView" value="${it}" />

<j:if test="${!newDashboardPage}">
<j:choose>
<j:when test="${it.owner.class == hudson.model.MyViewsProperty.class}">
<st:include it="${it.owner.viewsTabBar}" page="viewTabs" />
</j:when>
<j:otherwise>
<st:include it="${it.owner.userViewsTabBar}" page="viewTabs" />
</j:otherwise>
</j:choose>
</j:if>
</j:if>
<st:include it="${it}" page="noJob.jelly" />
</j:when>

<j:otherwise>
<t:projectView
jobs="${items}"
showViewTabs="true"
columnExtensions="${it.columns}"
indenter="${it.indenter}"
itemGroup="${it.owner.itemGroup}">

<j:set var="views" value="${it.owner.views}" />
<j:set var="currentView" value="${it}" />

<j:if test="${!newDashboardPage}">
<j:choose>
<j:when test="${it.owner.class == hudson.model.MyViewsProperty.class}">
<st:include it="${it.owner.viewsTabBar}" page="viewTabs" />
</j:when>
<j:otherwise>
<st:include it="${it.owner.userViewsTabBar}" page="viewTabs" />
</j:otherwise>
</j:choose>
</j:if>
</t:projectView>
</j:otherwise>
</j:choose>
</d:tag>
</d:taglib>

<j:choose>
<j:when test="${newDashboardPage}">
<div class="jenkins-inline-page">
<div class="jenkins-inline-page__side-panel">
<j:if test="${h.hasPermission(app.READ)}">
<j:forEach var="w" items="${it.widgets}">
<j:set var="view" value="${it}" />
<st:include it="${w}" page="index.jelly" />
</j:forEach>
</j:if>
</div>
<div>
<local:view />
</div>
</div>
</j:when>
<j:otherwise>
<local:view />
</j:otherwise>
</j:choose>
</j:jelly>
145 changes: 145 additions & 0 deletions core/src/main/resources/hudson/model/View/new-view-page.jelly
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<!--
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.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler"
xmlns:l="/lib/layout" xmlns:dd="/lib/layout/dropdowns" xmlns:t="/lib/hudson">
<l:layout title="${(it.class.name=='hudson.model.AllView' and it.ownerItemGroup == app) ? '%Dashboard' : it.displayName}${not empty it.ownerItemGroup.fullDisplayName ? ' - ' + it.ownerItemGroup.fullDisplayName : ''}">
<l:header>
<!-- for screen resolution detection -->
<script id="screenResolution-script" data-use-secure-cookie="${request2.secure}"/>
<st:adjunct includes="hudson.model.View.screen-resolution"/>
</l:header>

<j:set var="view" value="${it}"/> <!-- expose view to the scripts we include from owner -->
<l:main-panel>
<div class="app-build-bar">
<div class="app-build-bar__content">
<div class="app-build-bar__content__headline">
<j:set var="iconClass" value="${it.owner.iconColor.getIconClassName()}"/>
<j:if test="${iconClass != null}">
<j:set var="iconClass" value="${iconClass} icon-lg"/>
</j:if>
<j:choose>
<j:when test="${iconClass != null}">
<l:icon class="${iconClass}" alt="${it.owner.iconColor.description}" tooltip="${it.owner.iconColor.description}" />
</j:when>
<j:when test="${it.owner.iconColor.getImageOf('32x32') != null}">
<l:icon src="${it.owner.iconColor.getImageOf('32x32')}" alt="${it.owner.iconColor.description}" tooltip="${it.owner.iconColor.description}" />
</j:when>
</j:choose>

<h1>${it.ownerItemGroup == app ? '%Dashboard' : it.owner == app ? it.displayName : it.owner.displayName}</h1>
</div>
</div>

<j:set var="currentView" value="${it}" />
<j:set var="views" value="${it.owner.views}" />

<div class="app-build-bar__tabs">
<j:choose>
<j:when test="${view.class.name eq 'hudson.model.MyViewsProperty'}">
<st:include page="viewTabs" it="${view.owner.userViewsTabBar}" />
</j:when>
<j:otherwise>
<st:include page="viewTabs" it="${view.owner.viewsTabBar}" />
</j:otherwise>
</j:choose>
</div>

<div class="app-build-bar__controls">
<j:if test="${it.hasPermission(it.CREATE)}">
<a class="jenkins-button"
href="${rootURL}/${it.viewUrl}newJob">
<l:icon src="symbol-add" />
New Item
</a>
</j:if>

<j:if test="${it.isEditable() and it.hasPermission(it.CONFIGURE)}">
<!-- /configure URL on Jenkins object is overloaded with Jenkins's system config, so always use the explicit name. -->
<a class="jenkins-button"
href="${rootURL}/${it.viewUrl}configure">
${%Edit View}
</a>
</j:if>

<l:dialog title="${%Legend}" hash="legend">
<div>
<st:include page="_legend.jelly" it="${app}" />
</div>
</l:dialog>

<l:overflowButton>
<dd:custom>
<div class="app-build-overflow">
<j:set var="mode" value="side-panel" />
<st:include page="sidepanel.jelly" it="${it.object}" />
</div>
</dd:custom>
<dd:separator />
<dd:submenu icon="symbol-rss" text="${%Atom feed}">
<dd:item icon="symbol-rss" text="${%All}" href="rssAll" />
<dd:item icon="symbol-rss" text="${%Failures}" href="rssFailed" />
<dd:item icon="symbol-rss" text="${%LatestBuilds}" href="rssLatest" />
</dd:submenu>
<dd:custom>
<button class="jenkins-dropdown__item"
data-type="dialog-opener"
data-dialog-id="${dialogId}">
<div class="jenkins-dropdown__item__icon">
<l:icon src="symbol-information-circle" />
</div>
${%Legend}
</button>
</dd:custom>
<j:if test="${it.owner.canDelete(it) and it.hasPermission(it.DELETE)}">
<dd:separator />
<dd:custom>
<l:confirmationLink class="jenkins-dropdown__item jenkins-!-destructive-color"
href="doDelete"
title="todo"
message="${%delete.logrecorder(it.displayName)}"
destructive="true"
post="true">
<div class="jenkins-dropdown__item__icon">
<l:icon src="symbol-trash" />
</div>
${%Delete View}
</l:confirmationLink>
</dd:custom>
</j:if>
</l:overflowButton>
</div>
</div>

<div class="app-build-content" style="padding-top: var(--section-padding)">
<t:editableDescription permission="${it.CONFIGURE}" hideButton="true" />

<j:set var="items" value="${it.items}"/>
<st:include page="main.jelly" />
</div>
</l:main-panel>
</l:layout>
</j:jelly>
Loading
Loading