Merge pull request #104 from olblak/config/02
BoardElections && CSS improvements
This commit is contained in:
commit
fa149f7287
22
Dockerfile
22
Dockerfile
@ -5,25 +5,31 @@ LABEL \
|
||||
Project="https://github.com/jenkins-infra/account-app" \
|
||||
Maintainer="infra@lists.jenkins-ci.org"
|
||||
|
||||
ENV ELECTION_LOGDIR=/var/log/accountapp/elections
|
||||
ENV CIRCUIT_BREAKER_FILE=/etc/accountapp/circuitBreaker.txt
|
||||
ENV SMTP_SERVER=localhost
|
||||
ENV JIRA_URL=https://issues.jenkins-ci.org
|
||||
ENV APP_URL=http://accounts.jenkins.io/
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENV CIRCUIT_BREAKER_FILE /etc/accountapp/circuitBreaker.txt
|
||||
|
||||
# /home/jetty/.app is apparently needed by Stapler for some weird reason. O_O
|
||||
RUN \
|
||||
mkdir -p /home/jetty/.app &&\
|
||||
mkdir -p /etc/accountapp
|
||||
RUN mkdir -p /home/jetty/.app &&\
|
||||
mkdir -p /etc/accountapp &&\
|
||||
mkdir -p $ELECTION_LOGDIR
|
||||
|
||||
COPY config.properties.example /etc/accountapp/config.properties.example
|
||||
COPY circuitBreaker.txt /etc/accountapp/circuitBreaker.txt
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN \
|
||||
chmod 0755 /entrypoint.sh &&\
|
||||
chown -R jetty:root /etc/accountapp
|
||||
|
||||
COPY build/libs/accountapp*.war /var/lib/jetty/webapps/ROOT.war
|
||||
|
||||
RUN chmod 0755 /entrypoint.sh &&\
|
||||
chown -R jetty:root /etc/accountapp &&\
|
||||
chown -R jetty:root /var/lib/jetty &&\
|
||||
chown -R jetty:root $ELECTION_LOGDIR
|
||||
|
||||
USER jetty
|
||||
|
||||
ENTRYPOINT /entrypoint.sh
|
||||
|
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -11,7 +11,7 @@ node('docker') {
|
||||
stage('Build') {
|
||||
timestamps {
|
||||
checkout scm
|
||||
docker.image('java:8').inside {
|
||||
docker.image('openjdk:8-jdk').inside {
|
||||
sh './gradlew --no-daemon --info war'
|
||||
archiveArtifacts artifacts: 'build/libs/*.war', fingerprint: true
|
||||
}
|
||||
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
.PHONY: build clean run
|
||||
|
||||
current_dir := $(shell pwd)
|
||||
current_user := $(shell id -u)
|
||||
|
||||
build:
|
||||
docker run --rm -v $(current_dir):/opt/accountapp/ -u $(current_user):$(current_user) -w /opt/accountapp --entrypoint ./gradlew openjdk:8-jdk --no-daemon --info war
|
||||
docker-compose build run
|
||||
|
||||
clean:
|
||||
docker run --rm -v $(current_dir):/opt/accountapp/ -u $(current_user):$(current_user) -w /opt/accountapp --entrypoint ./gradlew openjdk:8-jdk clean
|
||||
docker-compose rm -f run
|
||||
|
||||
run:
|
||||
docker-compose up run
|
27
README.md
27
README.md
@ -5,7 +5,7 @@
|
||||
First, set up a tunnel to Jenkins LDAP server. Run the following command and
|
||||
keep the terminal open:
|
||||
|
||||
ssh -L 9389:localhost:389 ldap.jenkins.io
|
||||
ssh -4 -L 9389:localhost:389 ldap.jenkins.io
|
||||
|
||||
Create `config.properties` in the same directory as `pom.xml`. See the
|
||||
`Parameters` class for the details, but it should look something like the
|
||||
@ -37,17 +37,20 @@ _Require ssh tunnel to an ldap server and an WAR archive_
|
||||
* Create the file ```.env``` used by docker-compose to load configuration
|
||||
.env example
|
||||
```
|
||||
LDAP_URL=server=ldap://localhost:9389/
|
||||
LDAP_PASSWORD=<insert your ldap password>
|
||||
APP_URL=http://localhost:8080/
|
||||
ELECTION_CANDIDATES=alice,bob
|
||||
ELECTION_CLOSE=2038/01/19
|
||||
ELECTION_OPEN=1970/01/01
|
||||
JIRA_USERNAME=<insert your jira username>
|
||||
JIRA_PASSWORD=<insert your jira password>
|
||||
JIRA_URL=https://issues.jenkins-ci.org
|
||||
SMTP_SERVER=localhost
|
||||
RECAPTCHA_PRIVATE_KEY=recaptcha_private_key
|
||||
RECAPTCHA_PUBLIC_KEY=recaptcha_public_key
|
||||
APP_URL=http://localhost:8080/
|
||||
LDAP_URL=server=ldap://localhost:9389/
|
||||
LDAP_PASSWORD=<insert your ldap password>
|
||||
LDAP_MANAGER_DN=cn=admin,dc=jenkins-ci,dc=org
|
||||
LDAP_NEW_USER_BASE_DN=ou=people,dc=jenkins-ci,dc=org
|
||||
RECAPTCHA_PRIVATE_KEY=recaptcha_private_key
|
||||
RECAPTCHA_PUBLIC_KEY=recaptcha_public_key
|
||||
SMTP_SERVER=localhost
|
||||
```
|
||||
* Run docker-compose
|
||||
```docker-compose up --build accountapp```
|
||||
@ -72,6 +75,10 @@ we may want to use environment variable.
|
||||
```
|
||||
* APP_URL
|
||||
* CIRCUIT_BREAKER_FILE
|
||||
* ELECTION_CANDIDATES coma separated list of candidates
|
||||
* ELECTION_CLOSE date election will close. yyyy/MM/dd
|
||||
* ELECTION_OPEN date election will open. yyyy/MM/dd
|
||||
* ELECTION_LOGDIR
|
||||
* JIRA_PASSWORD
|
||||
* JIRA_URL
|
||||
* JIRA_USERNAME
|
||||
@ -83,3 +90,9 @@ we may want to use environment variable.
|
||||
* RECAPTCHA_PRIVATE_KEY
|
||||
* SMTP_SERVER
|
||||
```
|
||||
|
||||
## Makefile
|
||||
|
||||
``` make build```: Build build/libs/accountapp-2.5.war and docker image
|
||||
``` make run ```: Run docker container
|
||||
``` make clean ```: Clean build environment
|
||||
|
@ -24,7 +24,7 @@ dependencies {
|
||||
compile('org.kohsuke.stapler:stapler-jelly:[1.203,2.0)')
|
||||
compile 'org.kohsuke.stapler:stapler-openid-server:[1.0,2.0)'
|
||||
|
||||
compile 'org.jvnet.hudson:commons-jelly-tags-define:1.0.1-hudson-20071021'
|
||||
compile 'commons-jelly:commons-jelly-tags-define:1.0'
|
||||
|
||||
compile 'javax.mail:mail:[1.4,2.0)'
|
||||
compile 'javax.activation:activation:1.1.1'
|
||||
@ -33,6 +33,13 @@ dependencies {
|
||||
exclude module: 'javamail'
|
||||
}
|
||||
|
||||
compile 'com.esotericsoftware.yamlbeans:yamlbeans:1.11'
|
||||
|
||||
compile 'org.webjars:webjars-servlet-2.x:1.5'
|
||||
compile 'org.webjars:jquery:3.2.0'
|
||||
compile 'org.webjars:jquery-ui:1.12.1'
|
||||
compile 'org.webjars.bower:fontawesome:4.7.0'
|
||||
|
||||
testCompile 'junit:junit:[4.8.1,5.0)'
|
||||
}
|
||||
|
||||
|
@ -8,15 +8,24 @@ managerPassword=LDAP_PASSWORD
|
||||
newUserBaseDN=LDAP_NEW_USER_BASE_DN
|
||||
|
||||
# Host which accountapp can use for sending out password reset and other emails
|
||||
# Optional: Default value set to localhost
|
||||
smtpServer=SMTP_SERVER
|
||||
|
||||
recaptchaPublicKey=RECAPTCHA_PUBLIC_KEY
|
||||
recaptchaPrivateKey=RECAPTCHA_PRIVATE_KEY
|
||||
|
||||
# Optional: Default value set to http://accounts.jenkins.io/
|
||||
url=APP_URL
|
||||
|
||||
# Create this file on the host machine in order to temporarily disable account
|
||||
# creation
|
||||
# Optional: Default value set to /etc/accountapp/circuitBreaker.txt
|
||||
circuitBreakerFile=CIRCUIT_BREAKER_FILE
|
||||
|
||||
electionCandidates=ELECTION_CANDIDATES
|
||||
electionClose=ELECTION_CLOSE
|
||||
electionOpen= ELECTION_OPEN
|
||||
# Optional: Default value set to /var/log/accountapp/elections
|
||||
electionLogDir=ELECTION_LOGDIR
|
||||
|
||||
# vim: ft=conf
|
||||
|
@ -1,6 +1,6 @@
|
||||
version: '3'
|
||||
services:
|
||||
accountapp:
|
||||
run:
|
||||
build: .
|
||||
image: accountapp:latest
|
||||
env_file: .env
|
||||
|
@ -18,6 +18,18 @@ init_config_properties() {
|
||||
: "${LDAP_NEW_USER_BASE_DN:? Require ldap new user base DN}"
|
||||
: "${CIRCUIT_BREAKER_FILE:? Require circuitBreaker file}"
|
||||
|
||||
# Elections configurations
|
||||
: "${ELECTION_CANDIDATES:? Required coma separated list of candidates}"
|
||||
: "${ELECTION_CLOSE:? Required date election will close. yyyy/MM/dd}"
|
||||
: "${ELECTION_OPEN:? date election will open. yyyy/MM/dd }"
|
||||
: "${ELECTION_LOGDIR:? Require election log directory }"
|
||||
|
||||
#Directory to store collected votes. assume this path is well persisted/backup
|
||||
|
||||
if [ ! -d "${ELECTION_LOGDIR}" ]; then
|
||||
mkdir -p "${ELECTION_LOGDIR}"
|
||||
chown jetty: "$ELECTION_LOGDIR"
|
||||
fi
|
||||
|
||||
cp /etc/accountapp/config.properties.example /etc/accountapp/config.properties
|
||||
|
||||
@ -31,6 +43,10 @@ init_config_properties() {
|
||||
sed -i "s#LDAP_MANAGER_DN#$LDAP_MANAGER_DN#" /etc/accountapp/config.properties
|
||||
sed -i "s#LDAP_NEW_USER_BASE_DN#$LDAP_NEW_USER_BASE_DN#" /etc/accountapp/config.properties
|
||||
sed -i "s#CIRCUIT_BREAKER_FILE#$CIRCUIT_BREAKER_FILE#" /etc/accountapp/config.properties
|
||||
sed -i "s#ELECTION_CANDIDATES#$ELECTION_CANDIDATES#" /etc/accountapp/config.properties
|
||||
sed -i "s#ELECTION_OPEN#$ELECTION_OPEN#" /etc/accountapp/config.properties
|
||||
sed -i "s#ELECTION_CLOSE#$ELECTION_CLOSE#" /etc/accountapp/config.properties
|
||||
sed -i "s#ELECTION_LOGDIR#$ELECTION_LOGDIR#" /etc/accountapp/config.properties
|
||||
}
|
||||
|
||||
if [ ! -f /etc/accountapp/config.properties ]; then
|
||||
|
@ -16,6 +16,7 @@ import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.config.ConfigurationLoader;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonReader;
|
||||
@ -51,6 +52,7 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.rmi.RemoteException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -72,8 +74,7 @@ import static javax.naming.directory.DirContext.REMOVE_ATTRIBUTE;
|
||||
import static javax.naming.directory.DirContext.REPLACE_ATTRIBUTE;
|
||||
import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;
|
||||
import static org.apache.commons.lang.StringUtils.isEmpty;
|
||||
import static org.jenkinsci.account.LdapAbuse.REGISTRATION_DATE;
|
||||
import static org.jenkinsci.account.LdapAbuse.SENIOR_STATUS;
|
||||
import static org.jenkinsci.account.LdapAbuse.*;
|
||||
|
||||
/**
|
||||
* Root of the account application.
|
||||
@ -94,17 +95,22 @@ public class Application {
|
||||
// not exposing this to UI
|
||||
/*package*/ final CircuitBreaker circuitBreaker;
|
||||
|
||||
public Application(Parameters params) throws IOException {
|
||||
private BoardElection boardElection;
|
||||
|
||||
public Application(Parameters params) throws Exception {
|
||||
this.params = params;
|
||||
this.openid = new JenkinsOpenIDServer(this);
|
||||
this.circuitBreaker = new CircuitBreaker(params);
|
||||
if (params.electionCandidates() != null) {
|
||||
this.boardElection = new BoardElection(this, params);
|
||||
}
|
||||
}
|
||||
|
||||
public Application(Properties config) throws IOException {
|
||||
public Application(Properties config) throws Exception {
|
||||
this(ConfigurationLoader.from(config).as(Parameters.class));
|
||||
}
|
||||
|
||||
public Application(File config) throws IOException {
|
||||
public Application(File config) throws Exception {
|
||||
this(ConfigurationLoader.from(config).as(Parameters.class));
|
||||
}
|
||||
|
||||
@ -548,7 +554,7 @@ public class Application {
|
||||
}
|
||||
|
||||
// to limit the redirect to this application, require that the from URL starts from '/'
|
||||
if (from==null || !from.startsWith("/")) from="/myself/";
|
||||
if (from==null || !from.startsWith("/")) from="/";
|
||||
return HttpResponses.redirectTo(from);
|
||||
}
|
||||
|
||||
@ -573,12 +579,12 @@ public class Application {
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return current() !=null;
|
||||
return Myself.current() !=null;
|
||||
}
|
||||
|
||||
public boolean isAdmin() {
|
||||
Myself myself = current();
|
||||
return myself!=null && myself.isAdmin();
|
||||
Myself myself = Myself.current();
|
||||
return myself !=null && myself.isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -586,22 +592,38 @@ public class Application {
|
||||
* send the user to the login page.
|
||||
*/
|
||||
public Myself getMyself() {
|
||||
Myself myself = current();
|
||||
if (myself==null) {
|
||||
// needs to login
|
||||
StaplerRequest req = Stapler.getCurrentRequest();
|
||||
StringBuilder from = new StringBuilder(req.getRequestURI());
|
||||
if (req.getQueryString()!=null)
|
||||
from.append('?').append(req.getQueryString());
|
||||
try {
|
||||
throw HttpResponses.redirectViaContextPath("login?from="+ URLEncoder.encode(from.toString(),"UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
Myself myself = Myself.current();
|
||||
if (myself ==null) {
|
||||
needToLogin();
|
||||
}
|
||||
return myself;
|
||||
}
|
||||
|
||||
private void needToLogin() {
|
||||
// needs to login
|
||||
StaplerRequest req = Stapler.getCurrentRequest();
|
||||
StringBuilder from = new StringBuilder(req.getRequestURI());
|
||||
if (req.getQueryString()!=null)
|
||||
from.append('?').append(req.getQueryString());
|
||||
try {
|
||||
throw HttpResponses.redirectViaContextPath("login?from="+ URLEncoder.encode(from.toString(),"UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public @CheckForNull BoardElection getBoardElection() {
|
||||
return boardElection;
|
||||
}
|
||||
|
||||
public @CheckForNull BoardElection getElection() {
|
||||
Myself myself = Myself.current();
|
||||
if (myself ==null) {
|
||||
needToLogin();
|
||||
}
|
||||
return boardElection;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a test endpoint to make sure the reverse proxy forwarding is working.
|
||||
*/
|
||||
@ -609,10 +631,6 @@ public class Application {
|
||||
return HttpResponses.plainText(header);
|
||||
}
|
||||
|
||||
private Myself current() {
|
||||
return (Myself) Stapler.getCurrentRequest().getSession().getAttribute(Myself.class.getName());
|
||||
}
|
||||
|
||||
public AdminUI getAdmin() {
|
||||
return getMyself().isAdmin() ? new AdminUI(this) : null;
|
||||
}
|
||||
|
93
src/main/java/org/jenkinsci/account/BoardElection.java
Normal file
93
src/main/java/org/jenkinsci/account/BoardElection.java
Normal file
@ -0,0 +1,93 @@
|
||||
package org.jenkinsci.account;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.kohsuke.stapler.HttpRedirect;
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.QueryParameter;
|
||||
import org.kohsuke.stapler.interceptor.RequirePOST;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FileReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.esotericsoftware.yamlbeans.YamlReader;
|
||||
import com.esotericsoftware.yamlbeans.YamlWriter;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class BoardElection {
|
||||
|
||||
private final Date open;
|
||||
private final Date close;
|
||||
private final Application app;
|
||||
private final String[] candidates;
|
||||
private final String log;
|
||||
|
||||
public BoardElection(Application application, Parameters params) throws Exception {
|
||||
this.app = application;
|
||||
final SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
|
||||
open = format.parse(params.electionOpen());
|
||||
close = format.parse(params.electionClose());
|
||||
candidates = params.electionCandidates().split(",");
|
||||
log = params.electionLogDir() + "/" + params.electionClose().replaceAll("/","") + ".yaml";
|
||||
File f = new File(log);
|
||||
f.createNewFile();
|
||||
}
|
||||
|
||||
@RequirePOST
|
||||
public HttpResponse doVote(@QueryParameter String vote) throws NamingException, IOException {
|
||||
|
||||
final Myself user = Myself.current();
|
||||
if (user == null) {
|
||||
throw new UserError("Need to be authenticated");
|
||||
}
|
||||
|
||||
if (!isOpen()) {
|
||||
throw new UserError("Election is closed");
|
||||
}
|
||||
|
||||
List<String> selected = new ArrayList<>();
|
||||
// TODO check user is "old enough"
|
||||
for (String id : vote.split(",")) {
|
||||
selected.add(candidates[Integer.parseInt(id)]);
|
||||
}
|
||||
// TODO build a nicer yaml structure document
|
||||
YamlReader reader = new YamlReader(new FileReader(log));
|
||||
Object object = new Object();
|
||||
object = reader.read();
|
||||
if (null == object){
|
||||
String init_content = "election_close: " + close;
|
||||
YamlReader init = new YamlReader(init_content);
|
||||
object = init.read();
|
||||
}
|
||||
|
||||
Map map = (Map)object;
|
||||
map.put(user.userId,StringUtils.join(selected, ","));
|
||||
|
||||
YamlWriter writer = new YamlWriter(new FileWriter(log, false));
|
||||
writer.write(map);
|
||||
writer.close();
|
||||
|
||||
// TODO store
|
||||
return new HttpRedirect("done");
|
||||
}
|
||||
|
||||
public String[] getCandidates() {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
final long now = System.currentTimeMillis();
|
||||
return open != null && close != null && open.getTime() <= now && close.getTime() >= now;
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ package org.jenkinsci.account;
|
||||
import org.kohsuke.stapler.HttpRedirect;
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.QueryParameter;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
@ -13,7 +14,8 @@ import javax.naming.ldap.LdapContext;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.jenkinsci.account.LdapAbuse.*;
|
||||
import static org.jenkinsci.account.LdapAbuse.GITHUB_ID;
|
||||
import static org.jenkinsci.account.LdapAbuse.SSH_KEYS;
|
||||
|
||||
/**
|
||||
* Represents the current user logged in and operations on it.
|
||||
@ -29,16 +31,30 @@ public class Myself {
|
||||
private final Set<String> groups;
|
||||
|
||||
public Myself(Application parent, String dn, Attributes attributes, Set<String> groups) throws NamingException {
|
||||
this(parent, dn,
|
||||
getAttribute(attributes,"givenName"),
|
||||
getAttribute(attributes,"sn"),
|
||||
getAttribute(attributes,"mail"),
|
||||
getAttribute(attributes,"cn"),
|
||||
getAttribute(attributes, GITHUB_ID),
|
||||
getAttribute(attributes, SSH_KEYS),
|
||||
groups);
|
||||
}
|
||||
|
||||
public Myself(Application parent, String dn, String firstName, String lastName, String email, String userId, String githubId, String sshKeys, Set<String> groups) {
|
||||
this.parent = parent;
|
||||
this.dn = dn;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
this.userId = userId;
|
||||
this.githubId = githubId;
|
||||
this.sshKeys = sshKeys;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
firstName = getAttribute(attributes,"givenName");
|
||||
lastName = getAttribute(attributes,"sn");
|
||||
email = getAttribute(attributes,"mail");
|
||||
userId = getAttribute(attributes,"cn");
|
||||
githubId = getAttribute(attributes, GITHUB_ID);
|
||||
sshKeys = getAttribute(attributes, SSH_KEYS);
|
||||
public static Myself current() {
|
||||
return (Myself) Stapler.getCurrentRequest().getSession().getAttribute(Myself.class.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,7 +64,7 @@ public class Myself {
|
||||
return groups.contains("admins");
|
||||
}
|
||||
|
||||
private String getAttribute(Attributes attributes, String name) throws NamingException {
|
||||
private static String getAttribute(Attributes attributes, String name) throws NamingException {
|
||||
Attribute att = attributes.get(name);
|
||||
return att!=null ? (String) att.get() : null;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.jenkinsci.account;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Configuration of the application that needs to be set outside the application.
|
||||
*
|
||||
@ -32,4 +34,12 @@ public interface Parameters {
|
||||
* File that activates a circuit breaker, a temporary shutdown of a sign-up service.
|
||||
*/
|
||||
String circuitBreakerFile();
|
||||
|
||||
String electionCandidates();
|
||||
|
||||
String electionLogDir();
|
||||
|
||||
String electionOpen();
|
||||
|
||||
String electionClose();
|
||||
}
|
||||
|
@ -1,22 +1,39 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Administration">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
|
||||
<p>
|
||||
Confirm creating the following user
|
||||
</p>
|
||||
<form method="post" action="doSignup">
|
||||
<h5>User ID</h5>
|
||||
<input type="text" name="userid" value="${request.getParameter('userId')}" class="text" />
|
||||
<div class="form-group">
|
||||
<label>User ID</label>
|
||||
<input type="text" name="userid" value="${request.getParameter('userId')}" class="form-control text" placeholder="Userid" />
|
||||
</div>
|
||||
|
||||
<h5>First Name</h5>
|
||||
<input type="text" name="firstName" value="${request.getParameter('firstName')}" class="text"/>
|
||||
<div class="form-group">
|
||||
<label>First Name</label>
|
||||
<input type="text" name="firstName" value="${request.getParameter('firstName')}" class="form-control text" placeholder="First Name" />
|
||||
</div>
|
||||
|
||||
<h5>Last Name</h5>
|
||||
<input type="text" name="lastName" value="${request.getParameter('lastName')}" class="text"/>
|
||||
<div class="form-group">
|
||||
<label>Last Name</label>
|
||||
<input type="text" name="lastName" value="${request.getParameter('lastName')}" class="form-control text" placeholder="Last Name"/>
|
||||
</div>
|
||||
|
||||
<h5>E-mail</h5>
|
||||
<input type="text" name="email" value="${request.getParameter('email')}" class="text"/>
|
||||
<div class="form-group">
|
||||
<label>E-mail</label>
|
||||
<input type="email" name="email" value="${request.getParameter('email')}" class="form-control text" placeholder="Last Name"/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-default">Sign Up</button>
|
||||
|
||||
<input type="submit" style="margin-top:2em; display:block"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</t:layout>
|
||||
</j:jelly>
|
||||
|
@ -1,31 +1,18 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Account self-service app">
|
||||
<div style="width: 100%;">
|
||||
<p>
|
||||
You can create/manage your user account that you use for accessing
|
||||
<a href="http://wiki.jenkins-ci.org/" target="_top">Wiki</a> and <a href="http://issues.jenkins-ci.org/" target="_top">JIRA</a>,
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#account-menu H1 {
|
||||
margin: 1rem;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
You can create/manage your user account that you use for accessing
|
||||
<a href="http://wiki.jenkins-ci.org/" target="_top">Wiki</a> and <a href="http://issues.jenkins-ci.org/" target="_top">JIRA</a>,
|
||||
</p>
|
||||
|
||||
<div id="account-menu">
|
||||
<h1><a href="signup">Create a new account</a></h1>
|
||||
|
||||
<h1><a href="passwordReset">Reset the password</a></h1>
|
||||
|
||||
<h1><a href="myself/">Update your profile</a></h1>
|
||||
|
||||
<j:if test="${it.isLoggedIn()}">
|
||||
<h1><a href="logout">Logout</a></h1>
|
||||
</j:if>
|
||||
|
||||
<j:if test="${it.isAdmin()}">
|
||||
<h1><a href="admin/">Administration</a></h1>
|
||||
<j:if test="${not it.isLoggedIn()}">
|
||||
<script type="text/javascript">
|
||||
window.location.href = "login"
|
||||
</script>
|
||||
<h1><a href="login">Login</a></h1>
|
||||
<h1><a href="signup">Create a new account</a></h1>
|
||||
<h1><a href="passwordReset">Reset the password</a></h1>
|
||||
</j:if>
|
||||
</div>
|
||||
</t:layout>
|
||||
|
@ -1,25 +1,35 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Login">
|
||||
<h1>Login</h1>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h1 class="text-center">Login</h1>
|
||||
|
||||
<form method="post" action="doLogin">
|
||||
<h5>User ID</h5>
|
||||
<input type="text" name="userid" class="text" id="userid"/>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="userid">Login</label>
|
||||
<input type="text" name="userid" class="form-control text" id="userid" placeholder="Userid"/>
|
||||
</div>
|
||||
|
||||
<h5>Password</h5>
|
||||
<input type="password" name="password" class="text"/>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="login_password">Password</label>
|
||||
<input type="password" id="login_password" name="password" placeholder="Password" class="form-control text"/>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="from" value="${request.getParameter('from')}"/>
|
||||
|
||||
<input type="submit" style="margin-top:2em; display:block"/>
|
||||
<button type="submit" class="btn btn-default btn-lg btn-block">Login</button>
|
||||
</form>
|
||||
|
||||
(<a href="signup">Want to sign up?</a> or <a href="passwordReset">forgot the password?</a>)
|
||||
<small><a href="signup">Sign up?</a> - <a href="passwordReset">Forgot password</a></small>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.getElementById('userid').focus();
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
</div>
|
||||
</div>
|
||||
</t:layout>
|
||||
</j:jelly>
|
||||
|
@ -1,21 +1,30 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Reset your password">
|
||||
<h1>Reset your password</h1>
|
||||
<t:layout title="Reset password">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
|
||||
<h1>Reset password</h1>
|
||||
|
||||
<form method="post" action="doPasswordReset">
|
||||
<h5>User ID</h5>
|
||||
<input type="text" name="id" class="text"/>
|
||||
<div class="form-group">
|
||||
<label class="sr-only">Email</label>
|
||||
<input type="text" name="id" class="form-control text" placeholder="UserId or Email"/>
|
||||
</div>
|
||||
|
||||
<p class="description">
|
||||
You can also specify your e-mail address that you've registered with us.
|
||||
(Except for those accounts that are migrated from java.net, whose e-mail address is set to ID@java.net,
|
||||
and not your real e-mail address.)
|
||||
</p>
|
||||
|
||||
<input type="submit" value="Send me a new password via e-mail" style="margin-top:2em; display:block"/>
|
||||
<button type="submit" class="btn btn-default btn-lg btn-block">Reset password</button>
|
||||
|
||||
<p>
|
||||
If you can't figure this out, contact us to get your account recovered.
|
||||
</p>
|
||||
<small>If you can't figure this out, contact us to get your account recovered.</small>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</t:layout>
|
||||
</j:jelly>
|
||||
|
@ -1,38 +1,56 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Sign up">
|
||||
<h1>Sign up</h1>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 block-center">
|
||||
|
||||
<h1 class="text-center" >Sign up</h1>
|
||||
|
||||
<form method="post" action="doSignup">
|
||||
<h5>User ID</h5>
|
||||
<input type="text" name="userid" class="text"/>
|
||||
<p class="description">
|
||||
Only alphabets, numbers, and '_' is allowed.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="userid">User ID</label>
|
||||
<input type="text" name="userid" id="userid" class="form-control text" placeholder="Userid"/>
|
||||
<span id="helpBlock" class="help-block text-center">
|
||||
Only alphabets, numbers, and '_' is allowed.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h5>First Name</h5>
|
||||
<input type="text" name="firstName" class="text"/>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="firstname">First Name</label>
|
||||
<input type="text" name="firstName" id="firstname" class="form-control text" placeholder="First Name"/>
|
||||
</div>
|
||||
|
||||
<h5>Last Name</h5>
|
||||
<input type="text" name="lastName" class="text"/>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="lastname" >Last Name</label>
|
||||
<input type="text" id='lastname' name="lastName" class="form-control text" placeholder="Last Name"/>
|
||||
</div>
|
||||
|
||||
<h5>E-mail</h5>
|
||||
<input type="text" name="email" class="text"/>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="email">E-mail</label>
|
||||
<input type="email" name="email" id="email" class="form-control text" placeholder="Email"/>
|
||||
</div>
|
||||
|
||||
<h5>What do you use Jenkins for?</h5>
|
||||
<input type="text" name="usedFor" class="text"/>
|
||||
<input id="hp" type="text" name="hp"/>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="usedFor">Usage</label>
|
||||
<input type="text" name="usedFor" id="usedfor" class="form-control text" placeholder="What do you use Jenkins for?"/>
|
||||
</div>
|
||||
|
||||
<input id="hp" type="text" name="hp"/>
|
||||
<script>
|
||||
<![CDATA[
|
||||
document.getElementById("hp").style.display = "none";
|
||||
]]>
|
||||
<![CDATA[document.getElementById("hp").style.display = "none";]]>
|
||||
</script>
|
||||
|
||||
<j:if test="${it.captchaPublicKey()!=null}">
|
||||
<h5>Captcha</h5>
|
||||
<label class="sr-only" for="captcha">Captcha</label>
|
||||
<script src="https://www.google.com/recaptcha/api.js" async="true" defer="true"></script>
|
||||
<div class="g-recaptcha" data-sitekey="${it.captchaPublicKey()}"></div>
|
||||
<div class="g-recaptcha" id="captcha" data-sitekey="${it.captchaPublicKey()}"></div>
|
||||
</j:if>
|
||||
<br/>
|
||||
<input type="submit" style="margin-top:2em; display:block"/>
|
||||
<button type="submit" class="btn btn-default btn-lg btn-block">Sign Up</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</t:layout>
|
||||
</j:jelly>
|
||||
|
@ -0,0 +1,19 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Board Election">
|
||||
|
||||
<h1>Candidates</h1>
|
||||
|
||||
<ul>
|
||||
<j:forEach var="c" items="${it.candidates}">
|
||||
<li><img src="${c.avatar}"/> ${c.firstName} ${c.lastName} (<a href="https://wiki.jenkins-ci.org/display/~${c.userId}">${c.userId}</a>)</li>
|
||||
</j:forEach>
|
||||
</ul>
|
||||
|
||||
<form method="post" action="addCandidate">
|
||||
<h4>Add candidates by entering user ID</h4>
|
||||
<input type="text" name="userId" placeholed="userId"/>
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
|
||||
</t:layout>
|
||||
</j:jelly>
|
@ -0,0 +1,5 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Board Election">
|
||||
<h1>Your vote has been recorded, thanks!</h1>
|
||||
</t:layout>
|
||||
</j:jelly>
|
@ -0,0 +1,49 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Board Election">
|
||||
<j:choose>
|
||||
<j:when test="${it.open}">
|
||||
|
||||
<h1>Board Election</h1>
|
||||
<p class="help-block"> Use drag and drop to order candidates by your preference for <a href="https://wiki.jenkins-ci.org/display/JENKINS/Board+Election+Process">Board election</a>. </p>
|
||||
|
||||
<script>
|
||||
$( function() {
|
||||
$( "#sortable" ).sortable();
|
||||
$( "#sortable" ).disableSelection();
|
||||
} );
|
||||
|
||||
function vote() {
|
||||
var vote = $.map( $("#sortable > li"), function (element) {
|
||||
return element.id;
|
||||
}).join(",");
|
||||
$("#vote_form > input").val(vote);
|
||||
$("#vote_form").submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
<form method="post" action="vote" id="vote_form" >
|
||||
|
||||
<ul id="sortable" class="list-group">
|
||||
<j:forEach var="c" indexVar="i" items="${it.candidates}" >
|
||||
<li id="${i}" class="list-group-item text-center text-capitalize" >
|
||||
<span class="pull-left fa fa-arrows-v" aria-hidden="true"></span>
|
||||
${c}
|
||||
<span onclick="this.parentElement.remove()" class="pull-right fa fa-times"></span>
|
||||
</li>
|
||||
</j:forEach>
|
||||
</ul>
|
||||
|
||||
<input type="hidden" name="vote"/>
|
||||
</form>
|
||||
<button onclick="vote()" class="btn btn-default btn-lg pull-center center-block">Vote</button>
|
||||
|
||||
</j:when>
|
||||
<j:otherwise>
|
||||
|
||||
<h1>Board election is closed.</h1>
|
||||
|
||||
</j:otherwise>
|
||||
</j:choose>
|
||||
</t:layout>
|
||||
|
||||
</j:jelly>
|
@ -1,45 +1,60 @@
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:t="/org/jenkinsci/account/taglib">
|
||||
<t:layout title="Your Profile">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h1>Your Profile</h1>
|
||||
|
||||
<form method="post" action="update">
|
||||
<h5>User ID</h5>
|
||||
<input type="text" readonly="true" value="${it.userId}" class="text" disabled="true"/>
|
||||
<div class="form-group">
|
||||
<label>User ID</label>
|
||||
<input type="text" readonly="true" value="${it.userId}" class="form-control text" disabled="true"/>
|
||||
</div>
|
||||
|
||||
<h5>First Name</h5>
|
||||
<input type="text" name="firstName" value="${it.firstName}" class="text"/>
|
||||
<div class="form-group">
|
||||
<label>First Name</label>
|
||||
<input type="text" name="firstName" value="${it.firstName}" class="form-control text"/>
|
||||
</div>
|
||||
|
||||
<h5>Last Name</h5>
|
||||
<input type="text" name="lastName" value="${it.lastName}" class="text"/>
|
||||
<div class="form-group">
|
||||
<label>Last Name</label>
|
||||
<input type="text" name="lastName" value="${it.lastName}" class="form-control text"/>
|
||||
</div>
|
||||
|
||||
<h5>E-mail</h5>
|
||||
<input type="text" name="email" value="${it.email}" class="text"/>
|
||||
<div class="form-group">
|
||||
<label>E-mail</label>
|
||||
<input type="text" name="email" value="${it.email}" class="form-control text"/>
|
||||
</div>
|
||||
|
||||
<h5>GitHub ID</h5>
|
||||
<input type="text" name="githubId" value="${it.githubId}" class="text"/>
|
||||
<div class="form-group">
|
||||
<label>GitHub ID</label>
|
||||
<input type="text" name="githubId" value="${it.githubId}" class="form-control text"/>
|
||||
</div>
|
||||
|
||||
<h5>SSH Public Keys</h5>
|
||||
<textarea name="sshKeys" style="width:80%; height:10em;">${it.sshKeys}</textarea>
|
||||
<div class="form-group">
|
||||
<label>SSH Public Keys</label>
|
||||
<textarea class="form-control" rows="3">${it.sshKeys}</textarea>
|
||||
</div>
|
||||
|
||||
<div style="height:2em"></div>
|
||||
<fieldset style="width:25em">
|
||||
<legend>Change Password</legend>
|
||||
<p class="description">
|
||||
To update your password, please type your current password as well as new one for security.
|
||||
Leave this empty to keep the current password.
|
||||
</p>
|
||||
<legend>Change Password</legend>
|
||||
<p class="description">
|
||||
To update your password, please type your current password as well as new one for security.
|
||||
Leave this empty to keep the current password.
|
||||
</p>
|
||||
|
||||
<h5>Current Password</h5>
|
||||
<input type="password" name="password" value="" class="text"/>
|
||||
<label class="sr-only">Current Password</label>
|
||||
<input type="password" name="password" value="" class="form-control text" placeholder="Current Password"/>
|
||||
|
||||
<h5>New Password</h5>
|
||||
<input type="password" name="newPassword1" class="text"/>
|
||||
<label class="sr-only">New Password</label>
|
||||
<input type="password" name="newPassword1" class="form-control text" placeholder="New Password"/>
|
||||
|
||||
<h5>Confirm New Password</h5>
|
||||
<input type="password" name="newPassword2" class="text"/>
|
||||
</fieldset>
|
||||
<label class="sr-only">Confirm New Password</label>
|
||||
<input type="password" name="newPassword2" class="form-control text" placeholder="Confirm new password"/>
|
||||
|
||||
<input type="submit" style="margin-top:2em; display:block"/>
|
||||
<button type="submit" class="btn btn-default btn-lg btn-block">Update</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
</div>
|
||||
</div>
|
||||
</t:layout>
|
||||
</j:jelly>
|
||||
|
@ -4,7 +4,7 @@
|
||||
</st:documentation>
|
||||
<st:contentType value="text/html;charset=UTF-8" />
|
||||
|
||||
<html>
|
||||
<html style="height: 100%;">
|
||||
<head>
|
||||
<title>
|
||||
${attrs.title} | Jenkins
|
||||
@ -33,6 +33,7 @@
|
||||
<meta content='${attrs.title}' property='og:title'/>
|
||||
<meta content='article' property='og:type'/>
|
||||
<meta content='Jenkins – Continuous Delivery for every team' property='og:description'/>
|
||||
<link href='/webjars/fontawesome/4.7.0/css/font-awesome.min.css' media='screen' rel='stylesheet'/>
|
||||
<link href='https://jenkins.io/assets/bower/bootstrap/css/bootstrap.min.css' media='screen' rel='stylesheet'/>
|
||||
<link href='https://jenkins.io/assets/bower/tether/css/tether.min.css' media='screen' rel='stylesheet'/>
|
||||
<link href='https://jenkins.io/css/font-icons.css' media='screen' rel='stylesheet'/>
|
||||
@ -40,10 +41,10 @@
|
||||
<!-- Non-obtrusive CSS styles -->
|
||||
<link href='https://jenkins.io/assets/bower/ionicons/css/ionicons.min.css' media='screen' rel='stylesheet'/>
|
||||
<link href='https://jenkins.io/css/footer.css' media='screen' rel='stylesheet'/>
|
||||
<link href='https://jenkins.io/css/font-awesome.min.css' media='screen' rel='stylesheet'/>
|
||||
<script src="/webjars/jquery/3.2.0/jquery.js"></script>
|
||||
<script src="/webjars/jquery-ui/1.12.1/jquery-ui.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src='https://jenkins.io/assets/bower/jquery/jquery.js'></script>
|
||||
<body style="height: 100%;">
|
||||
<!-- starting partial toptoolbar.html.haml -->
|
||||
<nav class='navbar navbar-toggleable-md navbar-inverse top bg-inverse fixed-top' id='ji-toolbar'>
|
||||
<div class='container'>
|
||||
@ -183,12 +184,32 @@ Download
|
||||
<!-- ending partial toptoolbar.html.haml -->
|
||||
|
||||
|
||||
<div class='container'>
|
||||
<div class='row'>
|
||||
<div class="col-md-12">
|
||||
<d:invokeBody />
|
||||
</div>
|
||||
<div class='container' style="height: 60%;overflow: auto;">
|
||||
<div class='row'>
|
||||
<div class="col-sm-3 col-md-3">
|
||||
<div class='sidebar-nav tour'>
|
||||
<j:if test="${app.isLoggedIn() or it.isLoggedIn()}">
|
||||
<h4>Account</h4>
|
||||
<ul>
|
||||
<li><a href="/" class="active">Home</a></li>
|
||||
|
||||
<j:if test="${it.isAdmin() or app.isAdmin()}">
|
||||
<li><a href="/admin">Administration</a></li>
|
||||
</j:if>
|
||||
|
||||
<li><a href="/election">Board Election</a></li>
|
||||
<li><a href="/myself">Profile</a></li>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</j:if>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9 col-md-offset-3">
|
||||
<div class="main">
|
||||
<d:invokeBody />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -17,6 +17,16 @@
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!--Webjars Servlet-->
|
||||
<servlet>
|
||||
<servlet-name>WebjarsServlet</servlet-name>
|
||||
<servlet-class>org.webjars.servlet.WebjarsServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>WebjarsServlet</servlet-name>
|
||||
<url-pattern>/webjars/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<listener>
|
||||
<listener-class>org.jenkinsci.account.WebAppMain</listener-class>
|
||||
</listener>
|
||||
|
Loading…
Reference in New Issue
Block a user