Merge branch 'master' into fix-chevron-click

This commit is contained in:
Mark Waite authored and GitHub committed 2025-05-15 20:47:46 -06:00
commit 26c42005d4
182 files changed
+3894 -3255

No files matched your search

+1 -7
View File
@@ -23,12 +23,6 @@
"team:sig-ux"
]
},
{
"matchPackageNames": [
"node"
],
"allowedVersions": "/20.[0-9]+.[0-9]+(.[0-9]+)?$/"
},
{
"description": "Should be upgraded in lockstep in order to keep their corresponding Jetty versions aligned",
"matchManagers": [
@@ -124,7 +118,7 @@
"<node.version>(?<currentValue>.*?)</node.version>"
],
"depNameTemplate": "node",
"datasourceTemplate": "npm"
"datasourceTemplate": "node-version"
},
{
"customType": "regex",
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
discourse-author-username: jenkins-release-bot
discourse-category: 23
- name: Post on mailing list
uses: dawidd6/action-send-mail@611879133a9569642c41be66f4a323286e9b8a3b # v4
uses: dawidd6/action-send-mail@7ac0fb1e367721ffc3985c672ba2e7659379bc00 # v5
with:
server_address: smtp.gmail.com
server_port: 465
@@ -73,7 +73,7 @@ jobs:
wget -q https://get.jenkins.io/${REPO}/${PROJECT_VERSION}/${FILE_NAME}
- name: Upload Release Asset
id: upload-war
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -108,7 +108,7 @@ jobs:
- name: Upload Release Asset
id: upload-deb
if: always()
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -144,7 +144,7 @@ jobs:
- name: Upload Release Asset
id: upload-rpm
if: always()
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -180,7 +180,7 @@ jobs:
- name: Upload Release Asset
id: upload-msi
if: always()
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -216,7 +216,7 @@ jobs:
- name: Upload Release Asset
id: upload-suse-rpm
if: always()
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
+5 -10
View File
@@ -41,7 +41,7 @@ THE SOFTWARE.
<commons-fileupload2.version>2.0.0-M2</commons-fileupload2.version>
<groovy.version>2.4.21</groovy.version>
<jelly.version>1.1-jenkins-20250108</jelly.version>
<stapler.version>1967.vfcb_700b_75e9e</stapler.version>
<stapler.version>1983.v93c53e94b_c04</stapler.version>
</properties>
<dependencyManagement>
@@ -63,7 +63,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>6.2.5</version>
<version>6.2.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -71,7 +71,7 @@ THE SOFTWARE.
<!-- https://docs.spring.io/spring-security/reference/6.3/getting-spring-security.html#getting-maven-no-boot -->
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>6.4.4</version>
<version>6.4.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -89,7 +89,7 @@ THE SOFTWARE.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.6-jre</version>
<version>33.4.8-jre</version>
</dependency>
<dependency>
<groupId>com.sun.xml.txw2</groupId>
@@ -119,7 +119,7 @@ THE SOFTWARE.
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.18.0</version>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
@@ -206,11 +206,6 @@ THE SOFTWARE.
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
<dependency>
<groupId>org.connectbot</groupId>
<artifactId>jbcrypt</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<!-- Groovy shell uses this, but it doesn't declare the dependency -->
<groupId>org.fusesource.jansi</groupId>
-4
View File
@@ -246,10 +246,6 @@ THE SOFTWARE.
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</dependency>
<dependency>
<groupId>org.connectbot</groupId>
<artifactId>jbcrypt</artifactId>
</dependency>
<dependency>
<!-- Groovy shell uses this, but it doesn't declare the dependency -->
<groupId>org.fusesource.jansi</groupId>
@@ -1557,6 +1557,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
releaseTimestamp.put("displayValue", Messages.PluginManager_ago(Functions.getTimeSpanString(plugin.releaseTimestamp)));
jsonObject.put("releaseTimestamp", releaseTimestamp);
}
if (plugin.healthScore != null) {
jsonObject.put("healthScore", plugin.healthScore);
jsonObject.put("healthScoreClass", plugin.healthScoreClass);
}
return jsonObject;
})
.collect(toList());
@@ -222,6 +222,8 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
*/
private static Set<String> CORE_ONLY_DEPENDANT = Set.of("jenkins-core");
private Integer healthScore;
/**
* Set the list of components that depend on this plugin.
* @param dependents The list of components that depend on this plugin.
@@ -423,6 +425,32 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
}
@Restricted(NoExternalUse.class) // Jelly use only
public Integer getHealthScore() {
if (this.healthScore == null) {
this.healthScore = getInfoFromAllSites().stream()
.filter(Objects::nonNull)
.filter(p -> p.healthScore != null)
.findFirst()
.map(plugin -> plugin.healthScore)
.orElse(null);
}
return this.healthScore;
}
@Restricted(NoExternalUse.class)
public static String getHealthScoreClassForScore(int score) {
if (score > 80) return "top";
if (score > 60) return "middle";
return "bottom";
}
@Restricted(NoExternalUse.class) // Jelly use only
public String getHealthScoreClass() {
if (this.healthScore == null) return null;
return getHealthScoreClassForScore(this.healthScore);
}
@ExportedBean
public static final class Dependency {
@Exported
@@ -141,7 +141,10 @@ public class AnnotatedLargeText<T> extends LargeText {
* and use this request attribute to differentiate.
*/
private boolean isHtml() {
StaplerRequest2 req = Stapler.getCurrentRequest2();
return isHtml(Stapler.getCurrentRequest2());
}
private boolean isHtml(StaplerRequest2 req) {
return req != null && req.getAttribute("html") != null;
}
@@ -207,6 +210,11 @@ public class AnnotatedLargeText<T> extends LargeText {
return super.writeLogTo(start, w);
}
@Override
protected boolean delegateToWriteLogTo(StaplerRequest2 req, StaplerResponse2 rsp) {
return isHtml(req);
}
/**
* Strips annotations using a {@link PlainTextConsoleOutputStream}.
* {@inheritDoc}
@@ -118,6 +118,11 @@ public abstract class ConsoleAnnotator<T> implements Serializable {
default: return this;
}
}
@Override
public String toString() {
return "ConsoleAnnotatorAggregator" + list;
}
}
/**
@@ -31,7 +31,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.BulkChange;
import hudson.Extension;
import hudson.FilePath;
import hudson.RestrictedSince;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.AbstractModelObject;
@@ -43,7 +42,6 @@ import hudson.model.listeners.SaveableListener;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.slaves.ComputerListener;
import hudson.util.CopyOnWriteList;
import hudson.util.FormApply;
import hudson.util.FormValidation;
import hudson.util.HttpResponses;
@@ -104,15 +102,6 @@ import org.kohsuke.stapler.verb.POST;
public class LogRecorder extends AbstractModelObject implements Loadable, Saveable {
private volatile String name;
/**
* No longer used.
*
* @deprecated use {@link #getLoggers()}
*/
@Deprecated
@Restricted(NoExternalUse.class)
@RestrictedSince("2.324")
public final transient CopyOnWriteList<Target> targets = new CopyOnWriteList<>();
private List<Target> loggers = new ArrayList<>();
private static final TargetComparator TARGET_COMPARATOR = new TargetComparator();
@@ -124,22 +113,6 @@ public class LogRecorder extends AbstractModelObject implements Loadable, Saveab
new WeakLogHandler(handler, Logger.getLogger(""));
}
private Object readResolve() {
if (loggers == null) {
loggers = new ArrayList<>();
}
List<Target> tempLoggers = new ArrayList<>(loggers);
if (!targets.isEmpty()) {
loggers.addAll(targets.getView());
}
if (!tempLoggers.isEmpty() && !targets.getView().equals(tempLoggers)) {
targets.addAll(tempLoggers);
}
return this;
}
public List<Target> getLoggers() {
return loggers;
}
@@ -455,7 +428,6 @@ public class LogRecorder extends AbstractModelObject implements Loadable, Saveab
recorders.remove(new LogRecorder(name));
this.name = newName;
recorders.add(this);
getParent().setRecorders(recorders); // ensure that legacy logRecorders field is synced on save
redirect = "../" + Util.rawEncode(newName) + '/';
}
@@ -491,31 +463,12 @@ public class LogRecorder extends AbstractModelObject implements Loadable, Saveab
public synchronized void save() throws IOException {
if (BulkChange.contains(this)) return;
handlePluginUpdatingLegacyLogManagerMap();
getConfigFile().write(this);
loggers.forEach(Target::enable);
SaveableListener.fireOnChange(this, getConfigFile());
}
@SuppressWarnings("deprecation") // this is for compatibility
private void handlePluginUpdatingLegacyLogManagerMap() {
if (getParent().logRecorders.size() > getParent().getRecorders().size()) {
for (LogRecorder logRecorder : getParent().logRecorders.values()) {
if (!getParent().getRecorders().contains(logRecorder)) {
getParent().getRecorders().add(logRecorder);
}
}
}
if (getParent().getRecorders().size() > getParent().logRecorders.size()) {
for (LogRecorder logRecorder : getParent().getRecorders()) {
if (!getParent().logRecorders.containsKey(logRecorder.getName())) {
getParent().logRecorders.put(logRecorder.getName(), logRecorder);
}
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -155,7 +155,6 @@ public class LogRecorderManager extends AbstractModelObject implements ModelObje
lr.load();
recorders.add(lr);
}
setRecorders(recorders); // ensure that legacy logRecorders field is synced on load
}
/**
@@ -30,5 +30,5 @@ package hudson.model;
* @author Kohsuke Kawaguchi
* @deprecated Implement {@link Describable} directly.
*/
@Deprecated(since = "TODO")
@Deprecated(since = "2.505")
public abstract class AbstractDescribableImpl<T extends AbstractDescribableImpl<T>> implements Describable<T> {}
@@ -782,7 +782,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
public RunList getBuilds() {
return RunList.fromJobs((Iterable) Jenkins.get().allItems(Job.class)).node(getNode());
return RunList.fromJobs((Iterable) Jenkins.get().allItems(AbstractProject.class)).node(getNode());
}
/**
+8 -14
View File
@@ -97,7 +97,6 @@ import jenkins.model.JenkinsLocationConfiguration;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.PeepholePermalink;
import jenkins.model.ProjectNamingStrategy;
import jenkins.model.RunIdMigrator;
import jenkins.model.lazy.LazyBuildMixIn;
import jenkins.scm.RunWithSCM;
import jenkins.security.HexStringConfidentialKey;
@@ -192,10 +191,6 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
// this should have been DescribableList but now it's too late
protected CopyOnWriteList<JobProperty<? super JobT>> properties = new CopyOnWriteList<>();
@Restricted(NoExternalUse.class)
@SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility")
public transient RunIdMigrator runIdMigrator;
protected Job(ItemGroup parent, String name) {
super(parent, name);
}
@@ -206,20 +201,19 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
holdOffBuildUntilSave = holdOffBuildUntilUserSave;
}
@Override public void onCreatedFromScratch() {
super.onCreatedFromScratch();
runIdMigrator = new RunIdMigrator();
runIdMigrator.created(getBuildDir());
}
@Override
public void onLoad(ItemGroup<? extends Item> parent, String name)
throws IOException {
super.onLoad(parent, name);
File buildDir = getBuildDir();
runIdMigrator = new RunIdMigrator();
runIdMigrator.migrate(buildDir, Jenkins.get().getRootDir());
// see https://github.com/jenkinsci/jenkins/pull/10456#issuecomment-2748112449
// This code can be deleted after several Jenkins releases,
// when it is likely that everyone is running a version equal or higher to this version.
var buildDirPath = getBuildDir().toPath();
if (Files.deleteIfExists(buildDirPath.resolve("legacyIds"))) {
LOGGER.info("Deleting legacyIds file in " + buildDirPath + ". See https://issues.jenkins"
+ ".io/browse/JENKINS-75465 for more information.");
}
TextFile f = getNextBuildNumberFile();
if (f.exists()) {
+29 -32
View File
@@ -33,6 +33,9 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.Util;
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
@@ -54,22 +57,24 @@ import hudson.slaves.Cloud;
import hudson.slaves.NodeProvisioner;
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import jenkins.model.IComputer;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.util.antlr.JenkinsANTLRErrorListener;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.StaplerResponse2;
@@ -104,21 +109,6 @@ public abstract class Label extends Actionable implements Comparable<Label>, Mod
this.name = name;
// passing these causes an infinite loop - getTotalExecutors(),getBusyExecutors());
this.loadStatistics = new LoadStatistics(0, 0) {
@Override
public int computeIdleExecutors() {
return Label.this.getIdleExecutors();
}
@Override
public int computeTotalExecutors() {
return Label.this.getTotalExecutors();
}
@Override
public int computeQueueLength() {
return Jenkins.get().getQueue().countBuildableItemsFor(Label.this);
}
@Override
protected Set<Node> getNodes() {
return Label.this.getNodes();
@@ -210,18 +200,6 @@ public abstract class Label extends Actionable implements Comparable<Label>, Mod
return nodes.size() == 1 && nodes.iterator().next().getSelfLabel().equals(this);
}
private static class NodeSorter implements Comparator<Node>, Serializable {
private static final long serialVersionUID = -7368519598046684532L;
@Override
public int compare(Node o1, Node o2) {
if (o1 == o2) {
return 0;
}
return o1 instanceof Jenkins ? -1 : o2 instanceof Jenkins ? 1 : o1.getNodeName().compareTo(o2.getNodeName());
}
}
/**
* Gets all {@link Node}s that belong to this label.
*/
@@ -242,10 +220,29 @@ public abstract class Label extends Actionable implements Comparable<Label>, Mod
}
@Restricted(DoNotUse.class) // Jelly
public Set<Node> getSortedNodes() {
Set<Node> r = new TreeSet<>(new NodeSorter());
r.addAll(getNodes());
return r;
@NonNull
public Collection<? extends IComputer> getComputers() {
return ExtensionList.lookupFirst(LabelComputerSource.class).get(this).stream().sorted(Comparator.comparing(IComputer::getName)).toList();
}
/**
* Allows plugins to override the displayed list of computers per label.
* @see ComputerSet.ComputerSource
*/
@Restricted(Beta.class)
public interface LabelComputerSource extends ExtensionPoint {
@NonNull
Collection<? extends IComputer> get(@NonNull Label label);
}
@Extension(ordinal = -1)
@Restricted(DoNotUse.class)
public static class LabelComputerSourceImpl implements LabelComputerSource {
@Override
@NonNull
public Collection<? extends IComputer> get(@NonNull Label label) {
return label.getNodes().stream().map(Node::toComputer).filter(Objects::nonNull).toList();
}
}
/**
@@ -36,8 +36,6 @@ import java.awt.BasicStroke;
import java.awt.Color;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.concurrent.TimeUnit;
import jenkins.model.Jenkins;
@@ -74,12 +72,6 @@ import org.kohsuke.stapler.export.ExportedBean;
*/
@ExportedBean
public abstract class LoadStatistics {
/**
* {@code true} if and only if the new way of building statistics has been implemented by this class.
* @since 1.607
*/
private final boolean modern;
/**
* Number of executors defined for Jenkins and how it changes over time.
* @since 1.607
@@ -154,35 +146,6 @@ public abstract class LoadStatistics {
this.queueLength = new MultiStageTimeSeries(
Messages._LoadStatistics_Legends_QueueLength(), ColorPalette.GREY, 0, DECAY);
this.totalExecutors = onlineExecutors;
modern = isModern(getClass());
}
/*package*/ static boolean isModern(Class<? extends LoadStatistics> clazz) {
// cannot use Util.isOverridden as these are protected methods.
boolean hasGetNodes = false;
boolean hasMatches = false;
while (clazz != LoadStatistics.class && clazz != null && !(hasGetNodes && hasMatches)) {
if (!hasGetNodes) {
try {
final Method getNodes = clazz.getDeclaredMethod("getNodes");
hasGetNodes = !Modifier.isAbstract(getNodes.getModifiers());
} catch (NoSuchMethodException e) {
// ignore
}
}
if (!hasMatches) {
try {
final Method getNodes = clazz.getDeclaredMethod("matches", Queue.Item.class, SubTask.class);
hasMatches = !Modifier.isAbstract(getNodes.getModifiers());
} catch (NoSuchMethodException e) {
// ignore
}
}
if (!(hasGetNodes && hasMatches) && LoadStatistics.class.isAssignableFrom(clazz.getSuperclass())) {
clazz = (Class<? extends LoadStatistics>) clazz.getSuperclass();
}
}
return hasGetNodes && hasMatches;
}
/**
@@ -198,21 +161,27 @@ public abstract class LoadStatistics {
* @deprecated use {@link #computeSnapshot()} and then {@link LoadStatisticsSnapshot#getIdleExecutors()}
*/
@Deprecated
public abstract int computeIdleExecutors();
public int computeIdleExecutors() {
return computeSnapshot().getIdleExecutors();
}
/**
* Computes the # of total executors right now and obtains the snapshot value.
* @deprecated use {@link #computeSnapshot()} and then {@link LoadStatisticsSnapshot#getOnlineExecutors()}
*/
@Deprecated
public abstract int computeTotalExecutors();
public int computeTotalExecutors() {
return computeSnapshot().getOnlineExecutors();
}
/**
* Computes the # of queue length right now and obtains the snapshot value.
* @deprecated use {@link #computeSnapshot()} and then {@link LoadStatisticsSnapshot#getQueueLength()}
*/
@Deprecated
public abstract int computeQueueLength();
public int computeQueueLength() {
return computeSnapshot().getQueueLength();
}
/**
* Creates a trend chart.
@@ -333,13 +302,7 @@ public abstract class LoadStatistics {
* @since 1.607
*/
public LoadStatisticsSnapshot computeSnapshot() {
if (modern) {
return computeSnapshot(Jenkins.get().getQueue().getBuildableItems());
} else {
int t = computeTotalExecutors();
int i = computeIdleExecutors();
return new LoadStatisticsSnapshot(t, t, Math.max(i - t, 0), Math.max(t - i, 0), i, i, computeQueueLength());
}
return computeSnapshot(Jenkins.get().getQueue().getBuildableItems());
}
/**
@@ -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
@@ -341,6 +341,15 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
return Collections.unmodifiableSet(r);
}
/**
* @return the labels to be trimmed for this node.
*/
@NonNull
@Restricted(NoExternalUse.class)
public Set<LabelAtom> drainLabelsToTrim() {
return new HashSet<>(getAssignedLabels());
}
/**
* Return all the labels assigned dynamically to this node.
* This calls all the LabelFinder implementations with the node converts
@@ -56,21 +56,6 @@ public class OverallLoadStatistics extends LoadStatistics {
super(0, 0);
}
@Override
public int computeIdleExecutors() {
return new ComputerSet().getIdleExecutors();
}
@Override
public int computeTotalExecutors() {
return new ComputerSet().getTotalExecutors();
}
@Override
public int computeQueueLength() {
return Jenkins.get().getQueue().countBuildableItems();
}
@Override
protected Iterable<Node> getNodes() {
return Jenkins.get().getNodes();
@@ -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 2.507
*/
default @CheckForNull Badge getBadge() {
return null;
}
}
+31 -11
View File
@@ -72,7 +72,6 @@ import hudson.util.XStream2;
import io.jenkins.servlet.ServletExceptionWrapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
@@ -136,6 +135,7 @@ import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.jelly.XMLOutput;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
@@ -1074,6 +1074,11 @@ public abstract class Run<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
}
}
@Restricted(DoNotUse.class) // Jelly
public final boolean hasCustomArtifactManager() {
return artifactManager != null;
}
/**
* Gets the directory where the artifacts are archived.
* @deprecated Should only be used from {@link StandardArtifactManager} or subclasses.
@@ -1466,19 +1471,34 @@ public abstract class Run<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
*/
@SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED", justification = "method signature does not permit plumbing through the return value")
public void writeLogTo(long offset, @NonNull XMLOutput out) throws IOException {
long start = offset;
AnnotatedLargeText<?> logText = getLogText();
if (offset > 0) {
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(getLogInputStream())) {
if (offset == bufferedInputStream.skip(offset)) {
int r;
do {
r = bufferedInputStream.read();
start = r == -1 ? 0 : start + 1;
} while (r != -1 && r != '\n');
}
long _offset = offset;
try {
logText.writeRawLogTo(offset - 1, new OutputStream() {
long pos = _offset;
@Override
public void write(int b) throws IOException {
if (b == '\n') {
throw new Halt(pos);
} else {
pos++;
}
}
});
} catch (Halt halt) {
offset = halt.offset;
}
}
getLogText().writeHtmlTo(start, out.asWriter());
logText.writeHtmlTo(offset, out.asWriter());
}
private static final class Halt extends IOException {
final long offset;
Halt(long offset) {
this.offset = offset;
}
}
/**
+27 -12
View File
@@ -43,12 +43,11 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.SortedMap;
import java.util.function.IntPredicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.RunIdMigrator;
import jenkins.model.lazy.AbstractLazyLoadRunMap;
import jenkins.model.lazy.BuildReference;
import jenkins.model.lazy.LazyBuildMixIn;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -72,10 +71,6 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
private Constructor<R> cons;
/** Normally overwritten by {@link LazyBuildMixIn#onLoad} or {@link LazyBuildMixIn#onCreatedFromScratch}, in turn created during {@link Job#onLoad}. */
@Restricted(NoExternalUse.class)
public RunIdMigrator runIdMigrator = new RunIdMigrator();
// TODO: before first complete build
// patch up next/previous build link
@@ -156,7 +151,6 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
@Override
public boolean removeValue(R run) {
run.dropLinks();
runIdMigrator.delete(dir, run.getId());
return super.removeValue(run);
}
@@ -227,14 +221,13 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
return super._put(r);
}
@CheckForNull
@Override public R getById(String id) {
int n;
try {
n = Integer.parseInt(id);
} catch (NumberFormatException x) {
n = runIdMigrator.findNumber(id);
return getByNumber(Integer.parseInt(id));
} catch (NumberFormatException e) { // see https://issues.jenkins.io/browse/JENKINS-75476
return null;
}
return getByNumber(n);
}
/**
@@ -248,6 +241,7 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
return r.createReference();
}
@Restricted(NoExternalUse.class)
@Override
protected boolean allowLoad(int buildNumber) {
if (job == null) {
@@ -264,6 +258,27 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
return true;
}
@Restricted(NoExternalUse.class)
@Override
protected IntPredicate createLoadAllower() {
if (job == null) {
LOGGER.fine(() -> "deprecated constructor without Job used on " + dir);
return buildNumber -> true;
}
@SuppressWarnings("unchecked")
var allowers = RunListener.all().stream().map(l -> l.createLoadAllower(job)).toList();
return buildNumber -> {
for (var allower : allowers) {
if (!allower.test(buildNumber)) {
LOGGER.finer(() -> allower + " declined to load " + buildNumber + " in " + job);
return false;
}
}
LOGGER.finest(() -> "no RunListener declined to load " + buildNumber + " in " + job + " so proceeding");
return true;
};
}
@Override
protected R retrieve(File d) throws IOException {
if (new File(d, "build.xml").exists()) {
@@ -63,6 +63,7 @@ import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -70,6 +71,7 @@ import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.WorkspaceLocator;
@@ -340,6 +342,23 @@ public abstract class Slave extends Node implements Serializable {
return Util.fixNull(label).trim();
}
private transient Set<LabelAtom> previouslyAssignedLabels = new HashSet<>();
/**
* @return the labels to be trimmed for this node. This includes current and previous labels that were applied before the last call to this method.
*/
@Override
@NonNull
@Restricted(NoExternalUse.class)
public Set<LabelAtom> drainLabelsToTrim() {
var result = new HashSet<>(super.drainLabelsToTrim());
synchronized (previouslyAssignedLabels) {
result.addAll(previouslyAssignedLabels);
previouslyAssignedLabels.clear();
}
return result;
}
@Override
@DataBoundSetter
public void setLabelString(String labelString) throws IOException {
@@ -349,6 +368,9 @@ public abstract class Slave extends Node implements Serializable {
}
private void _setLabelString(String labelString) {
synchronized (this.previouslyAssignedLabels) {
this.previouslyAssignedLabels.addAll(getAssignedLabels().stream().filter(Objects::nonNull).collect(Collectors.toSet()));
}
this.label = Util.fixNull(labelString).trim();
this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
}
@@ -611,6 +633,7 @@ public abstract class Slave extends Node implements Serializable {
protected Object readResolve() {
if (nodeProperties == null)
nodeProperties = new DescribableList<>(this);
previouslyAssignedLabels = new HashSet<>();
_setLabelString(label);
return this;
}
@@ -765,6 +765,20 @@ public class UpdateCenter extends AbstractModelObject implements Loadable, Savea
return result;
}
@Restricted(NoExternalUse.class)
public boolean isHealthScoresAvailable() {
for (UpdateSite site : sites) {
final Data data = site.getData();
if (data == null) {
continue;
}
if (data.healthScoresAvailable) {
return true;
}
}
return false;
}
private boolean checkMinVersion(@CheckForNull Plugin p, @CheckForNull VersionNumber minVersion) {
return p != null
&& (minVersion == null || !minVersion.isNewerThan(new VersionNumber(p.version)));
@@ -611,6 +611,9 @@ public class UpdateSite {
*/
public final String connectionCheckUrl;
@Restricted(NoExternalUse.class)
public final boolean healthScoresAvailable;
Data(JSONObject o) {
this.sourceId = Util.intern((String) o.get("id"));
JSONObject c = o.optJSONObject("core");
@@ -649,6 +652,8 @@ public class UpdateSite {
}
}
boolean healthScoresAvailable = false;
for (Map.Entry<String, JSONObject> e : (Set<Map.Entry<String, JSONObject>>) o.getJSONObject("plugins").entrySet()) {
Plugin p = new Plugin(sourceId, e.getValue());
// JENKINS-33308 - include implied dependencies for older plugins that may need them
@@ -662,6 +667,10 @@ public class UpdateSite {
}
plugins.put(Util.intern(e.getKey()), p);
if (p.healthScore != null) {
healthScoresAvailable = true;
}
// compatibility with update sites that have no separate 'deprecated' top-level entry.
// Also do this even if there are deprecations to potentially allow limiting the top-level entry to overridden URLs.
if (p.hasCategory("deprecated")) {
@@ -671,6 +680,8 @@ public class UpdateSite {
}
}
this.healthScoresAvailable = healthScoresAvailable;
connectionCheckUrl = (String) o.get("connectionCheckUrl");
}
@@ -1256,6 +1267,12 @@ public class UpdateSite {
@Restricted(NoExternalUse.class)
public IssueTracker[] issueTrackers;
@Restricted(NoExternalUse.class)
public final Integer healthScore;
@Restricted(NoExternalUse.class)
public final String healthScoreClass;
@DataBoundConstructor
public Plugin(String sourceId, JSONObject o) {
super(sourceId, o, UpdateSite.this.url);
@@ -1292,6 +1309,12 @@ public class UpdateSite {
int optionalDepCount = (int) ja.stream().filter(IS_DEP_PREDICATE.and(IS_NOT_OPTIONAL.negate())).count();
dependencies = getPresizedMutableMap(depCount);
optionalDependencies = getPresizedMutableMap(optionalDepCount);
this.healthScore = o.has("health") ? o.getInt("health") : null;
if (healthScore != null) {
this.healthScoreClass = PluginWrapper.getHealthScoreClassForScore(healthScore);
} else {
this.healthScoreClass = null;
}
for (Object jo : o.getJSONArray("dependencies")) {
JSONObject depObj = (JSONObject) jo;
@@ -47,6 +47,7 @@ import java.io.File;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.IntPredicate;
import jenkins.model.lazy.AbstractLazyLoadRunMap;
import jenkins.util.Listeners;
import org.jvnet.tiger_types.Types;
@@ -187,6 +188,16 @@ public abstract class RunListener<R extends Run> implements ExtensionPoint {
return true;
}
/**
* Allows listeners to veto build loading during startup.
* Same behavior as {@link #allowLoad} but permits an implementation to more
* efficiently respond to numerous queries about historical build numbers.
*/
@Restricted(Beta.class)
public IntPredicate createLoadAllower(@NonNull Job<?, ?> job) {
return buildNumber -> allowLoad(job, buildNumber);
}
/**
* Registers this object as an active listener so that it can start getting
* callbacks invoked.
@@ -24,6 +24,7 @@
package hudson.security;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.User;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
@@ -181,6 +182,7 @@ public class BasicAuthenticationFilter implements CompatibleFilter {
d.include(req, rsp);
}
@SuppressFBWarnings(value = "UNVALIDATED_REDIRECT", justification = "Authenticated by the container")
private void prepareRedirect(HttpServletResponse rsp, String path) {
rsp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
rsp.setHeader("Location", path);
@@ -91,7 +91,6 @@ import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -100,6 +99,7 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
@@ -451,11 +451,12 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordRequired());
}
if (FIPS140.useCompliantAlgorithms()) {
if (si.password1.length() < FIPS_PASSWORD_LENGTH) {
si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_FIPS_PasswordLengthInvalid());
}
try {
PASSWORD_HASH_ENCODER.encode(si.password1);
} catch (RuntimeException ex) {
si.errors.put("password1", ex.getMessage());
}
if (si.fullname == null || si.fullname.isEmpty()) {
si.fullname = si.username;
}
@@ -823,10 +824,6 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
throw new FormException("Please confirm the password by typing it twice", "user.password2");
}
if (FIPS140.useCompliantAlgorithms() && pwd.length() < FIPS_PASSWORD_LENGTH) {
throw new FormException(Messages.HudsonPrivateSecurityRealm_CreateAccount_FIPS_PasswordLengthInvalid(), "user.password");
}
// will be null if it wasn't encrypted
String data = Protector.unprotect(pwd);
String data2 = Protector.unprotect(pwd2);
@@ -849,10 +846,18 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
if (data != null) {
String prefix = Stapler.getCurrentRequest2().getSession().getId() + ':';
if (data.startsWith(prefix)) {
// The password is not being changed
return Details.fromHashedPassword(data.substring(prefix.length()));
}
}
// The password is being changed
try {
PASSWORD_HASH_ENCODER.encode(pwd);
} catch (RuntimeException ex) {
throw new FormException(ex.getMessage(), "user.password");
}
User user = Util.getNearestAncestorOfTypeOrThrow(req, User.class);
// the UserSeedProperty is not touched by the configure page
UserSeedProperty userSeedProperty = user.getProperty(UserSeedProperty.class);
@@ -917,11 +922,10 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
}
}
// TODO can we instead use BCryptPasswordEncoder from Spring Security, which has its own copy of BCrypt so we could drop the special library?
/**
* {@link PasswordHashEncoder} that uses jBCrypt.
*/
static class JBCryptEncoder implements PasswordHashEncoder {
static class JBCryptEncoder extends BCryptPasswordEncoder implements PasswordHashEncoder {
// in jBCrypt the maximum is 30, which takes ~22h with laptop late-2017
// and for 18, it's "only" 20s
@Restricted(NoExternalUse.class)
@@ -931,12 +935,17 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
@Override
public String encode(CharSequence rawPassword) {
return BCrypt.hashpw(rawPassword.toString(), BCrypt.gensalt());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
try {
return super.encode(rawPassword);
} catch (IllegalArgumentException ex) {
if (ex.getMessage().equals("password cannot be more than 72 bytes")) {
if (rawPassword.toString().matches("\\A\\p{ASCII}+\\z")) {
throw new IllegalArgumentException(Messages.HudsonPrivateSecurityRealm_CreateAccount_BCrypt_PasswordTooLong_ASCII());
}
throw new IllegalArgumentException(Messages.HudsonPrivateSecurityRealm_CreateAccount_BCrypt_PasswordTooLong());
}
throw ex;
}
}
/**
@@ -978,6 +987,9 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
@Override
public String encode(CharSequence rawPassword) {
if (rawPassword.length() < FIPS_PASSWORD_LENGTH) {
throw new IllegalArgumentException(Messages.HudsonPrivateSecurityRealm_CreateAccount_FIPS_PasswordLengthInvalid());
}
try {
return generatePasswordHashWithPBKDF2(rawPassword);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
@@ -25,6 +25,7 @@
package hudson.security;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.ExtensionPoint;
@@ -382,6 +383,7 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten
rsp.sendRedirect2(getPostLogOutUrl2(req, auth));
}
@SuppressFBWarnings(value = "INSECURE_COOKIE", justification = "TODO needs triage")
private void resetRememberMeCookie(StaplerRequest2 req, StaplerResponse2 rsp, String contextPath) {
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, "");
cookie.setMaxAge(0);
@@ -977,6 +977,11 @@ public class SlaveComputer extends Computer {
}
}
@Override
public String toString() {
return nodeName != null ? super.toString() + "[" + nodeName + "]" : super.toString();
}
/**
* Grabs a {@link ComputerLauncher} out of {@link Node} to keep it in this {@link Computer}.
* The returned launcher will be set to {@link #launcher} and used to carry out the actual launch operation.
@@ -38,7 +38,7 @@ import java.util.logging.Logger;
*
* @author Kohsuke Kawaguchi
*/
@Deprecated(since = "TODO")
@Deprecated(since = "2.505")
public class Scrambler {
private static final Logger LOGGER = Logger.getLogger(Scrambler.class.getName());
@@ -72,8 +72,12 @@ public class RenderOnDemandClosure {
assert !bodyStack.isEmpty(); // there must be at least one, which is the direct child of <l:renderOnDemand>
Map<String, Object> variables = new HashMap<>();
for (String v : Util.fixNull(attributesToCapture).split(","))
variables.put(v.intern(), context.getVariable(v));
String _attributesToCapture = Util.fixEmpty(attributesToCapture);
if (_attributesToCapture != null) {
for (String v : _attributesToCapture.split(",")) {
variables.put(v.intern(), context.getVariable(v));
}
}
// capture the current base of context for descriptors
currentDescriptorByNameUrl = Descriptor.getCurrentDescriptorByNameUrl();
@@ -86,6 +90,7 @@ public class RenderOnDemandClosure {
for (String adjunct : _adjuncts) {
this.adjuncts[i++] = adjunct.intern();
}
LOGGER.fine(() -> "captured " + variables);
}
/**
@@ -96,6 +101,7 @@ public class RenderOnDemandClosure {
return new HttpResponse() {
@Override
public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object node) throws IOException, ServletException {
LOGGER.fine(() -> "rendering " + req.getPathInfo());
req.getWebApp().getDispatchValidator().allowDispatch(req, rsp);
try {
new DefaultScriptInvoker() {
@@ -36,7 +36,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Expose {@link hudson.diagnosis.MemoryUsageMonitor#heap} at the {@code /hudson.diagnosis.MemoryUsageMonitor/heap} URL.
*
* @since TODO
* @since 2.505
*/
@Extension
@Restricted(NoExternalUse.class)
@@ -27,6 +27,7 @@ package jenkins.diagnostics;
import hudson.Extension;
import hudson.init.InitMilestone;
import hudson.model.AdministrativeMonitor;
import jenkins.health.HealthCheck;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
@@ -49,9 +50,26 @@ public class CompletedInitializationMonitor extends AdministrativeMonitor {
@Override
public boolean isActivated() {
return !isInitCompleted();
}
private static boolean isInitCompleted() {
final Jenkins instance = Jenkins.get();
// Safe to check in such way, because monitors are being checked in UI only.
// So Jenkins class construction and initialization must be always finished by the call of this extension.
return instance.getInitLevel() != InitMilestone.COMPLETED;
return instance.getInitLevel() == InitMilestone.COMPLETED;
}
@Extension
public static final class HealthCheckImpl implements HealthCheck {
@Override
public String getName() {
return "completedInitialization";
}
@Override
public boolean check() {
return isInitCompleted();
}
}
}
@@ -0,0 +1,54 @@
/*
* The MIT License
*
* Copyright (c) 2025, 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.
*/
package jenkins.health;
import hudson.ExtensionPoint;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;
/**
* <p>Specifies a health check that is essential for Jenkins to function properly.
* <br>If this health check fails, monitoring systems will treat the controller as unusable for meaningful tasks.
* <br>It is assumed that restarting Jenkins can resolve the issue.
* <p>For instance, low disk space is not a good health check because it can't be solved by restarting Jenkins.
* <br>Detecting a deadlock in a critical singleton thread would be a good health check as long as associated information (thread dump) is reported somewhere before restarting.
* @since 2.506
*/
@Restricted(Beta.class)
public interface HealthCheck extends ExtensionPoint {
/**
* @return the name of the health check. Must be unique among health check implementations.
* Defaults to the fully qualified class name.
*/
default String getName() {
return getClass().getName();
}
/**
* @return true if the health check passed.
*/
boolean check();
}
@@ -0,0 +1,66 @@
/*
* The MIT License
*
* Copyright (c) 2025, 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.
*/
package jenkins.health;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.model.InvisibleAction;
import hudson.model.UnprotectedRootAction;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.json.JsonHttpResponse;
/**
* Provides a health check action for Jenkins.
*/
@Extension
@Restricted(NoExternalUse.class)
public final class HealthCheckAction extends InvisibleAction implements UnprotectedRootAction {
@Override
public String getUrlName() {
return "health";
}
public HttpResponse doIndex() {
boolean success = true;
var failing = new JSONArray();
for (var healthCheck : ExtensionList.lookup(HealthCheck.class)) {
var check = healthCheck.check();
success &= check;
if (!check) {
failing.add(healthCheck.getName());
}
}
var payload = new JSONObject().element("status", success);
if (!success) {
payload = payload.element("failures", failing);
}
return new JsonHttpResponse(payload, success ? 200 : 503);
}
}
@@ -761,6 +761,7 @@ public class SetupWizard extends PageDecorator {
}
@Override
@SuppressFBWarnings(value = "UNVALIDATED_REDIRECT", justification = "TODO needs triage")
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// Force root requests to the setup wizard
if (request instanceof HttpServletRequest && !Jenkins.get().getInstallState().isSetupComplete()) {
@@ -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();
@@ -24,14 +24,20 @@
package jenkins.model;
import com.google.common.annotations.VisibleForTesting;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.listeners.RunListener;
import hudson.remoting.SingleLaneExecutorService;
import hudson.util.LogTaskListener;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.util.Timer;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
@@ -43,18 +49,32 @@ public class GlobalBuildDiscarderListener extends RunListener<Run> {
private static final Logger LOGGER = Logger.getLogger(GlobalBuildDiscarderListener.class.getName());
private final ExecutorService executor = new SingleLaneExecutorService(Timer.get());
@Override
public void onFinalized(Run run) {
Job job = run.getParent();
try {
// Job-level build discarder execution is unconditional.
job.logRotate();
} catch (Exception e) {
LOGGER.log(Level.WARNING, e, () -> "Failed to rotate log for " + run);
}
// Avoid calling Job.logRotate twice in case JobGlobalBuildDiscarderStrategy is configured globally.
BackgroundGlobalBuildDiscarder.processJob(new LogTaskListener(LOGGER, Level.FINE), job,
GlobalBuildDiscarderConfiguration.get().getConfiguredBuildDiscarders().stream()
.filter(s -> !(s instanceof JobGlobalBuildDiscarderStrategy)));
executor.execute(() -> {
Job job = run.getParent();
try {
// Job-level build discarder execution is unconditional.
job.logRotate();
} catch (Exception e) {
LOGGER.log(Level.WARNING, e, () -> "Failed to rotate log for " + run);
}
// Avoid calling Job.logRotate twice in case JobGlobalBuildDiscarderStrategy is configured globally.
BackgroundGlobalBuildDiscarder.processJob(new LogTaskListener(LOGGER, Level.FINE), job,
GlobalBuildDiscarderConfiguration.get().getConfiguredBuildDiscarders().stream()
.filter(s -> !(s instanceof JobGlobalBuildDiscarderStrategy)));
});
}
/**
* Waits for all currently scheduled or running discards to complete.
*/
@VisibleForTesting
@Restricted(DoNotUse.class)
public static void await() throws Exception {
ExtensionList.lookupSingleton(GlobalBuildDiscarderListener.class).executor.submit(() -> {}).get();
}
}
@@ -2246,7 +2246,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
void trimLabels(Node... nodes) {
Set<LabelAtom> includedLabels = new HashSet<>();
Arrays.stream(nodes).filter(Objects::nonNull).forEach(n -> includedLabels.addAll(n.getAssignedLabels()));
Arrays.stream(nodes).filter(Objects::nonNull).forEach(n -> includedLabels.addAll(n.drainLabelsToTrim()));
trimLabels(includedLabels);
}
@@ -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();
@@ -3384,6 +3384,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
} catch (InvalidBuildsDir invalidBuildsDir) {
throw new IOException(invalidBuildsDir);
}
updateComputers(this);
}
private void setBuildsAndWorkspacesDir() throws IOException, InvalidBuildsDir {
@@ -4963,6 +4964,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* Changes the icon size by changing the cookie
*/
@SuppressFBWarnings(value = "INSECURE_COOKIE", justification = "TODO needs triage")
public void doIconSize(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException, ServletException {
String qs = req.getQueryString();
if (qs == null)
+2 -3
View File
@@ -166,11 +166,10 @@ public class Nodes implements PersistenceRoot {
@Override
public void run() {
nodes.compute(node.getNodeName(), (ignoredNodeName, ignoredNode) -> old);
jenkins.updateComputers(node);
if (old != null) {
jenkins.updateComputers(node, old);
jenkins.trimLabels(node, old);
} else {
jenkins.updateComputers(node);
jenkins.trimLabels(node);
}
}
@@ -265,7 +264,7 @@ public class Nodes implements PersistenceRoot {
Util.deleteRecursive(new File(getRootDir(), oldOne.getNodeName()));
}
Queue.withLock(() -> {
jenkins.updateComputers(oldOne, newOne);
jenkins.updateComputers(newOne);
jenkins.trimLabels(oldOne, newOne);
});
NodeListener.fireOnUpdated(oldOne, newOne);
@@ -1,271 +0,0 @@
/*
* The MIT License
*
* Copyright 2014 Jesse Glick.
*
* 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;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINER;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Util;
import hudson.model.Job;
import hudson.model.Run;
import hudson.util.AtomicFileWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Converts legacy {@code builds} directories to the current format.
*
* There would be one instance associated with each {@link Job}, to retain ID → build# mapping.
*
* The {@link Job#getBuildDir} is passed to every method call (rather than being cached) in case it is moved.
*/
@Restricted(NoExternalUse.class)
public final class RunIdMigrator {
private final DateFormat legacyIdFormatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
static final Logger LOGGER = Logger.getLogger(RunIdMigrator.class.getName());
private static final String MAP_FILE = "legacyIds";
/** avoids wasting a map for new jobs */
private static final Map<String, Integer> EMPTY = new TreeMap<>();
private @NonNull Map<String, Integer> idToNumber = EMPTY;
public RunIdMigrator() {}
/**
* @return whether there was a file to load
*/
private boolean load(File dir) {
File f = new File(dir, MAP_FILE);
if (!f.isFile()) {
return false;
}
if (f.length() == 0) {
return true;
}
idToNumber = new TreeMap<>();
try {
for (String line : Files.readAllLines(Util.fileToPath(f), StandardCharsets.UTF_8)) {
int i = line.indexOf(' ');
idToNumber.put(line.substring(0, i), Integer.parseInt(line.substring(i + 1)));
}
} catch (Exception x) { // IOException, IndexOutOfBoundsException, NumberFormatException
LOGGER.log(WARNING, "could not read from " + f, x);
}
return true;
}
private void save(File dir) {
File f = new File(dir, MAP_FILE);
try (AtomicFileWriter w = new AtomicFileWriter(f)) {
try {
synchronized (this) {
for (Map.Entry<String, Integer> entry : idToNumber.entrySet()) {
w.write(entry.getKey() + ' ' + entry.getValue() + '\n');
}
}
w.commit();
} finally {
w.abort();
}
} catch (IOException x) {
LOGGER.log(WARNING, "could not save changes to " + f, x);
}
}
/**
* Called when a job is first created.
* Just saves an empty marker indicating that this job needs no migration.
* @param dir as in {@link Job#getBuildDir}
*/
public void created(File dir) {
save(dir);
}
/**
* Perform one-time migration if this has not been done already.
* Where previously there would be a {@code 2014-01-02_03-04-05/build.xml} specifying {@code <number>99</number>} plus a symlink {@code 99 → 2014-01-02_03-04-05},
* after migration there will be just {@code 99/build.xml} specifying {@code <id>2014-01-02_03-04-05</id>} and {@code <timestamp>…</timestamp>} according to local time zone at time of migration.
* Newly created builds are untouched.
* Does not throw {@link IOException} since we make a best effort to migrate but do not consider it fatal to job loading if we cannot.
* @param dir as in {@link Job#getBuildDir}
* @param jenkinsHome root directory of Jenkins (for logging only)
* @return true if migration was performed
*/
public synchronized boolean migrate(File dir, @CheckForNull File jenkinsHome) {
if (load(dir)) {
LOGGER.log(FINER, "migration already performed for {0}", dir);
return false;
}
if (!dir.isDirectory()) {
LOGGER.log(/* normal during Job.movedTo */FINE, "{0} was unexpectedly missing", dir);
return false;
}
LOGGER.log(INFO, "Migrating build records in {0}. See https://www.jenkins.io/redirect/build-record-migration for more information.", dir);
doMigrate(dir);
save(dir);
return true;
}
private static final Pattern NUMBER_ELT = Pattern.compile("(?m)^ <number>(\\d+)</number>(\r?\n)");
private void doMigrate(File dir) {
idToNumber = new TreeMap<>();
File[] kids = dir.listFiles();
// Need to process symlinks first so we can rename to them.
List<File> kidsList = new ArrayList<>(Arrays.asList(kids));
Iterator<File> it = kidsList.iterator();
while (it.hasNext()) {
File kid = it.next();
String name = kid.getName();
try {
Integer.parseInt(name);
} catch (NumberFormatException x) {
LOGGER.log(FINE, "ignoring nonnumeric entry {0}", name);
continue;
}
try {
if (Util.isSymlink(kid)) {
LOGGER.log(FINE, "deleting build number symlink {0} → {1}", new Object[] {name, Util.resolveSymlink(kid)});
} else if (kid.isDirectory()) {
LOGGER.log(FINE, "ignoring build directory {0}", name);
continue;
} else {
LOGGER.log(WARNING, "need to delete anomalous file entry {0}", name);
}
Util.deleteFile(kid);
it.remove();
} catch (Exception x) {
LOGGER.log(WARNING, "failed to process " + kid, x);
}
}
it = kidsList.iterator();
while (it.hasNext()) {
File kid = it.next();
try {
String name = kid.getName();
try {
Integer.parseInt(name);
LOGGER.log(FINE, "skipping new build dir {0}", name);
continue;
} catch (NumberFormatException x) {
// OK, next…
}
if (!kid.isDirectory()) {
LOGGER.log(FINE, "skipping non-directory {0}", name);
continue;
}
long timestamp;
try {
synchronized (legacyIdFormatter) {
timestamp = legacyIdFormatter.parse(name).getTime();
}
} catch (ParseException x) {
LOGGER.log(WARNING, "found unexpected dir {0}", name);
continue;
}
File buildXml = new File(kid, "build.xml");
if (!buildXml.isFile()) {
LOGGER.log(WARNING, "found no build.xml in {0}", name);
continue;
}
String xml = Files.readString(Util.fileToPath(buildXml), StandardCharsets.UTF_8);
Matcher m = NUMBER_ELT.matcher(xml);
if (!m.find()) {
LOGGER.log(WARNING, "could not find <number> in {0}/build.xml", name);
continue;
}
int number = Integer.parseInt(m.group(1));
String nl = m.group(2);
xml = m.replaceFirst(" <id>" + name + "</id>" + nl + " <timestamp>" + timestamp + "</timestamp>" + nl);
File newKid = new File(dir, Integer.toString(number));
move(kid, newKid);
Files.writeString(Util.fileToPath(newKid).resolve("build.xml"), xml, StandardCharsets.UTF_8);
LOGGER.log(FINE, "fully processed {0} → {1}", new Object[] {name, number});
idToNumber.put(name, number);
} catch (Exception x) {
LOGGER.log(WARNING, "failed to process " + kid, x);
}
}
}
/**
* Tries to move/rename a file from one path to another.
* Uses {@link java.nio.file.Files#move} when available.
* Does not use {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} or any other options.
* TODO candidate for moving to {@link Util}
*/
static void move(File src, File dest) throws IOException {
try {
Files.move(src.toPath(), dest.toPath());
} catch (IOException x) {
throw x;
} catch (RuntimeException x) {
throw new IOException(x);
}
}
/**
* Look up a historical run by ID.
* @param id a nonnumeric ID which may be a valid {@link Run#getId}
* @return the corresponding {@link Run#number}, or 0 if unknown
*/
public synchronized int findNumber(@NonNull String id) {
Integer number = idToNumber.get(id);
return number != null ? number : 0;
}
/**
* Delete the record of a build.
* @param dir as in {@link Job#getBuildDir}
* @param id a {@link Run#getId}
*/
public synchronized void delete(File dir, String id) {
if (idToNumber.remove(id) != null) {
save(dir);
}
}
}
@@ -24,7 +24,6 @@
package jenkins.model;
import hudson.model.Computer;
import hudson.model.LoadStatistics;
import hudson.model.Node;
import hudson.model.Node.Mode;
@@ -52,35 +51,6 @@ public class UnlabeledLoadStatistics extends LoadStatistics {
super(0, 0);
}
@Override
public int computeIdleExecutors() {
int r = 0;
for (Computer c : Jenkins.get().getComputers()) {
Node node = c.getNode();
if (node != null && node.getMode() == Mode.NORMAL && (c.isOnline() || c.isConnecting()) && c.isAcceptingTasks()) {
r += c.countIdle();
}
}
return r;
}
@Override
public int computeTotalExecutors() {
int r = 0;
for (Computer c : Jenkins.get().getComputers()) {
Node node = c.getNode();
if (node != null && node.getMode() == Mode.NORMAL && c.isOnline()) {
r += c.countExecutors();
}
}
return r;
}
@Override
public int computeQueueLength() {
return Jenkins.get().getQueue().strictCountBuildableItemsFor(null);
}
@Override
protected Iterable<Node> getNodes() {
return nodes;
@@ -50,6 +50,7 @@ import java.util.Spliterator;
import java.util.Spliterators;
import java.util.TreeMap;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
@@ -343,6 +344,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
kids = MemoryReductionUtil.EMPTY_STRING_ARRAY;
}
SortedIntList list = new SortedIntList(kids.length / 2);
var allower = createLoadAllower();
for (String s : kids) {
if (!BUILD_NUMBER.matcher(s).matches()) {
// not a build directory
@@ -350,7 +352,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
}
try {
int buildNumber = Integer.parseInt(s);
if (allowLoad(buildNumber)) {
if (allower.test(buildNumber)) {
list.add(buildNumber);
} else {
LOGGER.fine(() -> "declining to consider " + buildNumber + " in " + dir);
@@ -368,6 +370,11 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
return true;
}
@Restricted(NoExternalUse.class)
protected IntPredicate createLoadAllower() {
return this::allowLoad;
}
/**
* Permits a previous blocked build number to be eligible for loading.
* @param buildNumber a build number
@@ -49,7 +49,6 @@ import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.RunIdMigrator;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -147,9 +146,6 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT, RunT> & Queue.Task &
return loadBuild(dir);
}
});
RunIdMigrator runIdMigrator = asJob().runIdMigrator;
assert runIdMigrator != null;
r.runIdMigrator = runIdMigrator;
return r;
}
@@ -1,7 +1,7 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* 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,25 +22,29 @@
* THE SOFTWARE.
*/
package hudson.util;
package jenkins.model.navigation;
import java.io.IOException;
import hudson.Extension;
import hudson.model.RootAction;
/**
* {@link IOException} with linked exception.
*
* @author Kohsuke Kawaguchi
* @deprecated Just use {@link IOException}, which since Java 6 supports a cause.
* TODO
*/
@Deprecated
public class IOException2 extends IOException {
@Extension(ordinal = 999)
public class SearchAction implements RootAction {
public IOException2(Throwable cause) {
super(cause);
@Override
public String getIconFileName() {
return "symbol-search";
}
public IOException2(String s, Throwable cause) {
super(s, cause);
@Override
public String getDisplayName() {
return "Search";
}
@Override
public String getUrlName() {
return null;
}
}
@@ -0,0 +1,96 @@
/*
* The MIT License
*
* Copyright (c) 2025, Jan Faracik
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.model.navigation;
import static hudson.Functions.getAvatar;
import hudson.Extension;
import hudson.model.Action;
import hudson.model.RootAction;
import hudson.model.User;
import java.util.ArrayList;
import java.util.List;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Display the user avatar in the navigation bar.
* Provides a handy jumplist for common user actions.
*/
@Extension(ordinal = -1)
public class UserAction implements RootAction {
@Override
public String getIconFileName() {
User current = User.current();
if (current == null) {
return null;
}
return getAvatar(current, "96x96");
}
@Override
public String getDisplayName() {
User current = User.current();
if (current == null) {
return null;
}
return current.getFullName();
}
@Override
public String getUrlName() {
User current = User.current();
if (current == null) {
return null;
}
return current.getUrl();
}
@Restricted(NoExternalUse.class)
public User getUser() {
return User.current();
}
@Restricted(NoExternalUse.class)
public List<Action> getActions() {
User current = User.current();
if (User.current() == null) {
return null;
}
List<Action> actions = new ArrayList<>();
actions.addAll(current.getPropertyActions());
actions.addAll(current.getTransientActions());
return actions.stream().filter(e -> e.getIconFileName() != null).toList();
}
}
@@ -47,7 +47,7 @@ import org.kohsuke.stapler.StaplerRequest2;
/**
* Collect information about which URLs in {@code /extensionList/} are being accessed.
*
* @since TODO
* @since 2.505
*/
@Extension
@Restricted(NoExternalUse.class)
@@ -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() {
@@ -1,2 +0,0 @@
new hudson.util.IOException2($t) :: $t instanceof Throwable => new java.io.IOException($t);;
new hudson.util.IOException2($s, $t) :: $s instanceof String && $t instanceof Throwable => new java.io.IOException($s, $t);;
@@ -473,7 +473,10 @@ window.addEventListener("load", function () {
candidate.checked = candidate.dataset.compatWarning === "false";
}
}
window.updateTableHeaderCheckbox();
const ev = new CustomEvent("updateIcon", {
bubbles: true,
});
compatibleCheckbox.dispatchEvent(ev);
});
}
@@ -72,7 +72,8 @@ THE SOFTWARE.
<form id="form" method="post" action="install">
<table id="plugins" class="jenkins-table sortable"
data-hasAdmin="${h.hasPermission(app.ADMINISTER)}">
data-hasAdmin="${h.hasPermission(app.ADMINISTER)}"
data-health="${app.updateCenter.healthScoresAvailable}">
<thead>
<tr>
<l:isAdmin>
@@ -82,6 +83,14 @@ THE SOFTWARE.
</l:isAdmin>
<th initialSortDir="down">${%Name}</th>
<th>${%Released}</th>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<th>
<j:set var="healthTooltip">${%healthTooltip}</j:set>
<span data-html-tooltip="${healthTooltip}" data-tooltip-interactive="true">
${%Health}
</span>
</th>
</j:if>
</tr>
</thead>
<tbody/>
@@ -0,0 +1,3 @@
healthTooltip=\
A score evaluating the health of a plugin, using metrics such as actively maintained, recommended repository configuration applied and documentation provided. \
<a href="https://jenkins.io/redirect/plugin-health-score" target="_blank">Learn more</a>
@@ -26,3 +26,4 @@ Install\ after\ restart=Installer après le redémarrage
Install=Installer
Name=Nom
Released=Publié
Health=Santé
@@ -78,6 +78,14 @@ THE SOFTWARE.
<thead>
<tr>
<th initialSortDir="down">${%Name}</th>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<th>
<j:set var="healthTooltip">${%healthTooltip}</j:set>
<span data-html-tooltip="${healthTooltip}" data-tooltip-interactive="true">
${%Health}
</span>
</th>
</j:if>
<th class="jenkins-table__cell--tight">${%Enabled}</th>
<l:isAdmin>
<th data-sort-disable="true"/>
@@ -161,6 +169,22 @@ THE SOFTWARE.
</div>
</j:if>
</td>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<td class="jenkins-table__cell">
<span>
<j:choose>
<j:when test="${p.healthScore != null}">
<a href="${p.url}/healthScore" class="jenkins-healthScore--badge jenkins-healthScore--${p.healthScoreClass}" target="_blank" rel="noopener noreferrer">
<st:out value="${p.healthScore}"/>
</a>
</j:when>
<j:otherwise>
<l:icon src="symbol-status-aborted" class="icon-lg" tooltip="${%No health score available}"/>
</j:otherwise>
</j:choose>
</span>
</td>
</j:if>
<j:set var="state" value="${p.enabled?'true':null}"/>
<td class="jenkins-table__cell--tight enable" data="${state}">
<div class="jenkins-table__cell__button-wrapper">
@@ -34,3 +34,6 @@ adoptThisPlugin=\
reportIssue=Report an issue with this plugin
uninstall-title=Are you sure you want to uninstall {0}?
uninstall-description=This will remove the plugin binary from your $JENKINS_HOME, but it will leave the configuration files of the plugin untouched
healthTooltip=\
A score evaluating the health of a plugin, using metrics such as actively maintained, recommended repository configuration applied and documentation provided. \
<a href="https://jenkins.io/redirect/plugin-health-score" target="_blank">Learn more</a>
@@ -32,3 +32,4 @@ Uninstall=Désinstaller
Version=Version
Previously\ installed\ version=Version précédente
downgradeTo=Rétrograder à {0}
Health=Santé
@@ -84,6 +84,14 @@ THE SOFTWARE.
<th initialSortDir="down">${%Name}</th>
<th>${%Released}</th>
<th>${%Installed}</th>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<th>
<j:set var="healthTooltip">${%healthTooltip}</j:set>
<span data-html-tooltip="${healthTooltip}" data-tooltip-interactive="true">
${%Health}
</span>
</th>
</j:if>
</tr>
</thead>
<j:forEach var="p" items="${list}">
@@ -225,6 +233,20 @@ THE SOFTWARE.
</j:otherwise>
</j:choose>
</td>
<j:if test="${app.updateCenter.healthScoresAvailable}">
<td>
<j:choose>
<j:when test="${p.healthScore != null}">
<a href="${p.wiki}/healthScore" class="jenkins-healthScore--badge jenkins-healthScore--${p.healthScoreClass}" target="_blank" rel="noopener noreferrer">
<st:out value="${p.healthScore}"/>
</a>
</j:when>
<j:otherwise>
<l:icon src="symbol-status-aborted" class="icon-lg" tooltip="${%No health score available}"/>
</j:otherwise>
</j:choose>
</td>
</j:if>
</tr>
</j:forEach>
</table>
@@ -48,3 +48,6 @@ adoptThisPlugin=\
deprecationWarning=<strong>This plugin is deprecated.</strong> In general, this means that it is either obsolete, no longer being developed, or may no longer work. <a href="{0}" rel="noopener noreferrer" target="_blank">Learn more.</a>
loading=Loading
CompatibleTooltip=Selects plugin updates that are compatible with their current version
healthTooltip=\
A score evaluating the health of a plugin, using metrics such as actively maintained, recommended repository configuration applied and documentation provided. \
<a href="https://jenkins.io/redirect/plugin-health-score" target="_blank">Learn more</a>
@@ -33,3 +33,4 @@ No\ updates\ available=Aucune mise à jour disponible
Version=Version
compatWarning=Avertissement: Cette nouvelle version n''est pas compatible avec la version installée. Il peut être nécessaire de reconfigurer les tâches utilisant ce plugin.
coreWarning=Ce plugin est conçu pour Jenkins {0} ou une version plus récente. Il pourrait ne pas fonctionner avec votre version de Jenkins.
Health=Santé
@@ -38,9 +38,7 @@ THE SOFTWARE.
</t:summary>
</j:forEach>
<t:artifactList caption="${%Last Successful Artifacts}"
build="${it.lastSuccessfulBuild}" baseURL="lastSuccessfulBuild/"
permission="${it.lastSuccessfulBuild.ARTIFACTS}"/>
<t:artifactList build="${it.lastSuccessfulBuild}" caption="${%Last Successful Artifacts}"/>
</table>
<!-- merge fragments from the actions -->
@@ -52,7 +52,7 @@ THE SOFTWARE.
<j:set var="descriptor" value="${it.descriptor}" />
<j:set var="instance" value="${it}" />
<div class="jenkins-section jenkins-section--no-border jenkins-!-margin-top-6">
<div class="jenkins-section jenkins-section--no-border jenkins-!-margin-top-3">
<f:entry title="${%Description}" help="${app.markupFormatter.helpUrl}">
<f:textarea name="description" value="${it.description}" codemirror-mode="${app.markupFormatter.codeMirrorMode}" codemirror-config="${app.markupFormatter.codeMirrorConfig}" previewEndpoint="/markupFormatter/previewDescription"/>
</f:entry>
@@ -43,8 +43,7 @@ THE SOFTWARE.
<div>
<h2>${%Nodes}</h2>
<j:forEach var="n" items="${it.sortedNodes}">
<j:set var="c" value="${app.getComputer(n.nodeName)}"/>
<j:forEach var="c" items="${it.computers}">
<j:set var="url" value="${rootURL}/${c.url}"/>
<nobr>
<a href="${url}" class="jenkins-link--with-icon"><l:icon src="${c.iconClassName}" /></a>
@@ -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
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright 2025 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" xmlns:t="/lib/hudson">
<l:ajax>
<j:if test="${!h.artifactsPermissionEnabled or h.artifactsPermissionEnabled and it.hasPermission(it.ARTIFACTS)}">
<j:set var="artifacts" value="${it.getArtifactsUpTo(it.LIST_CUTOFF+1)}" />
<j:if test="${!empty(artifacts)}">
<t:summary icon="symbol-cube" href="${rootURL}/${it.url}artifact/">
<a href="${rootURL}/${it.url}artifact/">${caption != null ? caption : request2.getParameter('caption')}</a>
<j:if test="${it.building}">
<p>
${%building}
</p>
</j:if>
<j:if test="${size(artifacts) le it.LIST_CUTOFF}">
<!-- if not too many, just list them -->
<table class="fileList">
<j:forEach var="f" items="${artifacts}">
<tr>
<td>
<l:icon class="icon-document icon-sm"/>
</td>
<td>
<a href="${rootURL}/${it.url}artifact/${f.href}">${f.displayPath}</a>
</td>
<td class="fileSize">
${h.humanReadableByteSize(f.getFileSize())}
</td>
<td>
<j:if test="${app.fingerprintMap.ready}">
<a href="${rootURL}/${it.url}artifact/${f.href}/*fingerprint*/">
<l:icon class="icon-fingerprint icon-sm"/>
</a>
<st:nbsp/>
</j:if>
<a href="${rootURL}/${it.url}artifact/${f.href}/*view*/">${%view}</a>
</td>
</tr>
</j:forEach>
</table>
</j:if>
<!-- otherwise just show link to directory browser -->
</t:summary>
</j:if>
</j:if>
</l:ajax>
</j:jelly>
@@ -67,8 +67,7 @@ THE SOFTWARE.
<table>
<t:artifactList build="${it}" caption="${%Build Artifacts}"
permission="${it.ARTIFACTS}" />
<t:artifactList build="${it}" caption="${%Build Artifacts}"/>
<!-- give actions a chance to contribute summary item -->
<j:forEach var="a" items="${it.allActions}">
@@ -72,7 +72,7 @@ THE SOFTWARE.
<l:card title="Summary">
<div>
<table>
<t:artifactList build="${it}" caption="${%Build Artifacts}" permission="${it.ARTIFACTS}" />
<t:artifactList build="${it}" caption="${%Build Artifacts}"/>
<!-- give actions a chance to contribute summary item -->
<j:forEach var="a" items="${it.allActions}">
@@ -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>
@@ -37,6 +37,8 @@ HudsonPrivateSecurityRealm.ManageUserLinks.Description=Create/delete/modify user
HudsonPrivateSecurityRealm.CreateAccount.TextNotMatchWordInImage=Text didn''t match the word shown in the image
HudsonPrivateSecurityRealm.CreateAccount.PasswordNotMatch=Password didn''t match
HudsonPrivateSecurityRealm.CreateAccount.FIPS.PasswordLengthInvalid=Password must be at least 14 characters long
HudsonPrivateSecurityRealm.CreateAccount.BCrypt.PasswordTooLong.ASCII=Password cannot be longer than 72 characters.
HudsonPrivateSecurityRealm.CreateAccount.BCrypt.PasswordTooLong=Password cannot be longer than 72 characters (a-z, A-Z, 0-9, and basic punctuation; fewer when using other characters, like Chinese characters or emoji).
HudsonPrivateSecurityRealm.CreateAccount.PasswordRequired=Password is required
HudsonPrivateSecurityRealm.CreateAccount.UserNameRequired=User name is required
HudsonPrivateSecurityRealm.CreateAccount.UserNameInvalidCharacters=User name must only contain alphanumeric characters, underscore and dash
@@ -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
@@ -20,4 +20,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Last\ Success=Zadnji upsešen
Last\ Success=Zadnji uspešen
@@ -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)}"/>
@@ -28,7 +28,7 @@ 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 permission="${app.ADMINISTER}" title="${%threadDump}" type="one-column">
<f:breadcrumb-config-outline title="${%threadDump}" />
<l:breadcrumb title="${%threadDump}" />
<l:main-panel>
<h1>${%threadDumpTitle}</h1>
<j:set var="tdumps" value="${it.getAllThreadDumps()}"/>
@@ -1,47 +0,0 @@
<!--
The MIT License
Copyright (c) 2014, 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:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:layout title="Jenkins">
<l:header />
<l:main-panel>
<p>
To reverse the effect of build record migration, run the following command
on the server. See <a href="https://www.jenkins.io/redirect/build-record-migration">documentation</a>
for more details:
</p>
<table style="width:100%">
<tr>
<td style="line-height:2em; width:80%">
<input type="text" value="${it.command}" style="width:100%"/>
</td>
<td style="line-height:2em">
<l:copyButton text="${it.command}"/>
</td>
</tr>
</table>
</l:main-panel>
</l:layout>
</j:jelly>
@@ -1,24 +0,0 @@
# The MIT License
#
# Bulgarian translation: Copyright (c) 2016, 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.
Copied=\
Копирано
@@ -1,23 +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.
Copied=Kopiert
@@ -1,24 +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.
Copied=Copiato
@@ -1,23 +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.
@@ -1,3 +0,0 @@
# This file is under the MIT License by authors
Copied=Ископирано
@@ -1,3 +0,0 @@
# This file is under the MIT License by authors
Copied=Kopierades
@@ -1,23 +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.
Copied=已複製
@@ -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>
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff. Show more