mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-13 20:12:14 +00:00
merging 2.0
This commit is contained in:
commit
f69080ea2a
53 files changed
+2046
-979
No files matched your search
+12
-3
@@ -55,11 +55,20 @@ Upcoming changes</a>
|
||||
<!-- Record your changes in the trunk here. -->
|
||||
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
|
||||
<ul class=image>
|
||||
<li class="rfe">
|
||||
Move periodic task log files from <code>JENKINS_HOME/*.log</code> to <code>JENKINS_HOME/logs/tasks/*.log</code> and rotate them periodically rather than overwrite every execution
|
||||
(<a href-"https://issues.jenkins-ci.org/browse/JENKINS-33068">issue 33068</a>)
|
||||
<li class="bug">
|
||||
Under some conditions Jenkins startup could fail because of incorrectly linked extensions; now recovering more gracefully.
|
||||
(<a href-"https://issues.jenkins-ci.org/browse/JENKINS-25440">issue 25440</a>)
|
||||
</ul>
|
||||
</div><!--=TRUNK-END=-->
|
||||
<h3><a name=v1.651>What's new in 1.651</a> (2016/02/28)</h3>
|
||||
<ul class=image>
|
||||
<li class="rfe">
|
||||
Move periodic task log files from <code>JENKINS_HOME/*.log</code> to <code>JENKINS_HOME/logs/tasks/*.log</code> and rotate them periodically rather than overwrite every execution.
|
||||
(<a href-"https://issues.jenkins-ci.org/browse/JENKINS-33068">issue 33068</a>)
|
||||
<li class="bug">
|
||||
Fix documentation of proxy configuration.
|
||||
(<a href="https://github.com/jenkinsci/jenkins/pull/2060">pull 2060</a>)
|
||||
</ul>
|
||||
<h3><a name=v1.650>What's new in 1.650</a> (2016/02/24)</h3>
|
||||
<ul class=image>
|
||||
<li class="major bug">
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ THE SOFTWARE.
|
||||
|
||||
<properties>
|
||||
<staplerFork>true</staplerFork>
|
||||
<stapler.version>1.239</stapler.version>
|
||||
<stapler.version>1.240</stapler.version>
|
||||
<spring.version>2.5.6.SEC03</spring.version>
|
||||
<groovy.version>1.8.9</groovy.version>
|
||||
</properties>
|
||||
|
||||
@@ -282,7 +282,6 @@ public abstract class ExtensionFinder implements ExtensionPoint {
|
||||
LOGGER.log(Level.SEVERE, "Failed to create Guice container from all the plugins",e);
|
||||
// failing to load all bindings are disastrous, so recover by creating minimum that works
|
||||
// by just including the core
|
||||
// TODO this recovery is pretty much useless; startup crashes anyway
|
||||
container = Guice.createInjector(new SezpozModule(loadSezpozIndices(Jenkins.class.getClassLoader())));
|
||||
}
|
||||
|
||||
@@ -479,7 +478,11 @@ public abstract class ExtensionFinder implements ExtensionPoint {
|
||||
m.invoke(ecl, c);
|
||||
c.getConstructors();
|
||||
c.getMethods();
|
||||
c.getFields();
|
||||
for (Field f : c.getFields()) {
|
||||
if (f.getAnnotation(javax.inject.Inject.class) != null || f.getAnnotation(com.google.inject.Inject.class) != null) {
|
||||
resolve(f.getType());
|
||||
}
|
||||
}
|
||||
LOGGER.log(Level.FINER, "{0} looks OK", c);
|
||||
while (c != Object.class) {
|
||||
c.getGenericSuperclass();
|
||||
|
||||
@@ -1189,7 +1189,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
|
||||
break;
|
||||
}
|
||||
updateCenter.persistInstallStatus();
|
||||
jenkins.setInstallState(InstallState.INITIAL_PLUGINS_INSTALLED);
|
||||
jenkins.setInstallState(InstallState.INITIAL_PLUGINS_INSTALLING.getNextState());
|
||||
InstallUtil.saveLastExecVersion();
|
||||
}
|
||||
}.start();
|
||||
|
||||
@@ -57,7 +57,8 @@ import jenkins.install.InstallState;
|
||||
import jenkins.install.InstallUtil;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.util.io.OnMaster;
|
||||
import net.sf.json.JSONArray;
|
||||
import net.sf.json.JSONObject;
|
||||
|
||||
import org.acegisecurity.Authentication;
|
||||
import org.acegisecurity.context.SecurityContext;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
@@ -73,7 +74,6 @@ import javax.annotation.Nonnull;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.servlet.ServletException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -81,12 +81,12 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -131,7 +131,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
|
||||
*/
|
||||
@ExportedBean
|
||||
public class UpdateCenter extends AbstractModelObject implements Saveable, OnMaster {
|
||||
|
||||
|
||||
private static final String UPDATE_CENTER_URL = System.getProperty(UpdateCenter.class.getName()+".updateCenterUrl","http://updates.jenkins-ci.org/");
|
||||
|
||||
/**
|
||||
@@ -142,7 +142,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
public static final String ID_UPLOAD = "_upload";
|
||||
|
||||
|
||||
/**
|
||||
* {@link ExecutorService} that performs installation.
|
||||
* @since 1.501
|
||||
@@ -155,7 +155,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
*/
|
||||
protected final ExecutorService updateService = Executors.newCachedThreadPool(
|
||||
new NamingThreadFactory(new DaemonThreadFactory(), "Update site data downloader"));
|
||||
|
||||
|
||||
/**
|
||||
* List of created {@link UpdateCenterJob}s. Access needs to be synchronized.
|
||||
*/
|
||||
@@ -302,6 +302,27 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
}
|
||||
}
|
||||
if (checkJob != null) {
|
||||
boolean isOffline = false;
|
||||
for (ConnectionStatus status : checkJob.connectionStates.values()) {
|
||||
if(ConnectionStatus.FAILED.equals(status)) {
|
||||
isOffline = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isOffline) {
|
||||
// retry connection states if determined to be offline
|
||||
checkJob.run();
|
||||
isOffline = false;
|
||||
for (ConnectionStatus status : checkJob.connectionStates.values()) {
|
||||
if(ConnectionStatus.FAILED.equals(status)) {
|
||||
isOffline = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!isOffline) { // also need to download the metadata
|
||||
updateAllSites();
|
||||
}
|
||||
}
|
||||
return HttpResponses.okJSON(checkJob.connectionStates);
|
||||
} else {
|
||||
return HttpResponses.errorJSON(String.format("Unknown site '%s'.", siteId));
|
||||
@@ -311,29 +332,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to bypass install wizard
|
||||
*/
|
||||
@Restricted(DoNotUse.class) // WebOnly
|
||||
public HttpResponse doCompleteInstall() {
|
||||
if(isRestartRequiredForCompletion()) {
|
||||
Jenkins.getActiveInstance().setInstallState(InstallState.RESTART);
|
||||
}
|
||||
InstallUtil.saveLastExecVersion();
|
||||
Jenkins.getActiveInstance().setInstallState(InstallState.INITIAL_PLUGINS_INSTALLED);
|
||||
return HttpResponses.okJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to determine if there was an incomplete installation, what the statuses of the plugins are
|
||||
*/
|
||||
@Restricted(DoNotUse.class) // WebOnly
|
||||
public HttpResponse doIncompleteInstallStatus() {
|
||||
try {
|
||||
Map<String,String> jobs = InstallUtil.getPersistedInstallStatus();
|
||||
if(jobs == null) {
|
||||
jobs = Collections.emptyMap();
|
||||
}
|
||||
Map<String,String> jobs = InstallUtil.getPersistedInstallStatus();
|
||||
if(jobs == null) {
|
||||
jobs = Collections.emptyMap();
|
||||
}
|
||||
return HttpResponses.okJSON(jobs);
|
||||
} catch (Exception e) {
|
||||
return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage()));
|
||||
@@ -353,16 +361,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
if (job instanceof InstallationJob) {
|
||||
InstallationJob installationJob = (InstallationJob) job;
|
||||
if(!installationJob.status.isSuccess()) {
|
||||
activeInstalls = true;
|
||||
activeInstalls = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(activeInstalls) {
|
||||
InstallUtil.persistInstallStatus(jobs); // save this info
|
||||
InstallUtil.persistInstallStatus(jobs); // save this info
|
||||
}
|
||||
else {
|
||||
InstallUtil.clearInstallStatus(); // clear this info
|
||||
InstallUtil.clearInstallStatus(); // clear this info
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +387,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
public HttpResponse doInstallStatus(StaplerRequest request) {
|
||||
try {
|
||||
String correlationId = request.getParameter("correlationId");
|
||||
Map<String,Object> response = new HashMap<>();
|
||||
response.put("state", Jenkins.getInstance().getInstallState().name());
|
||||
List<Map<String, String>> installStates = new ArrayList<>();
|
||||
response.put("jobs", installStates);
|
||||
List<UpdateCenterJob> jobCopy = getJobs();
|
||||
|
||||
for (UpdateCenterJob job : jobCopy) {
|
||||
@@ -400,7 +411,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
}
|
||||
}
|
||||
}
|
||||
return HttpResponses.okJSON(JSONArray.fromObject(installStates));
|
||||
return HttpResponses.okJSON(JSONObject.fromObject(response));
|
||||
} catch (Exception e) {
|
||||
return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage()));
|
||||
}
|
||||
@@ -558,7 +569,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
}
|
||||
response.sendRedirect2(".");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancel all scheduled jenkins restarts
|
||||
*/
|
||||
@@ -845,16 +856,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
|
||||
return new ArrayList<Plugin>(pluginMap.values());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that all UpdateSites are up to date, without requiring a user to
|
||||
* browse to the instance.
|
||||
*
|
||||
*
|
||||
* @return a list of {@link FormValidation} for each updated Update Site
|
||||
* @throws ExecutionException
|
||||
* @throws InterruptedException
|
||||
* @throws ExecutionException
|
||||
* @throws InterruptedException
|
||||
* @since 1.501
|
||||
*
|
||||
*
|
||||
*/
|
||||
public List<FormValidation> updateAllSites() throws InterruptedException, ExecutionException {
|
||||
List <Future<FormValidation>> futures = new ArrayList<Future<FormValidation>>();
|
||||
@@ -864,8 +875,8 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
|
||||
List<FormValidation> results = new ArrayList<FormValidation>();
|
||||
|
||||
List<FormValidation> results = new ArrayList<FormValidation>();
|
||||
for (Future<FormValidation> f : futures) {
|
||||
results.add(f.get());
|
||||
}
|
||||
@@ -1211,7 +1222,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
public String getErrorMessage() {
|
||||
return error != null ? error.getMessage() : null;
|
||||
}
|
||||
|
||||
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
}
|
||||
@@ -1226,10 +1237,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
*/
|
||||
@Exported(inline=true)
|
||||
public volatile RestartJenkinsJobStatus status = new Pending();
|
||||
|
||||
|
||||
/**
|
||||
* Cancel job
|
||||
*/
|
||||
*/
|
||||
public synchronized boolean cancel() {
|
||||
if (status instanceof Pending) {
|
||||
status = new Canceled();
|
||||
@@ -1237,7 +1248,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public RestartJenkinsJob(UpdateSite site) {
|
||||
super(site);
|
||||
}
|
||||
@@ -1260,26 +1271,26 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
public abstract class RestartJenkinsJobStatus {
|
||||
@Exported
|
||||
public final int id = iota.incrementAndGet();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class Pending extends RestartJenkinsJobStatus {
|
||||
@Exported
|
||||
public String getType() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Running extends RestartJenkinsJobStatus {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class Failure extends RestartJenkinsJobStatus {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class Canceled extends RestartJenkinsJobStatus {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1603,7 +1614,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
File baseDir = pm.rootDir;
|
||||
return new File(baseDir, plugin.name + ".jpi");
|
||||
}
|
||||
|
||||
|
||||
private File getLegacyDestination() {
|
||||
File baseDir = pm.rootDir;
|
||||
return new File(baseDir, plugin.name + ".hpi");
|
||||
@@ -1649,7 +1660,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
public String toString() {
|
||||
return super.toString()+"[plugin="+plugin.title+"]";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the download is completed to overwrite
|
||||
* the old file with the new file.
|
||||
@@ -1704,7 +1715,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
File baseDir = pm.rootDir;
|
||||
final File legacy = new File(baseDir, plugin.name + ".hpi");
|
||||
if(legacy.exists()){
|
||||
return legacy;
|
||||
return legacy;
|
||||
}
|
||||
return new File(baseDir, plugin.name + ".jpi");
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import hudson.Extension;
|
||||
import hudson.ExtensionList;
|
||||
import hudson.FilePath;
|
||||
import hudson.Util;
|
||||
import hudson.slaves.WorkspaceList;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -89,6 +90,7 @@ public class WorkspaceCleanupThread extends AsyncPeriodicWork {
|
||||
listener.getLogger().println("Deleting " + ws + " on " + node.getDisplayName());
|
||||
try {
|
||||
ws.deleteRecursive();
|
||||
WorkspaceList.tempDir(ws).deleteRecursive();
|
||||
} catch (IOException x) {
|
||||
x.printStackTrace(listener.error("Failed to delete " + ws + " on " + node.getDisplayName()));
|
||||
} catch (InterruptedException x) {
|
||||
|
||||
+29
-6
@@ -29,6 +29,7 @@ import jenkins.model.Jenkins;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
import org.kohsuke.stapler.DataBoundSetter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collections;
|
||||
@@ -36,30 +37,52 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link AuthorizationStrategy} that grants full-control to authenticated user
|
||||
* (other than anonymous users.)
|
||||
* and optionally read access to anonymous users
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class FullControlOnceLoggedInAuthorizationStrategy extends AuthorizationStrategy {
|
||||
/**
|
||||
* Whether to allow anonymous read access, for backward compatibility
|
||||
* default is to allow it
|
||||
*/
|
||||
private boolean denyAnonymousReadAccess = false;
|
||||
|
||||
@DataBoundConstructor
|
||||
public FullControlOnceLoggedInAuthorizationStrategy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ACL getRootACL() {
|
||||
return THE_ACL;
|
||||
return denyAnonymousReadAccess ? AUTHENTICATED_READ : ANONYMOUS_READ;
|
||||
}
|
||||
|
||||
public List<String> getGroups() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, anonymous read access will be allowed
|
||||
*/
|
||||
public boolean isAllowAnonymousRead() {
|
||||
return !denyAnonymousReadAccess;
|
||||
}
|
||||
|
||||
@DataBoundSetter
|
||||
public void setAllowAnonymousRead(boolean allowAnonymousRead) {
|
||||
this.denyAnonymousReadAccess = !allowAnonymousRead;
|
||||
}
|
||||
|
||||
private static final SparseACL THE_ACL = new SparseACL(null);
|
||||
private static final SparseACL AUTHENTICATED_READ = new SparseACL(null);
|
||||
private static final SparseACL ANONYMOUS_READ = new SparseACL(null);
|
||||
|
||||
static {
|
||||
THE_ACL.add(ACL.EVERYONE, Jenkins.ADMINISTER,true);
|
||||
THE_ACL.add(ACL.ANONYMOUS, Jenkins.ADMINISTER,false);
|
||||
THE_ACL.add(ACL.ANONYMOUS,Permission.READ,true);
|
||||
ANONYMOUS_READ.add(ACL.EVERYONE, Jenkins.ADMINISTER,true);
|
||||
ANONYMOUS_READ.add(ACL.ANONYMOUS, Jenkins.ADMINISTER,false);
|
||||
ANONYMOUS_READ.add(ACL.ANONYMOUS, Permission.READ,true);
|
||||
|
||||
AUTHENTICATED_READ.add(ACL.EVERYONE, Jenkins.ADMINISTER, true);
|
||||
AUTHENTICATED_READ.add(ACL.ANONYMOUS, Jenkins.ADMINISTER, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, David Calavera, Seiji Sogabe
|
||||
*
|
||||
*
|
||||
* 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
|
||||
@@ -29,6 +29,8 @@ import hudson.ExtensionList;
|
||||
import hudson.Util;
|
||||
import hudson.diagnosis.OldDataMonitor;
|
||||
import hudson.model.Descriptor;
|
||||
import jenkins.install.InstallState;
|
||||
import jenkins.install.SetupWizard;
|
||||
import jenkins.model.Jenkins;
|
||||
import hudson.model.ManagementLink;
|
||||
import hudson.model.ModelObject;
|
||||
@@ -97,7 +99,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
|
||||
/**
|
||||
* If true, sign up is not allowed.
|
||||
* <p>
|
||||
* This is a negative switch so that the default value 'false' remains compatible with older installations.
|
||||
* This is a negative switch so that the default value 'false' remains compatible with older installations.
|
||||
*/
|
||||
private final boolean disableSignup;
|
||||
|
||||
@@ -279,14 +281,36 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
|
||||
* This can be run by anyone, but only to create the very first user account.
|
||||
*/
|
||||
public void doCreateFirstAccount(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
if(hasSomeUser()) {
|
||||
boolean inSetup = !Jenkins.getInstance().getInstallState().isSetupComplete();
|
||||
if(!inSetup && hasSomeUser()) {
|
||||
rsp.sendError(SC_UNAUTHORIZED,"First user was already created");
|
||||
return;
|
||||
}
|
||||
User u = createAccount(req, rsp, false, "firstUser.jelly");
|
||||
if (u!=null) {
|
||||
tryToMakeAdmin(u);
|
||||
loginAndTakeBack(req, rsp, u);
|
||||
|
||||
User admin = null;
|
||||
try {
|
||||
String view = "firstUser.jelly";
|
||||
if(inSetup) {
|
||||
admin = getUser(SetupWizard.initialSetupAdminUserName);
|
||||
if(admin != null) {
|
||||
admin.delete(); // assume the new user may well be 'admin'
|
||||
}
|
||||
view = "setupWizardFirstUser.jelly";
|
||||
}
|
||||
|
||||
User u = createAccount(req, rsp, false, view);
|
||||
if (u!=null) {
|
||||
tryToMakeAdmin(u);
|
||||
if(admin != null) {
|
||||
admin = null;
|
||||
}
|
||||
Jenkins.getInstance().setInstallState(InstallState.CREATE_ADMIN_USER.getNextState());
|
||||
loginAndTakeBack(req, rsp, u);
|
||||
}
|
||||
} finally {
|
||||
if(admin != null) {
|
||||
admin.save(); // recreate this initial user if something failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ package hudson.slaves;
|
||||
import hudson.FilePath;
|
||||
import hudson.Functions;
|
||||
import hudson.model.Computer;
|
||||
import hudson.model.DirectoryBrowserSupport;
|
||||
import java.io.Closeable;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -283,6 +284,25 @@ public final class WorkspaceList {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates a conventional temporary directory to be associated with a workspace.
|
||||
* <p>This directory is suitable for temporary files to be deleted later in the course of a build,
|
||||
* or caches and local repositories which should persist across builds done in the same workspace.
|
||||
* (If multiple workspaces are present for a single job built concurrently, via {@link #allocate(FilePath)}, each will get its own temporary directory.)
|
||||
* <p>It may also be used for security-sensitive files which {@link DirectoryBrowserSupport} ought not serve,
|
||||
* acknowledging that these will be readable by builds of other jobs done on the same node.
|
||||
* <p>Each plugin using this directory is responsible for specifying sufficiently unique subdirectory/file names.
|
||||
* {@link FilePath#createTempFile} may be used for this purpose if desired.
|
||||
* <p>The resulting directory may not exist; you may call {@link FilePath#mkdirs} on it if you need it to.
|
||||
* It may be deleted alongside the workspace itself during cleanup actions.
|
||||
* @param ws a directory such as a build workspace
|
||||
* @return a sibling directory, for example {@code …/something@tmp} for {@code …/something}
|
||||
* @since 1.652
|
||||
*/
|
||||
public static FilePath tempDir(FilePath ws) {
|
||||
return ws.sibling(ws.getName() + COMBINATOR + "tmp");
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(WorkspaceList.class.getName());
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,32 +34,63 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
@Restricted(NoExternalUse.class)
|
||||
public enum InstallState {
|
||||
/**
|
||||
* New Jenkins install.
|
||||
* The initial set up has been completed
|
||||
*/
|
||||
NEW,
|
||||
INITIAL_SETUP_COMPLETED(true, null),
|
||||
/**
|
||||
* Creating an admin user for an initial Jenkins install.
|
||||
*/
|
||||
CREATE_ADMIN_USER(false, INITIAL_SETUP_COMPLETED),
|
||||
/**
|
||||
* New Jenkins install. The user has kicked off the process of installing an
|
||||
* initial set of plugins (via the install wizard).
|
||||
*/
|
||||
INITIAL_PLUGINS_INSTALLING,
|
||||
INITIAL_PLUGINS_INSTALLING(false, CREATE_ADMIN_USER),
|
||||
/**
|
||||
* New Jenkins install. The initial set of plugins are now installed.
|
||||
* New Jenkins install.
|
||||
*/
|
||||
INITIAL_PLUGINS_INSTALLED,
|
||||
NEW(false, INITIAL_PLUGINS_INSTALLING),
|
||||
/**
|
||||
* Restart of an existing Jenkins install.
|
||||
*/
|
||||
RESTART,
|
||||
RESTART(true, INITIAL_SETUP_COMPLETED),
|
||||
/**
|
||||
* Upgrade of an existing Jenkins install.
|
||||
*/
|
||||
UPGRADE,
|
||||
UPGRADE(true, INITIAL_SETUP_COMPLETED),
|
||||
/**
|
||||
* Downgrade of an existing Jenkins install.
|
||||
*/
|
||||
DOWNGRADE,
|
||||
DOWNGRADE(true, INITIAL_SETUP_COMPLETED),
|
||||
/**
|
||||
* Jenkins started in test mode (JenkinsRule).
|
||||
*/
|
||||
TEST
|
||||
TEST(true, INITIAL_SETUP_COMPLETED),
|
||||
/**
|
||||
* Jenkins started in development mode: Bolean.getBoolean("hudson.Main.development").
|
||||
* Can be run normally with the -Djenkins.install.runSetupWizard=true
|
||||
*/
|
||||
DEVELOPMENT(true, INITIAL_SETUP_COMPLETED);
|
||||
|
||||
private final boolean isSetupComplete;
|
||||
private final InstallState nextState;
|
||||
|
||||
private InstallState(boolean isSetupComplete, InstallState nextState) {
|
||||
this.isSetupComplete = isSetupComplete;
|
||||
this.nextState = nextState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the initial setup is complete
|
||||
*/
|
||||
public boolean isSetupComplete() {
|
||||
return isSetupComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next state
|
||||
*/
|
||||
public InstallState getNextState() {
|
||||
return nextState;
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ public class InstallUtil {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName());
|
||||
|
||||
// tests need this to be 1.0
|
||||
private static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0");
|
||||
|
||||
/**
|
||||
@@ -68,8 +69,15 @@ public class InstallUtil {
|
||||
* @return The type of "startup" currently under way in Jenkins.
|
||||
*/
|
||||
public static InstallState getInstallState() {
|
||||
if (Functions.getIsUnitTest()) {
|
||||
return InstallState.TEST;
|
||||
// install wizard will always run if environment specified
|
||||
if (!Boolean.getBoolean("jenkins.install.runSetupWizard")) {
|
||||
if (Functions.getIsUnitTest()) {
|
||||
return InstallState.TEST;
|
||||
}
|
||||
|
||||
if (Boolean.getBoolean("hudson.Main.development")) {
|
||||
return InstallState.DEVELOPMENT;
|
||||
}
|
||||
}
|
||||
|
||||
VersionNumber lastRunVersion = new VersionNumber(getLastExecVersion());
|
||||
@@ -77,6 +85,12 @@ public class InstallUtil {
|
||||
// Neither the top level config or the lastExecVersionFile have a version
|
||||
// stored in them, which means it's a new install.
|
||||
if (lastRunVersion.compareTo(NEW_INSTALL_VERSION) == 0) {
|
||||
// Edge case: used Jenkins 1 but did not save the system config page,
|
||||
// the version is not persisted and returns 1.0, so try to check if
|
||||
// they actually did anything
|
||||
if (!Jenkins.getInstance().getItemMap().isEmpty()) {
|
||||
return InstallState.UPGRADE;
|
||||
}
|
||||
return InstallState.NEW;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
package jenkins.install;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
|
||||
import hudson.BulkChange;
|
||||
import hudson.ExtensionList;
|
||||
import hudson.model.User;
|
||||
import hudson.model.UserProperty;
|
||||
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
|
||||
import hudson.security.HudsonPrivateSecurityRealm;
|
||||
import hudson.security.PermissionAdder;
|
||||
import hudson.security.SecurityRealm;
|
||||
import hudson.security.csrf.DefaultCrumbIssuer;
|
||||
import hudson.util.HttpResponses;
|
||||
import hudson.util.PluginServletFilter;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.security.s2m.AdminWhitelistRule;
|
||||
|
||||
/**
|
||||
* A Jenkins instance used during first-run to provide a limited set of services while
|
||||
* initial installation is in progress
|
||||
*/
|
||||
public class SetupWizard {
|
||||
/**
|
||||
* The security token parameter name
|
||||
*/
|
||||
public static String initialSetupAdminUserName = "admin";
|
||||
|
||||
private final Logger LOGGER = Logger.getLogger(SetupWizard.class.getName());
|
||||
|
||||
public SetupWizard(Jenkins j) throws IOException {
|
||||
User admin;
|
||||
// Create an admin user by default with a
|
||||
// difficult password
|
||||
if(j.getSecurityRealm() == null || j.getSecurityRealm() == SecurityRealm.NO_AUTHENTICATION) { // this seems very fragile
|
||||
BulkChange bc = new BulkChange(j);
|
||||
|
||||
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null);
|
||||
j.setSecurityRealm(securityRealm);
|
||||
String randomUUID = UUID.randomUUID().toString().replace("-", "").toLowerCase(Locale.ENGLISH);
|
||||
admin = securityRealm.createAccount(SetupWizard.initialSetupAdminUserName, randomUUID);
|
||||
admin.addProperty(new SetupWizard.AuthenticationKey(randomUUID));
|
||||
|
||||
// Lock Jenkins down:
|
||||
FullControlOnceLoggedInAuthorizationStrategy authStrategy = new FullControlOnceLoggedInAuthorizationStrategy();
|
||||
authStrategy.setAllowAnonymousRead(false);
|
||||
j.setAuthorizationStrategy(authStrategy);
|
||||
|
||||
// Shut down all the ports we can by default:
|
||||
j.setSlaveAgentPort(-1); // -1 to disable
|
||||
|
||||
// require a crumb issuer
|
||||
j.setCrumbIssuer(new DefaultCrumbIssuer(false));
|
||||
|
||||
// set master -> slave security:
|
||||
j.getInjector().getInstance(AdminWhitelistRule.class)
|
||||
.setMasterKillSwitch(false);
|
||||
|
||||
try{
|
||||
j.save(); // !!
|
||||
} finally {
|
||||
bc.commit();
|
||||
}
|
||||
}
|
||||
else {
|
||||
admin = j.getUser(SetupWizard.initialSetupAdminUserName);
|
||||
}
|
||||
|
||||
String setupKey = null;
|
||||
if(admin != null && admin.getProperty(SetupWizard.AuthenticationKey.class) != null) {
|
||||
setupKey = admin.getProperty(SetupWizard.AuthenticationKey.class).getKey();
|
||||
}
|
||||
if(setupKey != null) {
|
||||
LOGGER.info("\n\n*************************************************************\n"
|
||||
+ "*************************************************************\n"
|
||||
+ "*************************************************************\n"
|
||||
+ "\n"
|
||||
+ "Jenkins initial setup is required. A security token is required to proceed. \n"
|
||||
+ "Please use the following security token to proceed to installation: \n"
|
||||
+ "\n"
|
||||
+ "" + setupKey + "\n"
|
||||
+ "\n"
|
||||
+ "*************************************************************\n"
|
||||
+ "*************************************************************\n"
|
||||
+ "*************************************************************\n");
|
||||
}
|
||||
|
||||
try {
|
||||
PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER);
|
||||
} catch (ServletException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the setupWizard filter, ensure all updates are written to disk, etc
|
||||
*/
|
||||
public HttpResponse doCompleteInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
Jenkins j = Jenkins.getActiveInstance();
|
||||
j.setInstallState(InstallState.INITIAL_SETUP_COMPLETED);
|
||||
InstallUtil.saveLastExecVersion();
|
||||
PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER);
|
||||
// Also, clean up the setup wizard if it's completed
|
||||
j.setSetupWizard(null);
|
||||
return HttpResponses.okJSON();
|
||||
}
|
||||
|
||||
// Stores a user property for the authentication key, which is really the auto-generated user's password
|
||||
public static class AuthenticationKey extends UserProperty {
|
||||
String key;
|
||||
|
||||
public AuthenticationKey() {
|
||||
}
|
||||
|
||||
public AuthenticationKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter will validate that the security token is provided
|
||||
*/
|
||||
private final Filter FORCE_SETUP_WIZARD_FILTER = new Filter() {
|
||||
@Override
|
||||
public void init(FilterConfig cfg) throws ServletException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
// As an extra measure of security, the install wizard generates a security token, and
|
||||
// requires the user to enter it before proceeding through the installation. Once set
|
||||
// we'll set a cookie so the subsequent operations succeed
|
||||
if (request instanceof HttpServletRequest) {
|
||||
HttpServletRequest req = (HttpServletRequest)request;
|
||||
//if (!Pattern.compile(".*[.](css|ttf|gif|woff|eot|png|js)").matcher(req.getRequestURI()).matches()) {
|
||||
// Allow js & css requests through
|
||||
if((req.getContextPath() + "/").equals(req.getRequestURI())) {
|
||||
chain.doFilter(new HttpServletRequestWrapper(req) {
|
||||
public String getRequestURI() {
|
||||
return getContextPath() + "/setupWizard/";
|
||||
}
|
||||
}, response);
|
||||
return;
|
||||
}
|
||||
// fall through to handling the request normally
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -194,6 +194,7 @@ import jenkins.ExtensionRefreshException;
|
||||
import jenkins.InitReactorRunner;
|
||||
import jenkins.install.InstallState;
|
||||
import jenkins.install.InstallUtil;
|
||||
import jenkins.install.SetupWizard;
|
||||
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
|
||||
import jenkins.security.ConfidentialKey;
|
||||
import jenkins.security.ConfidentialStore;
|
||||
@@ -333,7 +334,13 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
/**
|
||||
* The Jenkins instance startup type i.e. NEW, UPGRADE etc
|
||||
*/
|
||||
private InstallState installState;
|
||||
private transient InstallState installState = InstallState.NEW;
|
||||
|
||||
/**
|
||||
* If we're in the process of an initial setup,
|
||||
* this will be set
|
||||
*/
|
||||
private transient SetupWizard setupWizard;
|
||||
|
||||
/**
|
||||
* Number of executors of the master node.
|
||||
@@ -759,7 +766,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
// As Jenkins is starting, grant this process full control
|
||||
// As Jenkins is starting, grant this process full control
|
||||
ACL.impersonate(ACL.SYSTEM);
|
||||
try {
|
||||
this.root = root;
|
||||
@@ -773,7 +780,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
if (installState == InstallState.RESTART || installState == InstallState.DOWNGRADE) {
|
||||
InstallUtil.saveLastExecVersion();
|
||||
}
|
||||
|
||||
|
||||
if (!new File(root,"jobs").exists()) {
|
||||
// if this is a fresh install, use more modern default layout that's consistent with agents
|
||||
workspaceDir = "${JENKINS_HOME}/workspace/${ITEM_FULLNAME}";
|
||||
@@ -834,6 +841,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
if(KILL_AFTER_LOAD)
|
||||
System.exit(0);
|
||||
|
||||
if(!installState.isSetupComplete()) {
|
||||
// Start immediately with the setup wizard for new installs
|
||||
setupWizard = new SetupWizard(this);
|
||||
}
|
||||
|
||||
launchTcpSlaveAgentListener();
|
||||
|
||||
if (UDPBroadcastThread.PORT != -1) {
|
||||
@@ -913,6 +925,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
* Get the Jenkins {@link jenkins.install.InstallState install state}.
|
||||
* @return The Jenkins {@link jenkins.install.InstallState install state}.
|
||||
*/
|
||||
@Nonnull
|
||||
@Restricted(NoExternalUse.class)
|
||||
public InstallState getInstallState() {
|
||||
return installState;
|
||||
@@ -1437,10 +1450,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
*/
|
||||
@Exported(name="jobs")
|
||||
public List<TopLevelItem> getItems() {
|
||||
if (authorizationStrategy instanceof AuthorizationStrategy.Unsecured ||
|
||||
authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy) {
|
||||
return new ArrayList(items.values());
|
||||
}
|
||||
if (authorizationStrategy instanceof AuthorizationStrategy.Unsecured ||
|
||||
authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy) {
|
||||
return new ArrayList(items.values());
|
||||
}
|
||||
|
||||
List<TopLevelItem> viewableItems = new ArrayList<TopLevelItem>();
|
||||
for (TopLevelItem item : items.values()) {
|
||||
@@ -1815,11 +1828,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
}
|
||||
|
||||
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
|
||||
return nodeProperties;
|
||||
return nodeProperties;
|
||||
}
|
||||
|
||||
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getGlobalNodeProperties() {
|
||||
return globalNodeProperties;
|
||||
return globalNodeProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2428,7 +2441,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
*/
|
||||
@Override public TopLevelItem getItem(String name) throws AccessDeniedException {
|
||||
if (name==null) return null;
|
||||
TopLevelItem item = items.get(name);
|
||||
TopLevelItem item = items.get(name);
|
||||
if (item==null)
|
||||
return null;
|
||||
if (!item.hasPermission(Item.READ)) {
|
||||
@@ -3079,10 +3092,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
}
|
||||
|
||||
public HttpResponse doToggleCollapse() throws ServletException, IOException {
|
||||
final StaplerRequest request = Stapler.getCurrentRequest();
|
||||
final String paneId = request.getParameter("paneId");
|
||||
final StaplerRequest request = Stapler.getCurrentRequest();
|
||||
final String paneId = request.getParameter("paneId");
|
||||
|
||||
PaneStatusProperties.forCurrentUser().toggleCollapsed(paneId);
|
||||
PaneStatusProperties.forCurrentUser().toggleCollapsed(paneId);
|
||||
|
||||
return HttpResponses.forwardToPreviousPage();
|
||||
}
|
||||
@@ -3113,9 +3126,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
LOGGER.info("Failed to get thread dump for node " + c.getName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (toComputer() == null) {
|
||||
future.put("master", RemotingDiagnostics.getThreadDumpAsync(FilePath.localChannel));
|
||||
}
|
||||
if (toComputer() == null) {
|
||||
future.put("master", RemotingDiagnostics.getThreadDumpAsync(FilePath.localChannel));
|
||||
}
|
||||
|
||||
// if the result isn't available in 5 sec, ignore that.
|
||||
// this is a precaution against hang nodes
|
||||
@@ -3891,6 +3904,20 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
public List<ManagementLink> getManagementLinks() {
|
||||
return ManagementLink.all();
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, a currently active setup wizard - e.g. installation
|
||||
*/
|
||||
public SetupWizard getSetupWizard() {
|
||||
return setupWizard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setup wizard
|
||||
*/
|
||||
public void setSetupWizard(SetupWizard setupWizard) {
|
||||
this.setupWizard = setupWizard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the current user to <tt>/me</tt> URL.
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?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">
|
||||
<f:entry field="allowAnonymousRead">
|
||||
<f:checkbox default="true" title="${%Allow anonymous read access}"/>
|
||||
</f:entry>
|
||||
</j:jelly>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
If checked, this will allow users who are not authenticated to access Jenkins in a read-only mode.
|
||||
</div>
|
||||
+38
-59
@@ -25,64 +25,43 @@ THE SOFTWARE.
|
||||
<!-- tag file sed by both signup.jelly and addUser.jelly -->
|
||||
<?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 norefresh="true" title="${%Sign up}">
|
||||
<l:header>
|
||||
<style>
|
||||
<!-- match width with captcha image -->
|
||||
INPUT {
|
||||
width:200px;
|
||||
}
|
||||
</style>
|
||||
</l:header>
|
||||
<l:hasPermission permission="${app.READ}" it="${host}">
|
||||
<st:include page="sidepanel.jelly" it="${host}" />
|
||||
</l:hasPermission>
|
||||
<l:main-panel>
|
||||
<h1>${title}</h1>
|
||||
<div style="margin: 2em;">
|
||||
<j:if test="${data.errorMessage!=null}">
|
||||
<div class="error" style="margin-bottom:1em">
|
||||
${data.errorMessage}
|
||||
</div>
|
||||
</j:if>
|
||||
<form action="${rootURL}/securityRealm/${action}" method="post" style="text-size:smaller">
|
||||
<table>
|
||||
<tr>
|
||||
<td>${%Username}:</td>
|
||||
<td><input type="text" name="username" id="username" value="${data.username}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%Password}:</td>
|
||||
<td><input type="password" name="password1" value="${data.password1}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%Confirm password}:</td>
|
||||
<td><input type="password" name="password2" value="${data.password2}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%Full name}:</td>
|
||||
<td><input type="text" name="fullname" value="${data.fullname}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%E-mail address}:</td>
|
||||
<td><input type="text" name="email" value="${data.email}" /></td>
|
||||
</tr>
|
||||
<j:if test="${captcha}">
|
||||
<tr>
|
||||
<td>${%Enter text as shown}:</td>
|
||||
<td>
|
||||
<input type="text" name="captcha" autocomplete="off" /><br />
|
||||
<img src="${rootURL}/securityRealm/captcha" alt="[captcha]"/>
|
||||
</td>
|
||||
</tr>
|
||||
</j:if>
|
||||
</table>
|
||||
<f:submit value="${title}" />
|
||||
<script>
|
||||
$('username').focus();
|
||||
</script>
|
||||
</form>
|
||||
<h1>${title}</h1>
|
||||
<div style="margin: 2em;">
|
||||
<j:if test="${data.errorMessage!=null}">
|
||||
<div class="error" style="margin-bottom:1em">
|
||||
${data.errorMessage}
|
||||
</div>
|
||||
</l:main-panel>
|
||||
</l:layout>
|
||||
</j:if>
|
||||
<table>
|
||||
<tr>
|
||||
<td>${%Username}:</td>
|
||||
<td><input type="text" name="username" id="username" value="${data.username}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%Password}:</td>
|
||||
<td><input type="password" name="password1" value="${data.password1}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%Confirm password}:</td>
|
||||
<td><input type="password" name="password2" value="${data.password2}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%Full name}:</td>
|
||||
<td><input type="text" name="fullname" value="${data.fullname}" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${%E-mail address}:</td>
|
||||
<td><input type="text" name="email" value="${data.email}" /></td>
|
||||
</tr>
|
||||
<j:if test="${captcha}">
|
||||
<tr>
|
||||
<td>${%Enter text as shown}:</td>
|
||||
<td>
|
||||
<input type="text" name="captcha" autocomplete="off" /><br />
|
||||
<img src="${rootURL}/securityRealm/captcha" alt="[captcha]"/>
|
||||
</td>
|
||||
</tr>
|
||||
</j:if>
|
||||
</table>
|
||||
</div>
|
||||
</j:jelly>
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2004-2016, Sun Microsystems, Inc., CloudBees, Inc., Kohsuke Kawaguchi, Seiji Sogabe
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- tag file used by both signup.jelly and addUser.jelly -->
|
||||
<?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 norefresh="true" title="${title}">
|
||||
<l:header>
|
||||
<style>
|
||||
<!-- match width with captcha image -->
|
||||
INPUT {
|
||||
width:200px;
|
||||
}
|
||||
</style>
|
||||
</l:header>
|
||||
<l:hasPermission permission="${app.READ}" it="${host}">
|
||||
<st:include page="sidepanel.jelly" it="${host}" />
|
||||
</l:hasPermission>
|
||||
<l:main-panel>
|
||||
<form action="${rootURL}/securityRealm/${action}" method="post" style="text-size:smaller">
|
||||
<local:_entryForm title="${title}" action="${action}" captcha="${captcha}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
<f:submit value="${title}" />
|
||||
<script>
|
||||
$('username').focus();
|
||||
</script>
|
||||
</form>
|
||||
</l:main-panel>
|
||||
</l:layout>
|
||||
</j:jelly>
|
||||
@@ -27,5 +27,5 @@ 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">
|
||||
<local:_entryForm host="${it}" title="${%Create User}" action="createAccountByAdmin" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
<local:_entryFormPage host="${it}" title="${%Create User}" action="createAccountByAdmin" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
</j:jelly>
|
||||
@@ -27,5 +27,5 @@ 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">
|
||||
<local:_entryForm host="${it}" title="${%Create First Admin User}" action="createFirstAccount" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
<local:_entryFormPage host="${it}" title="${%Create First Admin User}" action="createFirstAccount" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
</j:jelly>
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
This is used to create the first user.
|
||||
-->
|
||||
<?jelly escape-by-default='true'?>
|
||||
<l:html norefresh="true" title="${it.displayName}" 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:main-panel>
|
||||
<style type="text/css">
|
||||
@import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css);
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,300,500,900,700);
|
||||
|
||||
#main-panel {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
tr td {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
body {
|
||||
padding: 20px 20px 20px 100px;
|
||||
font-family: 'roboto';
|
||||
}
|
||||
form > div {
|
||||
margin: 0 !important;
|
||||
}
|
||||
h1 {
|
||||
font-family: 'roboto', sans-serif;
|
||||
font-size: 48px;
|
||||
margin-top: 30px;
|
||||
font-weight: 500;
|
||||
}
|
||||
h1 img {
|
||||
position: absolute;
|
||||
right: -80px;
|
||||
top: 38px;
|
||||
}
|
||||
tr td, input {
|
||||
line-height: 25px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
input[type=text], input[type=password] {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2px;
|
||||
padding: 1px 8px;
|
||||
}
|
||||
</style>
|
||||
<form action="${rootURL}/securityRealm/${action}" method="post">
|
||||
<local:_entryForm host="${app.securityRealm}" title="${%Create First Admin User}" action="createFirstAccount" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
<script>
|
||||
$('username').focus();
|
||||
</script>
|
||||
</form>
|
||||
</l:main-panel>
|
||||
</l:html>
|
||||
@@ -27,5 +27,5 @@ 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">
|
||||
<local:_entryForm host="${app}" title="${%Sign up}" action="createAccount" captcha="${it.isEnableCaptcha()}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
<local:_entryFormPage host="${app}" title="${%Sign up}" action="createAccount" captcha="${it.isEnableCaptcha()}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
</j:jelly>
|
||||
+1
-1
@@ -27,5 +27,5 @@ 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">
|
||||
<local:_entryForm host="${app}" title="${%Sign up}" action="createAccountWithFederatedIdentity" captcha="${true}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
<local:_entryFormPage host="${app}" title="${%Sign up}" action="createAccountWithFederatedIdentity" captcha="${true}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
|
||||
</j:jelly>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?jelly escape-by-default='true'?>
|
||||
<l:html norefresh="true" title="${app.instance.displayName}" 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:main-panel>
|
||||
<form action="${app.instance.securityRealm.authenticationGatewayUrl}" method="POST">
|
||||
<div class="plugin-setup-wizard bootstrap-3">
|
||||
<div class="modal fade in" style="display: block;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">${%Security Token}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<i class="water-mark icon-service"></i>
|
||||
<div class="jumbotron welcome-panel offline">
|
||||
|
||||
<h1>${%Security Token}</h1>
|
||||
<p>${%As an extra measure of security, please enter the setup security token to proceed.}</p>
|
||||
<p>${%It can be found in the logs for this Jenkins instance.}</p>
|
||||
<j:if test="${error}">
|
||||
<div class="alert alert-danger">
|
||||
<strong>${%ERROR:} </strong>
|
||||
${%There is a problem with your security token, please check the server logs for the correct token}
|
||||
</div>
|
||||
</j:if>
|
||||
<div class="form-group ${error ? 'has-error' : ''}">
|
||||
<label class="control-label" for="security-token">${%Security token}</label>
|
||||
<input name="j_username" value="${j.setupWizard.initialSetupAdminUserName}" type="hidden"/>
|
||||
<input id="security-token" class="form-control" name="j_password"/>
|
||||
<link rel="stylesheet" href="${j.installWizardPath}.css" type="text/css" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" class="btn btn-primary set-security-key" value="${%Continue}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</l:main-panel>
|
||||
</l:html>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?jelly escape-by-default='true'?>
|
||||
<l:html norefresh="true" title="${it.displayName}" 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:main-panel>
|
||||
<div class="plugin-setup-wizard-container"></div>
|
||||
<script src="${resURL}/${j.installWizardPath}.js" type="text/javascript"/>
|
||||
<link rel="stylesheet" href="${resURL}/${j.installWizardPath}.css" type="text/css" />
|
||||
</l:main-panel>
|
||||
</l:html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?jelly escape-by-default='true'?>
|
||||
<l:html norefresh="true" title="${it.displayName}" 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:main-panel>
|
||||
<h1>${%HTTP Proxy Configuration}</h1>
|
||||
<f:form method="post" action="/pluginManager/proxyConfigure" name="proxyConfigure">
|
||||
<j:scope>
|
||||
<j:set var="instance" value="${app.proxy}"/>
|
||||
<j:set var="descriptor" value="${app.pluginManager.proxyDescriptor}"/>
|
||||
<st:include from="${descriptor}" page="${descriptor.configPage}" />
|
||||
</j:scope>
|
||||
</f:form>
|
||||
</l:main-panel>
|
||||
</l:html>
|
||||
@@ -1,16 +1,16 @@
|
||||
installWizard_welcomePanel_title=Getting Started
|
||||
installWizard_welcomePanel_banner=Initial Jenkins Setup
|
||||
installWizard_welcomePanel_message=Choose plugins. This will add features to your Jenkins environment.
|
||||
installWizard_welcomePanel_recommendedActionTitle=Start with recommended plugins
|
||||
installWizard_welcomePanel_recommendedActionDetails=Install the set of plugins the community finds most useful
|
||||
installWizard_welcomePanel_customizeActionTitle=Customize your plugins
|
||||
installWizard_welcomePanel_customizeActionDetails=Select from a community approved list of plugins
|
||||
installWizard_welcomePanel_banner=Customize Jenkins
|
||||
installWizard_welcomePanel_message=Plugins extend Jenkins with additional features to support many different needs.
|
||||
installWizard_welcomePanel_recommendedActionTitle=Install suggested plugins
|
||||
installWizard_welcomePanel_recommendedActionDetails=Install plugins the Jenkins community finds most useful.
|
||||
installWizard_welcomePanel_customizeActionTitle=Select plugins to install
|
||||
installWizard_welcomePanel_customizeActionDetails=Select and install plugins most suitable for your needs.
|
||||
installWizard_offline_title=Offline
|
||||
installWizard_offline_message=This Jenkins instance appears to be offline. \
|
||||
<p style="font-size:18px; margin-top: 6%"> \
|
||||
For information about installing Jenkins without an internet connection, see the \
|
||||
<a href="https://wiki.jenkins-ci.org/display/JENKINS/Offline+Jenkins+Installation" target="_blank">Offline Jenkins Installation Documentation</a>. <br/><br/> \
|
||||
If you need to configure a proxy, you may close this wizard and use the Plugin Manager. \
|
||||
You may choose to continue by configuring a proxy or skipping plugin installation. \
|
||||
</p>
|
||||
installWizard_error_header=An error occurred
|
||||
installWizard_error_message=An error occurred during installation:
|
||||
@@ -28,12 +28,23 @@ installWizard_installing_title=Installing...
|
||||
installWizard_installing_detailsLink=Details...
|
||||
installWizard_installComplete_title=Installed
|
||||
installWizard_installComplete_banner=Jenkins is ready!
|
||||
installWizard_installComplete_message=Your plugin installations are complete.
|
||||
installWizard_installComplete_finishButtonLabel=Get Started
|
||||
installWizard_pluginsInstalled_message=Your plugin installations are complete.
|
||||
installWizard_installComplete_message=Your Jenkins setup is complete.
|
||||
installWizard_installComplete_finishButtonLabel=Start using Jenkins
|
||||
installWizard_installComplete_restartRequiredMessage=Some plugins require Jenkins to be restarted.
|
||||
installWizard_installComplete_restartLabel=Restart
|
||||
installWizard_installIncomplete_title=Resume Installation
|
||||
installWizard_installIncomplete_banner=Resume Installation
|
||||
installWizard_installIncomplete_message=Jenknins was restarted during installation and some plugins didn't seem to get installed.
|
||||
installWizard_installIncomplete_message=Jenkins was restarted during installation and some plugins didn't seem to get installed.
|
||||
installWizard_installIncomplete_resumeInstallationButtonLabel=Resume
|
||||
installWizard_saveFirstUser=Save and Finish
|
||||
installWizard_skipFirstUser=Skip
|
||||
installWizard_firstUserSkippedMessage=<div class="alert alert-warning fade in">\
|
||||
You've skipped creating an admin user. To log in, use the username: 'admin' and \
|
||||
the security token you used to access the setup wizard.\
|
||||
</div>
|
||||
installWizard_addFirstUser_title=Create an Admin User
|
||||
installWizard_configureProxy_label=Configure Proxy
|
||||
installWizard_configureProxy_save=Save and Continue
|
||||
installWizard_skipPluginInstallations=Skip Plugin Installations
|
||||
installWizard_installIncomplete_dependenciesLabel=Dependencies
|
||||
@@ -24,6 +24,10 @@ 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">
|
||||
<j:if test="${it.setupWizard != null}">
|
||||
<st:include it="${it.setupWizard}" page="authenticate-security-token"/>
|
||||
</j:if>
|
||||
<j:if test="${it.setupWizard == null}">
|
||||
<l:layout norefresh="true">
|
||||
<l:hasPermission permission="${app.READ}">
|
||||
<st:include page="sidepanel.jelly" />
|
||||
@@ -70,4 +74,5 @@ THE SOFTWARE.
|
||||
</div>
|
||||
</l:main-panel>
|
||||
</l:layout>
|
||||
</j:if>
|
||||
</j:jelly>
|
||||
@@ -27,6 +27,10 @@ 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">
|
||||
<j:choose>
|
||||
<j:new var="h" className="hudson.Functions" />
|
||||
<j:when test="${app.setupWizard != null}">
|
||||
<j:set var="error" value="true"/>
|
||||
<st:include it="${app.setupWizard}" page="authenticate-security-token"/>
|
||||
</j:when>
|
||||
<j:when test="${app.isUseSecurity() and h.isAnonymous()}">
|
||||
<!--
|
||||
The only time the error message makes sense is when Jenkins is protected and the user failed to authenticate.
|
||||
|
||||
@@ -44,6 +44,9 @@ THE SOFTWARE.
|
||||
<st:attribute name="onclick" />
|
||||
<st:attribute name="class" />
|
||||
<st:attribute name="negative" />
|
||||
<st:attribute name="readonly">
|
||||
If set to true, this will take precedence over the onclick attribute and prevent the state of the checkbox from being changed.
|
||||
</st:attribute>
|
||||
<st:attribute name="field">
|
||||
Used for databinding. TBD.
|
||||
</st:attribute>
|
||||
@@ -63,7 +66,7 @@ THE SOFTWARE.
|
||||
name="${name}"
|
||||
value="${attrs.value}"
|
||||
title="${attrs.tooltip}"
|
||||
onclick="${attrs.onclick}" id="${attrs.id}" class="${attrs.class} ${attrs.negative!=null ? 'negative' : null} ${attrs.checkUrl!=null?'validated':''}"
|
||||
onclick="${attrs.readonly=='true' ? 'return false;' : attrs.onclick}" id="${attrs.id}" class="${attrs.class} ${attrs.negative!=null ? 'negative' : null} ${attrs.checkUrl!=null?'validated':''}"
|
||||
checkUrl="${attrs.checkUrl}" checkDependsOn="${attrs.checkDependsOn}" json="${attrs.json}"
|
||||
checked="${value ? 'true' : null}"/>
|
||||
<j:if test="${attrs.title!=null}">
|
||||
|
||||
@@ -44,7 +44,11 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {
|
||||
|
||||
function hasChanged(selectEl, originalValue) {
|
||||
// seems like a race condition allows this to fire before the 'selectEl' is defined. If that happens, exit..
|
||||
<<<<<<< HEAD
|
||||
if(!selectEl || !selectEl.options || !selectEl.options[0])
|
||||
=======
|
||||
if(!selectEl || !selectEl.options || !selectEl[0])
|
||||
>>>>>>> 219481a2926a6a6e2d86753f250449ba73f198ba
|
||||
return false;
|
||||
var firstValue = selectEl.options[0].value;
|
||||
var selectedValue = selectEl.value;
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<!--
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2004-2016, Sun Microsystems, Inc., Kohsuke Kawaguchi,
|
||||
Daniel Dyer, Seiji Sogabe, Tom Huybrechts, Manufacture Francaise des Pneumatiques
|
||||
Michelin, Romain Seguy
|
||||
|
||||
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:i="jelly:fmt" xmlns:x="jelly:xml">
|
||||
<st:documentation>
|
||||
Outer-most tag for a normal (non-AJAX) HTML rendering.
|
||||
This is used with nested <header>, <side-panel>, and <main-panel>
|
||||
to form Jenkins's basic HTML layout.
|
||||
|
||||
<st:attribute name="title" use="required">
|
||||
Title of the HTML page. Rendered into <title> tag.
|
||||
</st:attribute>
|
||||
<st:attribute name="norefresh">
|
||||
If non-null and not "false", auto refresh is disabled on this page.
|
||||
This is necessary for pages that include forms.
|
||||
</st:attribute>
|
||||
<st:attribute name="css" deprecated="true">
|
||||
specify path that starts from "/" for loading additional CSS stylesheet.
|
||||
path is interprted as relative to the context root. e.g.,
|
||||
|
||||
{noformat}<l:layout css="/plugin/mysuperplugin/css/myneatstyle.css">{noformat}
|
||||
|
||||
This was originally added to allow plugins to load their stylesheets, but
|
||||
*the use of thie attribute is discouraged now.*
|
||||
plugins should now do so by inserting <style> elements and/or <script> elements
|
||||
in <l:header/> tag.
|
||||
</st:attribute>
|
||||
<st:attribute name="permission">
|
||||
If given, this page is only made available to users that has the specified permission.
|
||||
(The permission will be checked against the "it" object.)
|
||||
</st:attribute>
|
||||
</st:documentation>
|
||||
<st:setHeader name="Expires" value="0" />
|
||||
<st:setHeader name="Cache-Control" value="no-cache,no-store,must-revalidate" />
|
||||
<st:setHeader name="X-Hudson-Theme" value="default" />
|
||||
<st:contentType value="text/html;charset=UTF-8" />
|
||||
|
||||
<j:new var="h" className="hudson.Functions" /><!-- instead of JSP functions -->
|
||||
${h.initPageVariables(context)}
|
||||
<!--
|
||||
depending on what tags are used, we can later end up discovering that we needed a session,
|
||||
but it's too late because the headers are already committed. so ensure we always have a session.
|
||||
this also allows us to configure HttpSessionContextIntegrationFilter not to create sessions,
|
||||
which I suspect can end up creating sessions for wrong resource types (such as static resources.)
|
||||
-->
|
||||
<j:set var="isMSIE" value="${userAgent.contains('MSIE')}"/>
|
||||
<j:set var="_" value="${request.getSession()}"/>
|
||||
<j:set var="_" value="${h.configureAutoRefresh(request, response, attrs.norefresh!=null and !attrs.norefresh.equals(false))}"/>
|
||||
<j:set var="extensionsAvailable" value="${h.extensionsAvailable}"/>
|
||||
<j:if test="${request.servletPath=='/' || request.servletPath==''}">
|
||||
${h.advertiseHeaders(response)}
|
||||
<j:if test="${extensionsAvailable}">
|
||||
<j:forEach var="pd" items="${h.pageDecorators}">
|
||||
<st:include it="${pd}" page="httpHeaders.jelly" optional="true"/>
|
||||
</j:forEach>
|
||||
</j:if>
|
||||
</j:if>
|
||||
<x:doctype name="html" />
|
||||
<html>
|
||||
<head data-rooturl="${rootURL}" data-resurl="${resURL}" resURL="${resURL}">
|
||||
${h.checkPermission(it,permission)}
|
||||
|
||||
<j:if test="${isMSIE}">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
|
||||
</j:if>
|
||||
|
||||
<title>${h.appendIfNotNull(title, ' [Jenkins]', 'Jenkins')}</title>
|
||||
<link rel="stylesheet" href="${resURL}/css/style.css" type="text/css" />
|
||||
<link rel="stylesheet" href="${resURL}/css/color.css" type="text/css" />
|
||||
<link rel="stylesheet" href="${resURL}/css/responsive-grid.css" type="text/css" />
|
||||
<j:if test="${attrs.css!=null}">
|
||||
<link rel="stylesheet" href="${resURL}${attrs.css}" type="text/css" />
|
||||
</j:if>
|
||||
<link rel="shortcut icon" href="${resURL}/favicon.ico" type="image/vnd.microsoft.icon" />
|
||||
<link rel="mask-icon" href="${rootURL}/images/mask-icon.svg" color="black" />
|
||||
|
||||
<!-- are we running as an unit test? -->
|
||||
<script>var isRunAsTest=${h.isUnitTest}; var rootURL="${rootURL}"; var resURL="${resURL}";</script>
|
||||
|
||||
<script src="${resURL}/scripts/prototype.js" type="text/javascript"/>
|
||||
<script src="${resURL}/scripts/behavior.js" type="text/javascript"/>
|
||||
|
||||
<!-- we include our own prototype.js, so don't let stapler pull in another. -->
|
||||
<st:adjunct assumes="org.kohsuke.stapler.framework.prototype.prototype"
|
||||
includes="org.kohsuke.stapler.bind"/>
|
||||
|
||||
<!-- To use the debug version of YUI, set the system property 'debug.YUI' to true -->
|
||||
<j:set var="yuiSuffix" value="${h.yuiSuffix}" />
|
||||
<l:yui module="yahoo" />
|
||||
<l:yui module="dom" />
|
||||
<l:yui module="event" />
|
||||
<j:if test="${h.yuiSuffix=='debug'}">
|
||||
<l:yui module="logger" />
|
||||
</j:if>
|
||||
<l:yui module="animation" />
|
||||
<l:yui module="dragdrop" />
|
||||
<l:yui module="container" />
|
||||
<l:yui module="connection" />
|
||||
<l:yui module="datasource" />
|
||||
<l:yui module="autocomplete" />
|
||||
<l:yui module="menu" />
|
||||
<l:yui module="element" />
|
||||
<l:yui module="button" />
|
||||
<l:yui module="storage" />
|
||||
<!--l:yui module="editor" suffix="-beta" /-->
|
||||
|
||||
<script src="${resURL}/scripts/hudson-behavior.js" type="text/javascript"></script>
|
||||
<script src="${resURL}/scripts/sortable.js" type="text/javascript"/>
|
||||
|
||||
<j:if test="${extensionsAvailable}">
|
||||
<script>
|
||||
crumb.init("${h.getCrumbRequestField()}", "${h.getCrumb(request)}");
|
||||
</script>
|
||||
</j:if>
|
||||
|
||||
<link rel="stylesheet" href="${resURL}/scripts/yui/container/assets/container.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="${resURL}/scripts/yui/assets/skins/sam/skin.css" type="text/css" />
|
||||
<link rel="stylesheet" href="${resURL}/scripts/yui/container/assets/skins/sam/container.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="${resURL}/scripts/yui/button/assets/skins/sam/button.css" type="text/css" />
|
||||
<link rel="stylesheet" href="${resURL}/scripts/yui/menu/assets/skins/sam/menu.css" type="text/css" />
|
||||
<!--link rel="stylesheet" href="${resURL}/scripts/yui/editor/assets/skins/sam/editor.css" type="text/css" /-->
|
||||
|
||||
<l:hasPermission permission="${app.READ}">
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="${rootURL}/opensearch.xml" title="Jenkins" />
|
||||
</l:hasPermission>
|
||||
<meta name="ROBOTS" content="INDEX,NOFOLLOW" />
|
||||
<j:set var="mode" value="header" />
|
||||
<d:invokeBody />
|
||||
<j:if test="${extensionsAvailable}">
|
||||
<j:forEach var="pd" items="${h.pageDecorators}">
|
||||
<st:include it="${pd}" page="header.jelly" optional="true" />
|
||||
</j:forEach>
|
||||
</j:if>
|
||||
|
||||
<j:invokeStatic var="j" className="jenkins.model.Jenkins" method="getActiveInstance" />
|
||||
<j:set var="installState" value="${j.installState.name()}" />
|
||||
<j:if test="${installState == 'NEW' || installState == 'INITIAL_PLUGINS_INSTALLING'}">
|
||||
<script src="${resURL}/${j.installWizardPath}.js" type="text/javascript"/>
|
||||
<link rel="stylesheet" href="${resURL}/${j.installWizardPath}.css" type="text/css" />
|
||||
</j:if>
|
||||
|
||||
<j:if test="${isMSIE}">
|
||||
<script src="${resURL}/scripts/msie.js" type="text/javascript"/>
|
||||
</j:if>
|
||||
|
||||
</head>
|
||||
<body id="jenkins" class="yui-skin-sam jenkins-${h.version}" data-version="jenkins-${h.version}" data-model-type="${it.class.name}">
|
||||
<div id="main-panel" style="margin-left: 0;">
|
||||
<j:set var="mode" value="main-panel" />
|
||||
<d:invokeBody />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</j:jelly>
|
||||
@@ -166,10 +166,6 @@ ${h.initPageVariables(context)}
|
||||
|
||||
<j:invokeStatic var="j" className="jenkins.model.Jenkins" method="getActiveInstance" />
|
||||
<j:set var="installState" value="${j.installState.name()}" />
|
||||
<j:if test="${installState == 'NEW' || installState == 'INITIAL_PLUGINS_INSTALLING'}">
|
||||
<script src="${resURL}/${j.installWizardPath}.js" type="text/javascript"/>
|
||||
<link rel="stylesheet" href="${resURL}/${j.installWizardPath}.css" type="text/css" />
|
||||
</j:if>
|
||||
|
||||
<j:if test="${isMSIE}">
|
||||
<script src="${resURL}/scripts/msie.js" type="text/javascript"/>
|
||||
|
||||
@@ -58,7 +58,7 @@ THE SOFTWARE.
|
||||
<connection>scm:git:git://github.com/jenkinsci/jenkins.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/jenkinsci/jenkins.git</developerConnection>
|
||||
<url>https://github.com/jenkinsci/jenkins</url>
|
||||
<tag>HEAD</tag>
|
||||
<tag>jenkins-1.652</tag>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
|
||||
@@ -65,6 +65,12 @@ THE SOFTWARE.
|
||||
<artifactId>jenkins-test-harness</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>jenkins-war</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
import org.jvnet.hudson.test.JenkinsRule.WebClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -47,11 +48,15 @@ public class ManagementLinkTest {
|
||||
*/
|
||||
@Test
|
||||
public void links() throws Exception {
|
||||
HtmlPage page = j.createWebClient().goTo("manage");
|
||||
List<?> anchors = DomNodeUtil.selectNodes(page, "id('management-links')//*[@class='link']/a[not(@onclick)]");
|
||||
assertTrue(anchors.size()>=8);
|
||||
for(HtmlAnchor e : (List<HtmlAnchor>) anchors) {
|
||||
e.click();
|
||||
WebClient wc = j.createWebClient();
|
||||
|
||||
for (int i=0; ; i++) {
|
||||
HtmlPage page = wc.goTo("manage");
|
||||
List<?> anchors = DomNodeUtil.selectNodes(page, "id('management-links')//*[@class='link']/a[not(@onclick)]");
|
||||
assertTrue(anchors.size()>=8);
|
||||
if (i==anchors.size()) return; // done
|
||||
|
||||
((HtmlAnchor)anchors.get(i)).click();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,8 @@ public class UpdateCenterPluginInstallTest {
|
||||
String correlationId = data.getString("correlationId");
|
||||
JSONObject installStatus = jenkinsRule.getJSON("updateCenter/installStatus?correlationId=" + correlationId).getJSONObject();
|
||||
Assert.assertEquals("ok", json.get("status"));
|
||||
JSONArray states = installStatus.getJSONArray("data");
|
||||
JSONObject status = installStatus.getJSONObject("data");
|
||||
JSONArray states = status.getJSONArray("jobs");
|
||||
Assert.assertEquals(2, states.size());
|
||||
|
||||
JSONObject pluginInstallState = states.getJSONObject(0);
|
||||
|
||||
@@ -28,6 +28,7 @@ import hudson.FilePath;
|
||||
import hudson.remoting.VirtualChannel;
|
||||
import hudson.scm.NullSCM;
|
||||
import hudson.slaves.DumbSlave;
|
||||
import hudson.slaves.WorkspaceList;
|
||||
import hudson.util.StreamTaskListener;
|
||||
|
||||
import java.io.File;
|
||||
@@ -180,6 +181,19 @@ public class WorkspaceCleanupThreadTest {
|
||||
assertFalse(ws.exists());
|
||||
}
|
||||
|
||||
@Issue("JENKINS-27152")
|
||||
@Test
|
||||
public void deleteTemporaryDirectory() throws Exception {
|
||||
FreeStyleProject p = r.createFreeStyleProject();
|
||||
FilePath ws = createOldWorkspaceOn(r.jenkins, p);
|
||||
FilePath tmp = WorkspaceList.tempDir(ws);
|
||||
tmp.child("stuff").write("content", null);
|
||||
createOldWorkspaceOn(r.createOnlineSlave(), p);
|
||||
performCleanup();
|
||||
assertFalse(ws.exists());
|
||||
assertFalse("temporary directory should be cleaned up as well", tmp.exists());
|
||||
}
|
||||
|
||||
private FilePath createOldWorkspaceOn(Node slave, FreeStyleProject p) throws Exception {
|
||||
p.setAssignedNode(slave);
|
||||
FreeStyleBuild b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
|
||||
|
||||
@@ -149,7 +149,7 @@ exports.incompleteInstallStatus = function(handler, correlationId) {
|
||||
* Call this to complete the installation without installing anything
|
||||
*/
|
||||
exports.completeInstall = function(handler) {
|
||||
jenkins.get('/updateCenter/completeInstall', function() {
|
||||
jenkins.get('/setupWizard/completeInstall', function() {
|
||||
handler.call({ isError: false });
|
||||
}, {
|
||||
timeout: pluginManagerErrorTimeoutMillis,
|
||||
|
||||
@@ -8,17 +8,27 @@
|
||||
// away from these.
|
||||
//
|
||||
exports.recommendedPlugins = [
|
||||
"ant",
|
||||
"antisamy-markup-formatter",
|
||||
"credentials",
|
||||
"build-monitor-plugin",
|
||||
"build-timeout",
|
||||
"cloudbees-folder",
|
||||
"credentials-binding",
|
||||
"email-ext",
|
||||
"git",
|
||||
"github-branch-source",
|
||||
"junit",
|
||||
"gradle",
|
||||
"ldap",
|
||||
"mailer",
|
||||
"matrix-auth",
|
||||
"script-security",
|
||||
// "matrix-auth",
|
||||
"pam-auth",
|
||||
"pipeline-stage-view",
|
||||
"ssh-slaves",
|
||||
"subversion",
|
||||
"translation",
|
||||
"timestamper",
|
||||
"workflow-aggregator",
|
||||
"workflow-multibranch"
|
||||
"workflow-multibranch",
|
||||
"ws-cleanup"
|
||||
];
|
||||
|
||||
//
|
||||
@@ -27,50 +37,84 @@ exports.recommendedPlugins = [
|
||||
//
|
||||
exports.availablePlugins = [
|
||||
{
|
||||
"category": "General",
|
||||
"description": "(a collection of things I cannot think of a better name for)",
|
||||
"category":"Organization and Administration",
|
||||
"plugins": [
|
||||
{ "name": "external-monitor-job" },
|
||||
{ "name": "translation" }
|
||||
// { "name": "dashboard-view" },
|
||||
{ "name": "build-monitor-plugin" },
|
||||
{ "name": "cloudbees-folder" },
|
||||
{ "name": "antisamy-markup-formatter" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"category":"Organization and Administration",
|
||||
"category":"Build Features",
|
||||
"description":"Add general purpose features to your jobs",
|
||||
"plugins": [
|
||||
{ "name": "antisamy-markup-formatter" }
|
||||
{ "name": "ansicolor" },
|
||||
// { "name": "build-name-setter" },
|
||||
{ "name": "build-timeout" },
|
||||
{ "name": "config-file-provider" },
|
||||
{ "name": "credentials-binding" },
|
||||
{ "name": "rebuild" },
|
||||
{ "name": "ssh-agent" },
|
||||
// { "name": "throttle-concurrents" },
|
||||
{ "name": "timestamper" }
|
||||
// { "name": "ws-cleanup" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"category":"Build Tools",
|
||||
"plugins": [
|
||||
{ "name": "ant" },
|
||||
{ "name": "maven-plugin" }
|
||||
{ "name": "gradle" },
|
||||
{ "name": "msbuild" },
|
||||
{ "name": "nodejs" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"category":"Build Analysis and Reporting",
|
||||
"plugins": [
|
||||
{ "name": "javadoc" },
|
||||
{ "name": "junit" }
|
||||
// { "name": "checkstyle" },
|
||||
// { "name": "cobertura" },
|
||||
{ "name": "htmlpublisher" },
|
||||
{ "name": "junit" },
|
||||
// { "name": "sonar" },
|
||||
// { "name": "warnings" },
|
||||
{ "name": "xunit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"category":"Pipelines and Continuous Delivery",
|
||||
"plugins": [
|
||||
{ "name": "workflow-aggregator" },
|
||||
{ "name": "workflow-multibranch" },
|
||||
{ "name": "github-branch-source" },
|
||||
{ "name": "workflow-multibranch" }
|
||||
{ "name": "pipeline-stage-view" },
|
||||
{ "name": "build-pipeline-plugin" },
|
||||
// { "name": "conditional-buildstep" },
|
||||
// { "name": "jenkins-multijob-plugin" },
|
||||
{ "name": "parameterized-trigger" },
|
||||
{ "name": "copyartifact" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"category":"SCM",
|
||||
"category":"Source Code Management",
|
||||
"plugins": [
|
||||
{ "name": "bitbucket" },
|
||||
{ "name": "clearcase" },
|
||||
{ "name": "cvs" },
|
||||
{ "name": "subversion" }
|
||||
{ "name": "git" },
|
||||
{ "name": "git-parameter" },
|
||||
{ "name": "github" },
|
||||
{ "name": "gitlab-plugin" },
|
||||
{ "name": "p4" },
|
||||
{ "name": "repo" },
|
||||
{ "name": "subversion" },
|
||||
{ "name": "teamconcert" },
|
||||
{ "name": "tfs" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"category":"Distributed Builds and Containers",
|
||||
"category":"Distributed Builds",
|
||||
"plugins": [
|
||||
{ "name": "matrix-project" },
|
||||
{ "name": "ssh-slaves" },
|
||||
@@ -80,18 +124,22 @@ exports.availablePlugins = [
|
||||
{
|
||||
"category":"User Management and Security",
|
||||
"plugins": [
|
||||
{ "name": "credentials" },
|
||||
{ "name": "ldap" },
|
||||
{ "name": "matrix-auth" },
|
||||
// { "name": "matrix-auth" },
|
||||
{ "name": "pam-auth" },
|
||||
{ "name": "script-security" },
|
||||
{ "name": "ssh-credentials" }
|
||||
{ "name": "ldap" },
|
||||
// { "name": "role-strategy" },
|
||||
{ "name": "active-directory" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"category":"Notifications and Publishing",
|
||||
"plugins": [
|
||||
{ "name": "mailer" }
|
||||
{ "name": "email-ext" },
|
||||
{ "name": "emailext-template" },
|
||||
{ "name": "mailer" },
|
||||
{ "name": "publish-over-ssh" },
|
||||
{ "name": "slack" },
|
||||
{ "name": "ssh" }
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Provides a wrapper to interact with the security configuration
|
||||
*/
|
||||
|
||||
var jenkins = require('../util/jenkins');
|
||||
var jquery = require('jquery-detached');
|
||||
var wh = require('window-handle');
|
||||
|
||||
/**
|
||||
* Calls a stapler post method to save the first user settings
|
||||
*/
|
||||
exports.saveFirstUser = function($form, success, error) {
|
||||
jenkins.staplerPost(
|
||||
'/securityRealm/createFirstAccount',
|
||||
$form,
|
||||
success, {
|
||||
dataType: 'html',
|
||||
error: error
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls a stapler post method to save the first user settings
|
||||
*/
|
||||
exports.saveProxy = function($form, success, error) {
|
||||
jenkins.staplerPost(
|
||||
'/pluginManager/proxyConfigure',
|
||||
$form,
|
||||
success, {
|
||||
dataType: 'html',
|
||||
error: error
|
||||
});
|
||||
};
|
||||
@@ -6,5 +6,10 @@ var pluginSetupWizard = require('./pluginSetupWizardGui');
|
||||
|
||||
// This entry point for the bundle only bootstraps the main module in a browser
|
||||
$(function() {
|
||||
pluginSetupWizard.init();
|
||||
$('.plugin-setup-wizard-container').each(function() {
|
||||
var $container = $(this);
|
||||
if($container.children().length === 0) { // this may get double-initialized
|
||||
pluginSetupWizard.init($container);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -7,9 +7,13 @@ var jquery = require('jquery-detached');
|
||||
var bootstrap = require('bootstrap-detached');
|
||||
var jenkins = require('./util/jenkins');
|
||||
var pluginManager = require('./api/pluginManager');
|
||||
var securityConfig = require('./api/securityConfig');
|
||||
var wh = require('window-handle');
|
||||
|
||||
window.zq = jquery.getJQuery();
|
||||
|
||||
// Setup the dialog, exported
|
||||
var createPluginSetupWizard = function() {
|
||||
var createPluginSetupWizard = function(appendTarget) {
|
||||
// call getJQuery / getBootstrap within the main function so it will work with tests -- if getJQuery etc is called in the main
|
||||
var $ = jquery.getJQuery();
|
||||
var $bs = bootstrap.getBootstrap();
|
||||
@@ -109,6 +113,9 @@ var createPluginSetupWizard = function() {
|
||||
var progressPanel = require('./templates/progressPanel.hbs');
|
||||
var pluginSelectionPanel = require('./templates/pluginSelectionPanel.hbs');
|
||||
var successPanel = require('./templates/successPanel.hbs');
|
||||
var setupCompletePanel = require('./templates/setupCompletePanel.hbs');
|
||||
var proxyConfigPanel = require('./templates/proxyConfigPanel.hbs');
|
||||
var firstUserPanel = require('./templates/firstUserPanel.hbs');
|
||||
var offlinePanel = require('./templates/offlinePanel.hbs');
|
||||
var pluginSetupWizard = require('./templates/pluginSetupWizard.hbs');
|
||||
var incompleteInstallationPanel = require('./templates/incompleteInstallationPanel.hbs');
|
||||
@@ -133,16 +140,16 @@ var createPluginSetupWizard = function() {
|
||||
|
||||
// state variables for plugin data, selected plugins, etc.:
|
||||
var pluginList = pluginManager.plugins();
|
||||
var allPluginNames = pluginManager.pluginNames();
|
||||
var selectedPluginNames = pluginManager.recommendedPluginNames();
|
||||
var allPluginNames = pluginManager.pluginNames();
|
||||
var selectedPluginNames = pluginManager.recommendedPluginNames();
|
||||
var visibleDependencies = {};
|
||||
var categories = [];
|
||||
var availablePlugins = {};
|
||||
var categorizedPlugins = {};
|
||||
var categories = [];
|
||||
var availablePlugins = {};
|
||||
var categorizedPlugins = {};
|
||||
|
||||
// Instantiate the wizard panel
|
||||
var $wizard = $(pluginSetupWizard());
|
||||
$wizard.appendTo('body');
|
||||
$wizard.appendTo(appendTarget);
|
||||
var $container = $wizard.find('.modal-content');
|
||||
var currentPanel;
|
||||
|
||||
@@ -165,10 +172,10 @@ var createPluginSetupWizard = function() {
|
||||
var translations = {};
|
||||
|
||||
var decorations = [
|
||||
function() {
|
||||
function() {
|
||||
// any decorations after DOM replacement go here
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
// call this to set the panel in the app, this performs some additional things & adds common transitions
|
||||
var setPanel = function(panel, data, oncomplete) {
|
||||
@@ -177,7 +184,7 @@ var createPluginSetupWizard = function() {
|
||||
decorations[i]($base);
|
||||
}
|
||||
};
|
||||
var html = panel($.extend({translations: translations}, data));
|
||||
var html = panel($.extend({translations: translations, baseUrl: jenkins.baseUrl}, data));
|
||||
if(panel === currentPanel) { // just replace id-marked elements
|
||||
var $upd = $(html);
|
||||
$upd.find('*[id]').each(function() {
|
||||
@@ -200,6 +207,12 @@ var createPluginSetupWizard = function() {
|
||||
currentPanel = panel;
|
||||
$container.append(html);
|
||||
decorate($container);
|
||||
|
||||
var $modalHeader = $container.find('.modal-header');
|
||||
if($modalHeader.length > 0) {
|
||||
$modalHeader.prepend(
|
||||
'<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>');
|
||||
}
|
||||
|
||||
if(oncomplete) {
|
||||
oncomplete();
|
||||
@@ -255,25 +268,25 @@ var createPluginSetupWizard = function() {
|
||||
// Initializes the set of installing plugins with pending statuses
|
||||
var initInstallingPluginList = function() {
|
||||
installingPlugins = [];
|
||||
installingPlugins.names = [];
|
||||
installingPlugins.names = [];
|
||||
for (var i = 0; i < selectedPluginNames.length; i++) {
|
||||
var pluginName = selectedPluginNames[i];
|
||||
var p = availablePlugins[ pluginName];
|
||||
var pluginName = selectedPluginNames[i];
|
||||
var p = availablePlugins[ pluginName];
|
||||
if (p) {
|
||||
var plug = $.extend({
|
||||
installStatus : 'pending'
|
||||
}, p);
|
||||
installingPlugins.push(plug);
|
||||
installingPlugins[plug.name] = plug;
|
||||
installingPlugins.names.push(pluginName);
|
||||
installingPlugins.names.push(pluginName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// call this to go install the selected set of plugins
|
||||
var installPlugins = function(plugins) {
|
||||
var installPlugins = function(plugins) {
|
||||
pluginManager.installPlugins(plugins, handleGenericError(function() {
|
||||
showInstallProgress();
|
||||
showInstallProgress();
|
||||
}));
|
||||
|
||||
setPanel(progressPanel, { installingPlugins : installingPlugins });
|
||||
@@ -306,13 +319,22 @@ var createPluginSetupWizard = function() {
|
||||
};
|
||||
|
||||
// Define actions
|
||||
var showInstallProgress = function() {
|
||||
initInstallingPluginList();
|
||||
var showInstallProgress = function(state) {
|
||||
if(state) {
|
||||
if(/CREATE_ADMIN_USER/.test(state)) {
|
||||
setupFirstUser();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
initInstallingPluginList();
|
||||
setPanel(progressPanel, { installingPlugins : installingPlugins });
|
||||
|
||||
// call to the installStatus, update progress bar & plugin details; transition on complete
|
||||
var updateStatus = function() {
|
||||
pluginManager.installStatus(handleGenericError(function(jobs) {
|
||||
pluginManager.installStatus(handleGenericError(function(data) {
|
||||
var jobs = data.jobs;
|
||||
|
||||
var i, j;
|
||||
var complete = 0;
|
||||
var total = 0;
|
||||
@@ -402,10 +424,7 @@ var createPluginSetupWizard = function() {
|
||||
else {
|
||||
// mark complete
|
||||
$('.progress-bar').css({width: '100%'});
|
||||
setPanel(successPanel, {
|
||||
installingPlugins : installingPlugins,
|
||||
restartRequired: restartRequired
|
||||
});
|
||||
setupFirstUser();
|
||||
}
|
||||
}));
|
||||
};
|
||||
@@ -416,7 +435,7 @@ var createPluginSetupWizard = function() {
|
||||
|
||||
// Called to complete the installation
|
||||
var finishInstallation = function() {
|
||||
jenkins.goTo('/');
|
||||
closeInstaller();
|
||||
};
|
||||
|
||||
// load the plugin data, callback
|
||||
@@ -514,28 +533,28 @@ var createPluginSetupWizard = function() {
|
||||
});
|
||||
|
||||
// walk the elements and search for the text
|
||||
var walk = function(elements, element, text, xform) {
|
||||
var i, child, n= element.childNodes.length;
|
||||
for (i = 0; i<n; i++) {
|
||||
child = element.childNodes[i];
|
||||
if (child.nodeType===3 && xform(child.data).indexOf(text)!==-1) {
|
||||
elements.push(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i<n; i++) {
|
||||
child = element.childNodes[i];
|
||||
if (child.nodeType === 1) {
|
||||
walk(elements, child, text, xform);
|
||||
}
|
||||
}
|
||||
};
|
||||
var walk = function(elements, element, text, xform) {
|
||||
var i, child, n= element.childNodes.length;
|
||||
for (i = 0; i<n; i++) {
|
||||
child = element.childNodes[i];
|
||||
if (child.nodeType===3 && xform(child.data).indexOf(text)!==-1) {
|
||||
elements.push(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i<n; i++) {
|
||||
child = element.childNodes[i];
|
||||
if (child.nodeType === 1) {
|
||||
walk(elements, child, text, xform);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// find elements matching the given text, optionally transforming the text before match (e.g. you can .toLowerCase() it)
|
||||
// find elements matching the given text, optionally transforming the text before match (e.g. you can .toLowerCase() it)
|
||||
var findElementsWithText = function(ancestor, text, xform) {
|
||||
var elements= [];
|
||||
walk(elements, ancestor, text, xform ? xform : function(d){ return d; });
|
||||
return elements;
|
||||
var elements= [];
|
||||
walk(elements, ancestor, text, xform ? xform : function(d){ return d; });
|
||||
return elements;
|
||||
};
|
||||
|
||||
// search UI vars
|
||||
@@ -646,7 +665,64 @@ var createPluginSetupWizard = function() {
|
||||
$c.slideDown();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var enableButtonsAfterFrameLoad = function() {
|
||||
$('iframe[src]').load(function() {
|
||||
var location = $(this).contents().get(0).location.href;
|
||||
$('button').prop({disabled:false});
|
||||
});
|
||||
};
|
||||
|
||||
var setupFirstUser = function() {
|
||||
setPanel(firstUserPanel, {}, enableButtonsAfterFrameLoad);
|
||||
};
|
||||
|
||||
// call to submit the firstuser
|
||||
var saveFirstUser = function() {
|
||||
$('button').prop({disabled:true});
|
||||
var handleSubmit = function(data) {
|
||||
if(data.status && data.status > 200) {
|
||||
// Nothing we can really do here
|
||||
setPanel(errorPanel, { errorMessage: data.statusText });
|
||||
return;
|
||||
}
|
||||
// we get 200 OK
|
||||
var $page = $(data);
|
||||
var $errors = $page.find('.error');
|
||||
if($errors.length > 0) {
|
||||
var $main = $page.find('#main-panel').detach();
|
||||
if($main.length > 0) {
|
||||
data = data.replace(/body([^>]*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+'</body');
|
||||
}
|
||||
var doc = $('iframe[src]').contents()[0];
|
||||
doc.open();
|
||||
doc.write(data);
|
||||
doc.close();
|
||||
}
|
||||
else {
|
||||
setPanel(setupCompletePanel);
|
||||
}
|
||||
};
|
||||
securityConfig.saveFirstUser($('iframe[src]').contents().find('form:not(.no-json)'), handleSubmit, handleSubmit);
|
||||
};
|
||||
|
||||
var skipFirstUser = function() {
|
||||
$('button').prop({disabled:true});
|
||||
setPanel(setupCompletePanel, {message: translations.installWizard_firstUserSkippedMessage});
|
||||
};
|
||||
|
||||
// call to setup the proxy
|
||||
var setupProxy = function() {
|
||||
setPanel(proxyConfigPanel, {}, enableButtonsAfterFrameLoad);
|
||||
};
|
||||
|
||||
// Save the proxy config
|
||||
var saveProxyConfig = function() {
|
||||
securityConfig.saveProxy($('iframe[src]').contents().find('form:not(.no-json)'), function() {
|
||||
jenkins.goTo('/'); // this will re-run connectivity test
|
||||
});
|
||||
};
|
||||
|
||||
// Call this to resume an installation after restart
|
||||
var resumeInstallation = function() {
|
||||
// don't re-initialize installing plugins
|
||||
@@ -716,7 +792,12 @@ var createPluginSetupWizard = function() {
|
||||
'.select-category': selectCategory,
|
||||
'.close': closeInstaller,
|
||||
'.resume-installation': resumeInstallation,
|
||||
'.install-done-restart': restartJenkins
|
||||
'.install-done-restart': restartJenkins,
|
||||
'.save-first-user:not([disabled])': saveFirstUser,
|
||||
'.skip-first-user': skipFirstUser,
|
||||
'.show-proxy-config': setupProxy,
|
||||
'.save-proxy-config': saveProxyConfig,
|
||||
'.skip-plugin-installs': function() { installPlugins([]); }
|
||||
};
|
||||
for(var cls in actions) {
|
||||
bindClickHandler(cls, actions[cls]);
|
||||
@@ -737,26 +818,28 @@ var createPluginSetupWizard = function() {
|
||||
}
|
||||
|
||||
// check for updates when first loaded...
|
||||
pluginManager.installStatus(handleGenericError(function(jobs) {
|
||||
pluginManager.installStatus(handleGenericError(function(data) {
|
||||
var jobs = data.jobs;
|
||||
|
||||
if(jobs.length > 0) {
|
||||
if (installingPlugins.length === 0) {
|
||||
// This can happen on a page reload if we are in the middle of
|
||||
// an install. So, lets get a list of plugins being installed at the
|
||||
// moment and use that as the "selectedPlugins" list.
|
||||
selectedPluginNames = [];
|
||||
loadPluginData(handleGenericError(function() {
|
||||
for (var i = 0; i < jobs.length; i++) {
|
||||
// If the job does not have a 'correlationId', then it was not selected
|
||||
// by the user for install i.e. it's probably a dependency plugin.
|
||||
if (jobs[i].correlationId) {
|
||||
selectedPluginNames.push(jobs[i].name);
|
||||
}
|
||||
}
|
||||
showInstallProgress();
|
||||
}));
|
||||
} else {
|
||||
showInstallProgress();
|
||||
}
|
||||
if (installingPlugins.length === 0) {
|
||||
// This can happen on a page reload if we are in the middle of
|
||||
// an install. So, lets get a list of plugins being installed at the
|
||||
// moment and use that as the "selectedPlugins" list.
|
||||
selectedPluginNames = [];
|
||||
loadPluginData(handleGenericError(function() {
|
||||
for (var i = 0; i < jobs.length; i++) {
|
||||
// If the job does not have a 'correlationId', then it was not selected
|
||||
// by the user for install i.e. it's probably a dependency plugin.
|
||||
if (jobs[i].correlationId) {
|
||||
selectedPluginNames.push(jobs[i].name);
|
||||
}
|
||||
}
|
||||
showInstallProgress(data.state);
|
||||
}));
|
||||
} else {
|
||||
showInstallProgress(data.state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -774,12 +857,12 @@ var createPluginSetupWizard = function() {
|
||||
|
||||
for(var plugName in incompleteStatus) {
|
||||
var j = installingPlugins[plugName];
|
||||
|
||||
if (!j) {
|
||||
console.warn('Plugin "' + plugName + '" not found in the list of installing plugins.');
|
||||
console.warn('\tInstalling plugins: ' + installingPlugins.names);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!j) {
|
||||
console.warn('Plugin "' + plugName + '" not found in the list of installing plugins.');
|
||||
console.warn('\tInstalling plugins: ' + installingPlugins.names);
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = false;
|
||||
var state = false;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{translations.installWizard_addFirstUser_title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="jumbotron welcome-panel security-panel">
|
||||
<iframe src="{{baseUrl}}/securityRealm/setupWizardFirstUser"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link skip-first-user" disabled>
|
||||
{{translations.installWizard_skipFirstUser}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary save-first-user" disabled>
|
||||
{{translations.installWizard_saveFirstUser}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,5 +1,4 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{translations.installWizard_installIncomplete_title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{translations.installWizard_offline_title}}</h4>
|
||||
<h4 class="modal-title">{{translations.installWizard_offline_title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="jumbotron welcome-panel offline">
|
||||
<h1>{{translations.installWizard_offline_title}}</h1>
|
||||
<p>{{{translations.installWizard_offline_message}}}</p>
|
||||
</div>
|
||||
<div class="jumbotron welcome-panel offline">
|
||||
<h1>{{translations.installWizard_offline_title}}</h1>
|
||||
<p>{{{translations.installWizard_offline_message}}}</p>
|
||||
<p>
|
||||
<button type="button" class="btn btn-primary show-proxy-config">{{translations.installWizard_configureProxy_label}}</button>
|
||||
<button type="button" class="btn btn-primary skip-plugin-installs">{{translations.installWizard_skipPluginInstallations}}</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,4 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{translations.installWizard_installCustom_title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body plugin-selector">
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{translations.installWizard_configureProxy_label}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="jumbotron welcome-panel security-panel">
|
||||
<iframe src="{{baseUrl}}/setupWizard/proxy-configuration"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link install-home">
|
||||
{{translations.installWizard_goBack}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary save-proxy-config" disabled>
|
||||
{{translations.installWizard_configureProxy_save}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{translations.installWizard_installComplete_title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="jumbotron welcome-panel success-panel">
|
||||
<h1>{{translations.installWizard_installComplete_banner}}</h1>
|
||||
|
||||
{{{message}}}
|
||||
|
||||
{{#if restartRequired}}
|
||||
<p>{{translations.installWizard_installComplete_message}} {{translations.installWizard_installComplete_restartRequiredMessage}}</p>
|
||||
<button type="button" class="btn btn-primary install-done-restart">
|
||||
{{translations.installWizard_installComplete_restartLabel}}
|
||||
</button>
|
||||
{{else}}
|
||||
<p>{{translations.installWizard_installComplete_message}}</p>
|
||||
<button type="button" class="btn btn-primary install-done">
|
||||
{{translations.installWizard_installComplete_finishButtonLabel}}
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +1,9 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{translations.installWizard_installComplete_title}}</h4>
|
||||
<h4 class="modal-title">{{translations.installWizard_pluginsInstalled_title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="jumbotron welcome-panel success-panel">
|
||||
<h1>{{translations.installWizard_installComplete_banner}}</h1>
|
||||
<h1>{{translations.installWizard_pluginsInstalled_banner}}</h1>
|
||||
|
||||
{{#if restartRequired}}
|
||||
<p>{{translations.installWizard_installComplete_message}} {{translations.installWizard_installComplete_restartRequiredMessage}}</p>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{translations.installWizard_welcomePanel_title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
+112
-24
@@ -28,15 +28,15 @@ exports.stringify = function(o) {
|
||||
h: Hash.prototype.toJSON,
|
||||
s: String.prototype.toJSON
|
||||
};
|
||||
try {
|
||||
delete Array.prototype.toJSON;
|
||||
try {
|
||||
delete Array.prototype.toJSON;
|
||||
delete Object.prototype.toJSON;
|
||||
delete Hash.prototype.toJSON;
|
||||
delete String.prototype.toJSON;
|
||||
delete Hash.prototype.toJSON;
|
||||
delete String.prototype.toJSON;
|
||||
|
||||
return JSON.stringify(o);
|
||||
}
|
||||
finally {
|
||||
}
|
||||
finally {
|
||||
if(protoJSON.a) {
|
||||
Array.prototype.toJSON = protoJSON.a;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ exports.stringify = function(o) {
|
||||
if(protoJSON.s) {
|
||||
String.prototype.toJSON = protoJSON.s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return JSON.stringify(o);
|
||||
@@ -67,7 +67,7 @@ exports.idIfy = function(str) {
|
||||
* redirect
|
||||
*/
|
||||
exports.goTo = function(url) {
|
||||
wh.getWindow().location.replace(exports.baseUrl() + url);
|
||||
wh.getWindow().location.replace(exports.baseUrl() + url);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -76,13 +76,13 @@ exports.goTo = function(url) {
|
||||
*/
|
||||
exports.get = function(url, success, options) {
|
||||
if(debug) {
|
||||
console.log('get: ' + url);
|
||||
}
|
||||
console.log('get: ' + url);
|
||||
}
|
||||
var $ = jquery.getJQuery();
|
||||
var args = {
|
||||
url: exports.baseUrl() + url,
|
||||
type: 'GET',
|
||||
cache: false,
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
success: success
|
||||
};
|
||||
@@ -98,22 +98,49 @@ exports.get = function(url, success, options) {
|
||||
*/
|
||||
exports.post = function(url, data, success, options) {
|
||||
if(debug) {
|
||||
console.log('post: ' + url);
|
||||
}
|
||||
console.log('post: ' + url);
|
||||
}
|
||||
|
||||
var $ = jquery.getJQuery();
|
||||
|
||||
// handle crumbs
|
||||
var headers = {};
|
||||
var wnd = wh.getWindow();
|
||||
var crumb;
|
||||
if('crumb' in options) {
|
||||
crumb = options.crumb;
|
||||
}
|
||||
else if('crumb' in wnd) {
|
||||
crumb = wnd.crumb;
|
||||
}
|
||||
|
||||
if(crumb) {
|
||||
headers[crumb.fieldName] = crumb.value;
|
||||
}
|
||||
|
||||
var formBody = data;
|
||||
if(formBody instanceof Object) {
|
||||
if(crumb) {
|
||||
formBody = $.extend({}, formBody);
|
||||
formBody[crumb.fieldName] = crumb.value;
|
||||
}
|
||||
formBody = exports.stringify(formBody);
|
||||
}
|
||||
|
||||
var args = {
|
||||
url: exports.baseUrl() + url,
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
data: exports.stringify(data),
|
||||
contentType: "application/json",
|
||||
success: success
|
||||
data: formBody,
|
||||
contentType: "application/json",
|
||||
success: success,
|
||||
headers: headers
|
||||
};
|
||||
if(options instanceof Object) {
|
||||
$.extend(args, options);
|
||||
}
|
||||
$.ajax(args);
|
||||
$.ajax(args);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -124,20 +151,20 @@ exports.initHandlebars = function() {
|
||||
|
||||
Handlebars.registerHelper('ifeq', function(o1, o2, options) {
|
||||
if(o1 === o2) {
|
||||
return options.fn();
|
||||
}
|
||||
return options.fn();
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('ifneq', function(o1, o2, options) {
|
||||
if(o1 !== o2) {
|
||||
return options.fn();
|
||||
}
|
||||
return options.fn();
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('in-array', function(arr, val, options) {
|
||||
if(arr.indexOf(val) >= 0) {
|
||||
return options.fn();
|
||||
}
|
||||
return options.fn();
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('id', exports.idIfy);
|
||||
@@ -186,3 +213,64 @@ exports.testConnectivity = function(handler) {
|
||||
};
|
||||
testConnectivity();
|
||||
};
|
||||
|
||||
/**
|
||||
* gets the window containing a form, taking in to account top-level iframes
|
||||
*/
|
||||
exports.getWindow = function($form) {
|
||||
var $ = jquery.getJQuery();
|
||||
$form = $($form);
|
||||
var wnd = wh.getWindow();
|
||||
$(top.document).find('iframe').each(function() {
|
||||
var windowFrame = this.contentWindow;
|
||||
var $f = $(this).contents().find('form');
|
||||
if($f.length > 0 && $form[0] === $f[0]) {
|
||||
wnd = windowFrame;
|
||||
}
|
||||
});
|
||||
return wnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a stapler form post
|
||||
*/
|
||||
exports.buildFormPost = function($form) {
|
||||
var $ = jquery.getJQuery();
|
||||
$form = $($form);
|
||||
var wnd = exports.getWindow($form);
|
||||
var form = $form[0];
|
||||
if(wnd.buildFormTree(form)) {
|
||||
return $form.serialize() +
|
||||
'&core:apply=&Submit=Save&json=' + $form.find('input[name=json]').val();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the crumb, if crumbs are enabled
|
||||
*/
|
||||
exports.getFormCrumb = function($form) {
|
||||
var $ = jquery.getJQuery();
|
||||
$form = $($form);
|
||||
var wnd = exports.getWindow($form);
|
||||
return wnd.crumb;
|
||||
};
|
||||
|
||||
/**
|
||||
* Jenkins Stapler JSON POST callback
|
||||
* If last parameter is an object, will be extended to jQuery options (e.g. pass { error: function() ... } to handle errors)
|
||||
*/
|
||||
exports.staplerPost = function(url, $form, success, options) {
|
||||
var $ = jquery.getJQuery();
|
||||
$form = $($form);
|
||||
var postBody = exports.buildFormPost($form);
|
||||
var crumb = exports.getFormCrumb($form);
|
||||
exports.post(
|
||||
url,
|
||||
postBody,
|
||||
success, $.extend({
|
||||
processData: false,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
crumb: crumb
|
||||
}, options));
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -15,20 +15,20 @@ pluginList.recommendedPlugins = ['subversion'];
|
||||
|
||||
// Iterates through all responses until the end and returns the last response repeatedly
|
||||
var LastResponse = function(responses) {
|
||||
var counter = 0;
|
||||
this.next = function() {
|
||||
if(counter < responses.length) {
|
||||
try {
|
||||
return responses[counter];
|
||||
} finally {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
if(responses.length > 0) {
|
||||
return responses[counter-1];
|
||||
}
|
||||
return { status: 'fail' };
|
||||
};
|
||||
var counter = 0;
|
||||
this.next = function() {
|
||||
if(counter < responses.length) {
|
||||
try {
|
||||
return responses[counter];
|
||||
} finally {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
if(responses.length > 0) {
|
||||
return responses[counter-1];
|
||||
}
|
||||
return { status: 'fail' };
|
||||
};
|
||||
};
|
||||
|
||||
// common mocks for jQuery $.ajax
|
||||
@@ -48,17 +48,23 @@ var ajaxMocks = function(responseMappings) {
|
||||
},
|
||||
'/jenkins/updateCenter/installStatus': new LastResponse([{
|
||||
status: 'ok',
|
||||
data: [] // first, return nothing by default, no ongoing install
|
||||
data: { // first, return nothing by default, no ongoing install
|
||||
state: 'NEW',
|
||||
jobs: []
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 'ok',
|
||||
data: [
|
||||
{
|
||||
name: 'subversion',
|
||||
type: 'InstallJob',
|
||||
installStatus: 'Success'
|
||||
}
|
||||
]
|
||||
data: {
|
||||
state: 'INSTALLING_PLUGINS',
|
||||
jobs: [
|
||||
{
|
||||
name: 'subversion',
|
||||
type: 'InstallJob',
|
||||
installStatus: 'Success'
|
||||
}
|
||||
]
|
||||
}
|
||||
}]),
|
||||
'/jenkins/pluginManager/plugins': {
|
||||
status: 'ok',
|
||||
@@ -92,8 +98,8 @@ var ajaxMocks = function(responseMappings) {
|
||||
}
|
||||
},
|
||||
'/jenkins/pluginManager/installPlugins': {
|
||||
status: 'ok',
|
||||
data: 'RANDOM_UUID_1234'
|
||||
status: 'ok',
|
||||
data: 'RANDOM_UUID_1234'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -114,7 +120,7 @@ var ajaxMocks = function(responseMappings) {
|
||||
throw 'No data mapping provided for AJAX call: ' + call.url;
|
||||
}
|
||||
if(response instanceof LastResponse) {
|
||||
response = response.next();
|
||||
response = response.next();
|
||||
}
|
||||
call.success(response);
|
||||
};
|
||||
@@ -122,7 +128,7 @@ var ajaxMocks = function(responseMappings) {
|
||||
|
||||
// call this for each test, it will provide a new wizard, jquery to the caller
|
||||
var test = function(test, ajaxMappings) {
|
||||
jsTest.onPage(function() {
|
||||
jsTest.onPage(function() {
|
||||
// deps
|
||||
var $ = getJQuery();
|
||||
|
||||
@@ -133,41 +139,41 @@ var test = function(test, ajaxMappings) {
|
||||
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
|
||||
|
||||
// exported init
|
||||
pluginSetupWizard.init();
|
||||
pluginSetupWizard.init('body');
|
||||
|
||||
test($, pluginSetupWizard);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// helper to validate the appropriate plugins were installed
|
||||
var validatePlugins = function(plugins, complete) {
|
||||
var jenkins = jsTest.requireSrcModule('util/jenkins');
|
||||
if(!jenkins.originalPost) {
|
||||
jenkins.originalPost = jenkins.post;
|
||||
jenkins.originalPost = jenkins.post;
|
||||
}
|
||||
jenkins.post = function(url, data, cb) {
|
||||
expect(url).toBe('/pluginManager/installPlugins');
|
||||
var allMatch = true;
|
||||
for(var i = 0; i < plugins.length; i++) {
|
||||
if(data.plugins.indexOf(plugins[i]) < 0) {
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
if(data.plugins.indexOf(plugins[i]) < 0) {
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!allMatch) {
|
||||
expect(JSON.stringify(plugins)).toBe('In: ' + JSON.stringify(data.plugins));
|
||||
}
|
||||
// return status
|
||||
cb({status:'ok',data:{correlationId:1}});
|
||||
if(complete) {
|
||||
complete();
|
||||
}
|
||||
if(complete) {
|
||||
complete();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
describe("pluginSetupWizard.js", function () {
|
||||
it("wizard shows", function (done) {
|
||||
test(function($) {
|
||||
it("wizard shows", function (done) {
|
||||
test(function($) {
|
||||
// Make sure the dialog was shown
|
||||
var $wizard = $('.plugin-setup-wizard');
|
||||
expect($wizard.size()).toBe(1);
|
||||
@@ -176,49 +182,49 @@ describe("pluginSetupWizard.js", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("offline shows", function (done) {
|
||||
jsTest.onPage(function() {
|
||||
// deps
|
||||
var jenkins = jsTest.requireSrcModule('./util/jenkins');
|
||||
it("offline shows", function (done) {
|
||||
jsTest.onPage(function() {
|
||||
// deps
|
||||
var jenkins = jsTest.requireSrcModule('./util/jenkins');
|
||||
|
||||
var $ = getJQuery();
|
||||
$.ajax = ajaxMocks();
|
||||
var $ = getJQuery();
|
||||
$.ajax = ajaxMocks();
|
||||
|
||||
var get = jenkins.get;
|
||||
try {
|
||||
// Respond with failure
|
||||
jenkins.get = function(url, cb) {
|
||||
if (debug) {
|
||||
var get = jenkins.get;
|
||||
try {
|
||||
// Respond with failure
|
||||
jenkins.get = function(url, cb) {
|
||||
if (debug) {
|
||||
console.log('Jenkins.GET: ' + url);
|
||||
}
|
||||
|
||||
if(url === '/updateCenter/connectionStatus?siteId=default') {
|
||||
cb({
|
||||
status: 'ok',
|
||||
data: {
|
||||
updatesite: 'ERROR',
|
||||
internet: 'ERROR'
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
get(url, cb);
|
||||
}
|
||||
};
|
||||
if(url === '/updateCenter/connectionStatus?siteId=default') {
|
||||
cb({
|
||||
status: 'ok',
|
||||
data: {
|
||||
updatesite: 'ERROR',
|
||||
internet: 'ERROR'
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
get(url, cb);
|
||||
}
|
||||
};
|
||||
|
||||
// load the module
|
||||
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
|
||||
// load the module
|
||||
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
|
||||
|
||||
// exported init
|
||||
pluginSetupWizard.init();
|
||||
// exported init
|
||||
pluginSetupWizard.init('body');
|
||||
|
||||
expect($('.welcome-panel h1').text()).toBe('Offline');
|
||||
expect($('.welcome-panel h1').text()).toBe('Offline');
|
||||
|
||||
done();
|
||||
} finally {
|
||||
jenkins.get = get;
|
||||
}
|
||||
});
|
||||
done();
|
||||
} finally {
|
||||
jenkins.get = get;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("install defaults", function (done) {
|
||||
@@ -240,16 +246,16 @@ describe("pluginSetupWizard.js", function () {
|
||||
});
|
||||
|
||||
var doit = function($, sel, trigger) {
|
||||
var $el = $(sel);
|
||||
if($el.length !== 1) {
|
||||
console.log('Not found! ' + sel);
|
||||
var $el = $(sel);
|
||||
if($el.length !== 1) {
|
||||
console.log('Not found! ' + sel);
|
||||
console.log(new Error().stack);
|
||||
}
|
||||
if(trigger === 'check') {
|
||||
$el.prop('checked', true);
|
||||
trigger = 'change';
|
||||
}
|
||||
$el.trigger(trigger);
|
||||
}
|
||||
if(trigger === 'check') {
|
||||
$el.prop('checked', true);
|
||||
trigger = 'change';
|
||||
}
|
||||
$el.trigger(trigger);
|
||||
};
|
||||
|
||||
it("install custom", function (done) {
|
||||
@@ -258,8 +264,8 @@ describe("pluginSetupWizard.js", function () {
|
||||
|
||||
// validate a call to installPlugins with our defaults
|
||||
validatePlugins(['junit','mailer'], function() {
|
||||
// install a specific, other 'set' of plugins
|
||||
$('input[name=searchbox]').val('junit');
|
||||
// install a specific, other 'set' of plugins
|
||||
$('input[name=searchbox]').val('junit');
|
||||
|
||||
done();
|
||||
});
|
||||
@@ -277,7 +283,7 @@ describe("pluginSetupWizard.js", function () {
|
||||
|
||||
it("resume install", function (done) {
|
||||
var ajaxMappings = {
|
||||
'/jenkins/updateCenter/incompleteInstallStatus': {
|
||||
'/jenkins/updateCenter/incompleteInstallStatus': {
|
||||
status: 'ok',
|
||||
data: {
|
||||
'junit': 'Success',
|
||||
@@ -288,27 +294,27 @@ describe("pluginSetupWizard.js", function () {
|
||||
}
|
||||
};
|
||||
test(function($) {
|
||||
expect($('.modal-title').text()).toBe('Resume Installation');
|
||||
expect($('*[data-name="junit"]').is('.success')).toBe(true);
|
||||
done();
|
||||
expect($('.modal-title').text()).toBe('Resume Installation');
|
||||
expect($('*[data-name="junit"]').is('.success')).toBe(true);
|
||||
done();
|
||||
}, ajaxMappings);
|
||||
});
|
||||
|
||||
it("error conditions", function (done) {
|
||||
var ajaxMappings = {
|
||||
'/jenkins/updateCenter/incompleteInstallStatus': {
|
||||
'/jenkins/updateCenter/incompleteInstallStatus': {
|
||||
status: 'error',
|
||||
data: {
|
||||
'junit': 'Success',
|
||||
'subversion': 'Pending',
|
||||
'other': 'Failed',
|
||||
'mailer': 'Success'
|
||||
'junit': 'Success',
|
||||
'subversion': 'Pending',
|
||||
'other': 'Failed',
|
||||
'mailer': 'Success'
|
||||
}
|
||||
}
|
||||
};
|
||||
test(function($) {
|
||||
expect($('.error-container h1').html()).toBe('Error');
|
||||
done();
|
||||
expect($('.error-container h1').html()).toBe('Error');
|
||||
done();
|
||||
}, ajaxMappings);
|
||||
});
|
||||
|
||||
@@ -321,18 +327,24 @@ describe("pluginSetupWizard.js", function () {
|
||||
var ajaxMappings = {
|
||||
'/jenkins/updateCenter/installStatus': new LastResponse([{
|
||||
status: 'ok',
|
||||
data: [] // first, return nothing by default, no ongoing install
|
||||
data: { // first, return nothing by default, no ongoing install
|
||||
state: 'NEW',
|
||||
jobs: []
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 'ok',
|
||||
data: [
|
||||
{
|
||||
name: 'subversion',
|
||||
type: 'InstallJob',
|
||||
installStatus: 'Success',
|
||||
requiresRestart: 'true' // a string...
|
||||
}
|
||||
]
|
||||
data: {
|
||||
state: 'INSTALLING_PLUGINS',
|
||||
jobs: [
|
||||
{
|
||||
name: 'subversion',
|
||||
type: 'InstallJob',
|
||||
installStatus: 'Success',
|
||||
requiresRestart: 'true' // a string...
|
||||
}
|
||||
]
|
||||
}
|
||||
}])
|
||||
};
|
||||
test(function($) {
|
||||
@@ -340,12 +352,13 @@ describe("pluginSetupWizard.js", function () {
|
||||
expect(goButton.size()).toBe(1);
|
||||
|
||||
// validate a call to installPlugins with our defaults
|
||||
setTimeout(function() {
|
||||
setTimeout(function() {
|
||||
expect($('.install-done').is(':visible')).toBe(false);
|
||||
expect($('.install-done-restart').is(':visible')).toBe(true);
|
||||
|
||||
expect($('.save-first-user').is(':visible')).toBe(true);
|
||||
|
||||
done();
|
||||
}, 500);
|
||||
}, 500);
|
||||
|
||||
goButton.click();
|
||||
}, ajaxMappings);
|
||||
|
||||
Reference in new issue
Block a user