From 90670e05c17b34270cda9436eaf877cc72a341ec Mon Sep 17 00:00:00 2001 From: Thorsten Scherler Date: Sun, 17 Jun 2018 18:02:37 +0200 Subject: [PATCH] [JENKINS-50447] New design and slim down of login page (#3380) * [JENKINS-50447] New design and slim down of login page The change will provide a new designed login page that is using no external deps to prevent any possible malvious javascript. We still allow the user to use the legacy login page, which has not changed the behaviour. [JENKINS-50447] Implement error case and use final svg jenkins logo [JENKINS-50447] remove mock up logo [JENKINS-50447] Fix feet back from jlong [JENKINS-50447_squash] Use theme css which only contains values to be overriden. Drop ie js since with the theme css it is not needed anymore * [JENKINS-50447_squash] Fix expression to evaluate when to show an error and when to simply redirect * eslint - formating changes and fix offences * [JENKINS-50447_squash] Use system property to controll whether or not to use the legacy login * [JENKINS-50447_squash] prevent that the jsessionId appears in the url which should fix most tests * [JENKINS-50447_squash] Fix cli test by adding pageDecorators magic from layout.jelly * [JENKINS-50447_squash] remove pageDecorators because they assume existence of js that we do not have * [JENKINS-50447] New design and slim down of login page The change will provide a new designed login page that is using no external deps to prevent any possible malvious javascript. We still allow the user to use the legacy login page, which has not changed the behaviour. [JENKINS-50447] Implement error case and use final svg jenkins logo [JENKINS-50447] remove mock up logo [JENKINS-50447] Fix feet back from jlong [JENKINS-50447_squash] Use theme css which only contains values to be overriden. Drop ie js since with the theme css it is not needed anymore [JENKINS-50447_squash] Fix expression to evaluate when to show an error and when to simply redirect eslint - formating changes and fix offences [JENKINS-50447_squash] Use system property to controll whether or not to use the legacy login [JENKINS-50447_squash] prevent that the jsessionId appears in the url which should fix most tests [JENKINS-50447_squash] Fix cli test by adding pageDecorators magic from layout.jelly [JENKINS-50447_squash] remove pageDecorators because they assume existence of js that we do not have [JENKINS-50447_squash] make login page extensible with pagedecorators [JENKINS-50447_squash] drop legacy support * Make theme customisable by using a magic package for css and header. Limiting the implemetation of those to one. * [JENKINS-50447_squash] Creating a specific decorator for login which only returns one instance * [JENKINS-50447_squash] using rounded corner * JENKINS-50447 remove legacy code * [JENKINS-50447_squash] Fix comments from @amuniz about javadoc and uniqueness of css for login * [JENKINS-50447_squash] Fix click on label span to toggle the select of the checkbox * [JENKINS-50447_squash] fix comments from @daniel-beck * [JENKINS-50447_squash] Fix license headers * [JENKINS-50447_squash] Implement loading/restarting and signup with same design * [JENKINS-50447_squash] Implement the loading/restarting view and signup with same design as the login page * [JENKINS-50447_squash] Remove limitation that only one validation error can be shown at a time * [JENKINS-50447_squash] implement password strength checker * [JENKINS-50447_squash] Fix different comments from @daniel-beck regarding titles. Further enhanced spacing of label and input * [JENKINS-50447_squash] Fix restart screen css. Fix comments regarding signup page. * [JENKINS-50447_squash] fix tabindex flow * harmonize design - of checkboxes using jdl styles. - distance of headers * [JENKINS-50447] Fix comments from @daniel-beck - fix some accessibility issues -- html has now a lang tag -- define main region - fix missplaced } - fix when server down for real that we still show the loading page * [JENKINS-50447_squash] should never ended up here --- core/src/main/java/hudson/Functions.java | 12 +- .../security/HudsonPrivateSecurityRealm.java | 66 +++-- .../model/DefaultSimplePageDecorator.java | 36 +++ .../jenkins/model/SimplePageDecorator.java | 72 +++++ .../HudsonPrivateSecurityRealm/signup.jelly | 273 +++++++++++++++++- .../hudson/util/HudsonIsLoading/index.jelly | 53 ++-- .../util/HudsonIsRestarting/index.jelly | 55 ++-- .../simple-head.jelly | 29 ++ .../simple-header.jelly | 30 ++ .../jenkins/model/Jenkins/login.jelly | 180 ++++++++---- .../jenkins/model/Jenkins/login.properties | 4 +- .../jenkins/model/Jenkins/loginError.jelly | 25 +- war/src/main/webapp/css/loading.css | 86 ++++++ war/src/main/webapp/css/signup.css | 56 ++++ war/src/main/webapp/css/simple-page-forms.css | 79 +++++ war/src/main/webapp/css/simple-page.css | 135 +++++++++ war/src/main/webapp/css/simple-page.theme.css | 66 +++++ war/src/main/webapp/images/jenkins.svg | 1 + war/src/main/webapp/scripts/loading.js | 60 ++++ 19 files changed, 1175 insertions(+), 143 deletions(-) create mode 100644 core/src/main/java/jenkins/model/DefaultSimplePageDecorator.java create mode 100644 core/src/main/java/jenkins/model/SimplePageDecorator.java create mode 100644 core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-head.jelly create mode 100644 core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-header.jelly create mode 100644 war/src/main/webapp/css/loading.css create mode 100644 war/src/main/webapp/css/signup.css create mode 100644 war/src/main/webapp/css/simple-page-forms.css create mode 100644 war/src/main/webapp/css/simple-page.css create mode 100644 war/src/main/webapp/css/simple-page.theme.css create mode 100644 war/src/main/webapp/images/jenkins.svg create mode 100644 war/src/main/webapp/scripts/loading.js diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index d416b61095..9a0b27ce90 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -47,6 +47,7 @@ import hudson.model.JobPropertyDescriptor; import hudson.model.ModelObject; import hudson.model.Node; import hudson.model.PageDecorator; +import jenkins.model.SimplePageDecorator; import hudson.model.PaneStatusProperties; import hudson.model.ParameterDefinition; import hudson.model.ParameterDefinition.ParameterDescriptor; @@ -133,8 +134,6 @@ import jenkins.model.Jenkins; import jenkins.model.ModelObjectWithChildren; import jenkins.model.ModelObjectWithContextMenu; -import org.acegisecurity.Authentication; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.apache.commons.jelly.JellyContext; import org.apache.commons.jelly.JellyTagException; import org.apache.commons.jelly.Script; @@ -1772,7 +1771,14 @@ public class Functions { if(Jenkins.getInstanceOrNull()==null) return Collections.emptyList(); return PageDecorator.all(); } - + /** + * Gets only one {@link SimplePageDecorator}. + * @since FIXME + */ + public static SimplePageDecorator getSimplePageDecorator() { + return SimplePageDecorator.first(); + } + public static List> getCloudDescriptors() { return Cloud.all(); } diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index 389517c577..cf6c94e085 100644 --- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -78,11 +78,7 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import java.io.IOException; import java.lang.reflect.Constructor; import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.MissingResourceException; -import java.util.ResourceBundle; +import java.util.*; import java.util.logging.Logger; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -312,13 +308,22 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea public User createAccountFromSetupWizard(StaplerRequest req) throws IOException, AccountCreationFailedException { checkPermission(Jenkins.ADMINISTER); SignupInfo si = validateAccountCreationForm(req, false); - if (si.errorMessage != null) { - throw new AccountCreationFailedException(si.errorMessage); + if (!si.errors.isEmpty()) { + String messages = getErrorMessages(si); + throw new AccountCreationFailedException(messages); } else { return createAccount(si); } } + private String getErrorMessages(SignupInfo si) { + StringBuilder messages = new StringBuilder(); + for (String message : si.errors.values()) { + messages.append(message).append(" | "); + } + return messages.toString(); + } + /** * Creates a first admin user account. * @@ -363,7 +368,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea private User createAccount(StaplerRequest req, StaplerResponse rsp, boolean validateCaptcha, String formView) throws ServletException, IOException { SignupInfo si = validateAccountCreationForm(req, validateCaptcha); - if (si.errorMessage != null) { + if (!si.errors.isEmpty()) { // failed. ask the user to try again. req.getView(this, formView).forward(req, rsp); return null; @@ -377,7 +382,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea * @param validateCaptcha whether to attempt to validate a captcha in the request * * @return a {@link SignupInfo#SignupInfo(StaplerRequest) SignupInfo from given request}, with {@link - * SignupInfo#errorMessage} set to a non-null value if any of the supported fields are invalid + * SignupInfo#errors} set to a non-null value if any of the supported fields are invalid */ private SignupInfo validateAccountCreationForm(StaplerRequest req, boolean validateCaptcha) { // form field validation @@ -385,24 +390,16 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea SignupInfo si = new SignupInfo(req); if (validateCaptcha && !validateCaptcha(si.captcha)) { - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_TextNotMatchWordInImage(); - } - - if (si.password1 != null && !si.password1.equals(si.password2)) { - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordNotMatch(); - } - - if (!(si.password1 != null && si.password1.length() != 0)) { - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordRequired(); + si.errors.put("captcha", Messages.HudsonPrivateSecurityRealm_CreateAccount_TextNotMatchWordInImage()); } if (si.username == null || si.username.length() == 0) { - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameRequired(); + si.errors.put("username", Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameRequired()); } else if(!containsOnlyAcceptableCharacters(si.username)) { if (ID_REGEX == null) { - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameInvalidCharacters(); + si.errors.put("username", Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameInvalidCharacters()); } else { - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameInvalidCharactersCustom(ID_REGEX); + si.errors.put("username", Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameInvalidCharactersCustom(ID_REGEX)); } } else { // do not create the user - we just want to check if the user already exists but is not a "login" user. @@ -410,7 +407,15 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea if (null != user) // Allow sign up. SCM people has no such property. if (user.getProperty(Details.class) != null) - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameAlreadyTaken(); + si.errors.put("username", Messages.HudsonPrivateSecurityRealm_CreateAccount_UserNameAlreadyTaken()); + } + + if (si.password1 != null && !si.password1.equals(si.password2)) { + si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordNotMatch()); + } + + if (!(si.password1 != null && si.password1.length() != 0)) { + si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordRequired()); } if (si.fullname == null || si.fullname.length() == 0) { @@ -418,31 +423,32 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea } if (isMailerPluginPresent() && (si.email == null || !si.email.contains("@"))) { - si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_InvalidEmailAddress(); + si.errors.put("email", Messages.HudsonPrivateSecurityRealm_CreateAccount_InvalidEmailAddress()); } if (!User.isIdOrFullnameAllowed(si.username)) { - si.errorMessage = hudson.model.Messages.User_IllegalUsername(si.username); + si.errors.put("username", hudson.model.Messages.User_IllegalUsername(si.username)); } if (!User.isIdOrFullnameAllowed(si.fullname)) { - si.errorMessage = hudson.model.Messages.User_IllegalFullname(si.fullname); + si.errors.put("fullname", hudson.model.Messages.User_IllegalFullname(si.fullname)); } req.setAttribute("data", si); // for error messages in the view return si; } /** - * Creates a new account from a valid signup info. A signup info is valid if its {@link SignupInfo#errorMessage} - * field is null. + * Creates a new account from a valid signup info. A signup info is valid if its {@link SignupInfo#errors} + * field is empty. * * @param si the valid signup info to create an account from * @return a valid {@link User} object created from given signup info * @throws IllegalArgumentException if an invalid signup info is passed */ private User createAccount(SignupInfo si) throws IOException { - if (si.errorMessage != null) { - throw new IllegalArgumentException("invalid signup info passed to createAccount(si): " + si.errorMessage); + if (!si.errors.isEmpty()) { + String messages = getErrorMessages(si); + throw new IllegalArgumentException("invalid signup info passed to createAccount(si): " + messages); } // register the user User user = createAccount(si.username, si.password1); @@ -541,6 +547,8 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea */ public String errorMessage; + public HashMap errors = new HashMap(); + public SignupInfo() { } diff --git a/core/src/main/java/jenkins/model/DefaultSimplePageDecorator.java b/core/src/main/java/jenkins/model/DefaultSimplePageDecorator.java new file mode 100644 index 0000000000..6a4a9278a3 --- /dev/null +++ b/core/src/main/java/jenkins/model/DefaultSimplePageDecorator.java @@ -0,0 +1,36 @@ +/* + * The MIT License + * + * Copyright 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.model; + +import hudson.Extension; + +/** + * In case there are no other implementations we will fallback to this implementation. + * + * To make sure that we load this extension last (or at least very late) we use a negative ordinal. + * This allows custom implementation to be "first" + */ +@Extension(ordinal=-9999) +public class DefaultSimplePageDecorator extends SimplePageDecorator { +} diff --git a/core/src/main/java/jenkins/model/SimplePageDecorator.java b/core/src/main/java/jenkins/model/SimplePageDecorator.java new file mode 100644 index 0000000000..866fc69925 --- /dev/null +++ b/core/src/main/java/jenkins/model/SimplePageDecorator.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * + * Copyright 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package jenkins.model; + +import hudson.DescriptorExtensionList; +import hudson.ExtensionPoint; +import hudson.model.Describable; +import hudson.model.Descriptor; +/** + * Participates in the rendering of the login page + * + *

+ * This class provides a few hooks to augment the HTML of the login page. + * + * @since TODO + */ +public class SimplePageDecorator extends Descriptor implements ExtensionPoint, Describable { + + protected SimplePageDecorator() { + super(self()); + } + + @Override + public final Descriptor getDescriptor() { + return this; + } + /** + * Obtains the URL of this object, excluding the context path. + * + *

+ * Every {@link SimplePageDecorator} is bound to URL via {@link Jenkins#getDescriptor()}. + * This method returns such an URL. + */ + public final String getUrl() { + return "descriptor/"+clazz.getName(); + } + + /** + * The first found LoginDecarator, there can only be one. + * @return the first found {@link SimplePageDecorator} + */ + public static SimplePageDecorator first(){ + DescriptorExtensionList descriptorList = Jenkins.getInstanceOrNull().getDescriptorList(SimplePageDecorator.class); + if (descriptorList.size() >= 1) { + return descriptorList.get(0); + } else { + return null; + } + } + +} diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signup.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signup.jelly index f2477da1df..6312f5b8b4 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signup.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signup.jelly @@ -26,6 +26,275 @@ THE SOFTWARE. User self sign up page. --> - - + + + + + + + + + + + ${h.initPageVariables(context)} + + + + + + + ${%Create an account! [Jenkins]} + + + + + + + +

+ +
+ + diff --git a/core/src/main/resources/hudson/util/HudsonIsLoading/index.jelly b/core/src/main/resources/hudson/util/HudsonIsLoading/index.jelly index 625be9422f..75157ee49a 100644 --- a/core/src/main/resources/hudson/util/HudsonIsLoading/index.jelly +++ b/core/src/main/resources/hudson/util/HudsonIsLoading/index.jelly @@ -23,22 +23,41 @@ THE SOFTWARE. --> - - - - - - - - -

- ${%Please wait while Jenkins is getting ready to work}... -

-

- ${%Your browser will reload automatically when Jenkins is ready.} -

- -
-
+ + + + + + + + + + + ${%Starting Jenkins} + + + + + + + +
+ +
+ + + +
\ No newline at end of file diff --git a/core/src/main/resources/hudson/util/HudsonIsRestarting/index.jelly b/core/src/main/resources/hudson/util/HudsonIsRestarting/index.jelly index 65e8bb6e45..aafd2cfc60 100644 --- a/core/src/main/resources/hudson/util/HudsonIsRestarting/index.jelly +++ b/core/src/main/resources/hudson/util/HudsonIsRestarting/index.jelly @@ -23,22 +23,41 @@ THE SOFTWARE. --> - - - - - - - - -

- ${%Please wait while Jenkins is restarting}... -

-

- ${%Your browser will reload automatically when Jenkins is ready.} -

- -
-
-
\ No newline at end of file + + + + + + + + + + + ${%Restarting Jenkins} + + + + + + + +
+ +
+ + + + +
diff --git a/core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-head.jelly b/core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-head.jelly new file mode 100644 index 0000000000..c8ff1fb711 --- /dev/null +++ b/core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-head.jelly @@ -0,0 +1,29 @@ + + + + + + + diff --git a/core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-header.jelly b/core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-header.jelly new file mode 100644 index 0000000000..59c61a3c2c --- /dev/null +++ b/core/src/main/resources/jenkins/model/DefaultSimplePageDecorator/simple-header.jelly @@ -0,0 +1,30 @@ + + + +
+ +

${%Welcome to Jenkins!}

+
+
diff --git a/core/src/main/resources/jenkins/model/Jenkins/login.jelly b/core/src/main/resources/jenkins/model/Jenkins/login.jelly index cb4ed08319..3276493af8 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/login.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/login.jelly @@ -23,58 +23,136 @@ THE SOFTWARE. --> - - - - - - - - - - - -
- -
- - - - - - - - - - - - - - - - -
${%User}:
${%Password}:
- - - - + + + + + + + + ${h.advertiseHeaders(response)} + + + + + + + + + + ${h.initPageVariables(context)} + + + + + + + + + + ${%signIn} [Jenkins] + + + + + + + + + + + +
+ - -
- - -
-
+ + + +
diff --git a/core/src/main/resources/jenkins/model/Jenkins/login.properties b/core/src/main/resources/jenkins/model/Jenkins/login.properties index d297fd6dc9..1cb864cfbb 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/login.properties +++ b/core/src/main/resources/jenkins/model/Jenkins/login.properties @@ -20,5 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -signUp=Create an account if you are not a member yet. +signUp=Please sign in below or create an account. login=log in +signIn=Sign in +loginWelcome=Welcome to Jenkins! \ No newline at end of file diff --git a/core/src/main/resources/jenkins/model/Jenkins/loginError.jelly b/core/src/main/resources/jenkins/model/Jenkins/loginError.jelly index a5a9549aca..0e178b2df8 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/loginError.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/loginError.jelly @@ -24,7 +24,7 @@ THE SOFTWARE. - + @@ -32,28 +32,9 @@ THE SOFTWARE. - - - - - - -
- ${%Invalid login information. Please try again.} -
- ${%Try again} -
-
-
- ${%If you are a system administrator and suspect this to be a configuration problem, see the server console output for more details.} -
-
-
-
+ +
diff --git a/war/src/main/webapp/css/loading.css b/war/src/main/webapp/css/loading.css new file mode 100644 index 0000000000..c6ed611c28 --- /dev/null +++ b/war/src/main/webapp/css/loading.css @@ -0,0 +1,86 @@ +/* + * The MIT License + * + * Copyright (c) 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +@keyframes blink { + /** + * At the start of the animation the dot + * has an opacity of .2 + */ + 0% { + opacity: .2; + } + /** + * At 20% the dot is fully visible and + * then fades out slowly + */ + 20% { + opacity: 1; + } + /** + * Until it reaches an opacity of .2 and + * the animation can start again + */ + 100% { + opacity: .2; + } +} +.loading span { + /** + * Use the blink animation, which is defined above + */ + animation-name: blink; + /** + * The animation should take 1.4 seconds + */ + animation-duration: 1.4s; + /** + * It will repeat itself forever + */ + animation-iteration-count: infinite; + /** + * This makes sure that the starting style (opacity: .2) + * of the animation is applied before the animation starts. + * Otherwise we would see a short flash or would have + * to set the default styling of the dots to the same + * as the animation. Same applies for the ending styles. + */ + animation-fill-mode: both; +} + +.loading span:nth-child(2) { + /** + * Starts the animation of the third dot + * with a delay of .2s, otherwise all dots + * would animate at the same time + */ + animation-delay: .2s; +} + +.loading span:nth-child(3) { + /** + * Starts the animation of the third dot + * with a delay of .4s, otherwise all dots + * would animate at the same time + */ + animation-delay: .4s; +} diff --git a/war/src/main/webapp/css/signup.css b/war/src/main/webapp/css/signup.css new file mode 100644 index 0000000000..17a7b58a38 --- /dev/null +++ b/war/src/main/webapp/css/signup.css @@ -0,0 +1,56 @@ +/* + * The MIT License + * + * Copyright (c) 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +.signup{ + min-width: 480px; + max-width: 480px; + width: 480px; +} +.signupIntroDefault { + text-align: center; +} +form.signup{ + min-width: 480px; + max-width: 480px; + width: 480px; + text-align: left; +} +.inputHeader { + display: flex; + justify-content: space-between; + align-items: center; +} +#showPassword { + width: unset; + margin-right: 5px; +} +.combinedElement{ + display: flex; + align-items: center; +} +label { + margin-bottom: 7px; +} +#passwordStrengthWrapper { + margin-top: 4px; +} \ No newline at end of file diff --git a/war/src/main/webapp/css/simple-page-forms.css b/war/src/main/webapp/css/simple-page-forms.css new file mode 100644 index 0000000000..cac4a03afd --- /dev/null +++ b/war/src/main/webapp/css/simple-page-forms.css @@ -0,0 +1,79 @@ +.Checkbox { + display: inline-block; +} +.Checkbox .svg-icon { + width: 20px; + height: 20px; +} +.Checkbox .Checkbox-wrapper { + display: flex; + align-items: center; +} +.Checkbox input[type="checkbox"] { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.Checkbox .Checkbox-indicator { + display: flex; + border: 1px solid rgba(73, 73, 73, 0.5); + border-radius: 3px; + cursor: pointer; +} +.Checkbox .Checkbox-text { + margin-left: 5px; + cursor: pointer; +} +.Checkbox .svg-icon { + opacity: 0; + fill: #fff; + transition: opacity 0.1s linear; +} +.Checkbox input[type="checkbox"]:checked + .Checkbox-indicator { + background-color: #4A90E2; + border-color: #4A90E2; +} +.Checkbox input[type="checkbox"]:checked + .Checkbox-indicator .svg-icon { + opacity: 1; +} +.Checkbox input[type="checkbox"]:focus + .Checkbox-indicator, +.Checkbox.focus .Checkbox-indicator { + outline: 0; + box-shadow: inset 0 0 2px 3px rgba(0, 0, 0, 0.2); +} +.Checkbox input[disabled] + .Checkbox-indicator, +fieldset[disabled] .Checkbox input + .Checkbox-indicator, +.Checkbox input[disabled] ~ .Checkbox-text, +fieldset[disabled] .Checkbox input ~ .Checkbox-text { + opacity: 0.4; + cursor: auto; +} +@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { + .Checkbox input[type="checkbox"]:focus + .Checkbox-indicator, + .Checkbox.focus .Checkbox-indicator { + box-shadow: inset 0 0 1px 3px rgba(0, 0, 0, 0.2); + } +} +@supports (-ms-ime-align:auto) { + .Checkbox input[type="checkbox"]:focus + .Checkbox-indicator, + .Checkbox.focus .Checkbox-indicator { + box-shadow: inset 0 0 1px 3px rgba(0, 0, 0, 0.2); + } +} +.Checkbox-small .svg-icon { + width: 16px; + height: 16px; +} +.Checkbox-medium .svg-icon { + width: 20px; + height: 20px; +} +.Checkbox-large .svg-icon { + width: 24px; + height: 24px; +} \ No newline at end of file diff --git a/war/src/main/webapp/css/simple-page.css b/war/src/main/webapp/css/simple-page.css new file mode 100644 index 0000000000..d269ceb791 --- /dev/null +++ b/war/src/main/webapp/css/simple-page.css @@ -0,0 +1,135 @@ +/* + * The MIT License + * + * Copyright (c) 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +html { + height: 100vh; + font-family: "LatoLatinWeb", "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 18px; +} + +.simple-page h1 { + font-size: 24px; +} + +.simple-page { + text-align: center; + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -moz-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: center; + -moz-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + min-height: 90vh; +} + +.simple-page form { + min-width: 300px; + max-width: 300px; + width: 300px; +} + +.simple-page input { + padding: 6px 8px; + display: inline-block; + text-align: left; + text-decoration: none; + font-size: 18px; + border-radius: 3px; + border: 1px solid rgba(74,74,74,0.25); +} + +.simple-page input[type=text], +.simple-page input[type=password], +.simple-page input[type=submit] { + width: 100%; + box-sizing: border-box; +} + +.simple-page .formRow { + margin-top: 27px; +} +.simple-page .formRow ~ .formRow { + margin-top: 16px; +} + +.simple-page input[type=submit] { + text-align: center; +} + +.simple-page form .submit input { + border-radius: 3px; + color: #fff; + cursor: pointer; +} + +.simple-page .alert { + text-align: center; + font-size: 14px; + line-height: 18px; +} + +.simple-page .Checkbox-wrapper { + justify-content: center; +} +.simple-page .login .Checkbox-wrapper { + margin-top: 20px; +} +#remember_me{ + width: unset; + margin-right: 5px; +} + +/* forms */ + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +.layout-small { + font-size: 12px; +} + +.layout-medium { + font-size: 14px; +} + +.layout-large { + font-size: 18px; +} \ No newline at end of file diff --git a/war/src/main/webapp/css/simple-page.theme.css b/war/src/main/webapp/css/simple-page.theme.css new file mode 100644 index 0000000000..af00024ceb --- /dev/null +++ b/war/src/main/webapp/css/simple-page.theme.css @@ -0,0 +1,66 @@ +/* + * The MIT License + * + * Copyright (c) 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/* + * IE11 prevents of the usage of variable, + * but to show the color theme we left it here + */ +:root { + --main-bg-color-focus: #2171ce; + --main-bg-color: #4A90E2; + --danger-color: #C4000A; +} + +html { + color: #4A4A4A; +} + +.simple-page .logo { + background-image: url('../images/jenkins.svg'); + background-repeat: no-repeat; + background-position: 50% 0; + height: 130px; +} + +.simple-page form .submit input:focus { + background-color: #2171ce; /*var(--main-bg-color-focus);*/ + border: solid 1px #2171ce; /*var(--main-bg-color-focus);*/ +} + +.simple-page form .submit input { + background-color: #4A90E2; /*var(--main-bg-color);*/ + border: solid 1px #4A90E2; /*var(--main-bg-color-focus);*/ +} + +.simple-page input.normal:focus { + border: solid 1px #2171ce; /*var(--main-bg-color-focus);*/ +} + + .simple-page .danger { + border: 1px solid #C4000A; /*var(--danger-color);*/ + box-shadow: 0 0 1px #C4000A; /*var(--danger-color);*/ +} + +.simple-page .alert { + color: #C4000A; /*var(--danger-color);*/ +} \ No newline at end of file diff --git a/war/src/main/webapp/images/jenkins.svg b/war/src/main/webapp/images/jenkins.svg new file mode 100644 index 0000000000..c9ee5d084a --- /dev/null +++ b/war/src/main/webapp/images/jenkins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/war/src/main/webapp/scripts/loading.js b/war/src/main/webapp/scripts/loading.js new file mode 100644 index 0000000000..282f3395e4 --- /dev/null +++ b/war/src/main/webapp/scripts/loading.js @@ -0,0 +1,60 @@ +/* + * The MIT License + * + * Copyright (c) 2018, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +const Fetch = { + get: function (url, callback) { + const xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.onload = function () { + const status = xhr.status; + if(callback) { + callback(null, status); + } + }; + xhr.onerror = function(){ + const message = xhr.responseText; + const status = xhr.status; + if(callback) { + callback(message, status); + } + }; + xhr.send(); + } +}; + +function safeRedirector(url) { + const timeout = 5000; + window.setTimeout(function() { + const statusChecker = arguments.callee; + Fetch.get(url, function(error, status) { + if(status === 503 || status === 0) { + window.setTimeout(statusChecker, timeout) + } else { + window.location.replace(url); + } + if(error) { + console.error(error); + } + }) + }, timeout); +}