mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-10-26 19:15:09 +00:00
Merge branch 'master' into fix-chevron-click
This commit is contained in:
182 files changed
+3894
-3255
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+17
-13
@@ -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
|
||||
-71
@@ -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>
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
tooltip=There are {0} active administrative monitors.
|
||||
tooltipSec=There are {0} active security administrative monitors.
|
||||
-27
@@ -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
|
||||
-24
@@ -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.
|
||||
-2
@@ -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.
|
||||
-25
@@ -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.
|
||||
-24
@@ -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
|
||||
-24
@@ -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.
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
tooltip=Det finns {0} aktiva administrativa övervakningar.
|
||||
tooltipSec=Det finns {0} aktiva säkerhetsadministrativa övervakningar.
|
||||
-25
@@ -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.
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
tooltip=有 {0} 個啟用中的管理監視器。
|
||||
tooltipSec=有 {0} 個啟用中的安全性管理監視器。
|
||||
-184
@@ -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;
|
||||
}
|
||||
}
|
||||
-128
@@ -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()}"/>
|
||||
|
||||
-47
@@ -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>
|
||||
-24
@@ -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=\
|
||||
Копирано
|
||||
-23
@@ -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
|
||||
-24
@@ -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
|
||||
-23
@@ -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.
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This file is under the MIT License by authors
|
||||
|
||||
Copied=Ископирано
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This file is under the MIT License by authors
|
||||
|
||||
Copied=Kopierades
|
||||
-23
@@ -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
Reference in new issue
Block a user