Horizon Login now inherits from Bootstrap Theme

The Horizon login page was not properly inheriting its styles from
its theme.  The implementation was making use of the _modal_form.html
template just to inherit a form, but there was no way to remove
the modal classes with that implementation.

The login page now uses a standard Bootstrap 'panel'.  This will
inherit the look and feel of any theme more naturally when its not
inside of a modal, as not all themes use box-shadow outside of a
modal; some chose to be very flat on purpose, and the built in
panels take advantage of this.

When used within the Region selector, it does need to exist within
a modal, so some simple logic was added for the classes necessary.

The panel is a little bit wider than it was before, but it is now
a standard Bootstrap column size, so its responsive down to a very
small screen size.

The modal is a little bit wider than it was before as well, as it is
is now the standard medium modal size for Bootstrap.

Improvements:
 * Logo is now an <img> tag, which means it can automagically resize
   to fit in the available space
 * Unneccesary styles removed
 * _splash.scss was only being used in _login.html, which was
   confusing, so _splash.scss is renamed to _login.scss, and is now a
   class based style, so it can live in /components
 * Its now Theme ready and responsive
 * Region Selector Login now has a proper modal backdrop
 * has-error help-text should not be alert alert-danger

Partially-Implements: blueprint horizon-theme-css-reorg
Partially-Implements: blueprint bootstrap-html-standards
Change-Id: Ie968414ab8ef2154623edfc21ce5623e8c4057c6
This commit is contained in:
Diana Whitten 2015-09-01 18:25:01 -07:00
parent 38b4be52d4
commit 20ff47185f
21 changed files with 227 additions and 110 deletions

View File

@ -128,6 +128,7 @@ full use of the Bootstrap theme architecture.
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
* Tables_ * Tables_
* Login_
Step 1 Step 1
------ ------
@ -197,6 +198,25 @@ The standard Bootstrap tables will be borderless by default. If you wish to
add a border, like the ``default`` theme, see add a border, like the ``default`` theme, see
``openstack_dashboard/themes/default/horizon/components/_tables.scss`` ``openstack_dashboard/themes/default/horizon/components/_tables.scss``
.. _Login:
Login
-----
Login Splash Page
~~~~~~~~~~~~~~~~~
The login splash page now uses a standard Bootstrap panel in its implementation.
See the **Panels** section in your variables file to variables to easily
customize.
Modal Login
~~~~~~~~~~~
The modal login experience, as used when switching regions, uses a standard
Bootstrap dialog. See the **Modals** section of your variables file for
specific variables to customize.
Bootswatch and Material Design Bootswatch and Material Design
------------------------------ ------------------------------

View File

@ -47,7 +47,7 @@
* `helpText` exists outside of element, * `helpText` exists outside of element,
* so we have to traverse one node up * so we have to traverse one node up
*/ */
var helpText = element.parent().find('#help_text'); var helpText = element.parent().find('.help_text');
helpText.hide(); helpText.hide();
// Update the visuals when user selects item from dropdown // Update the visuals when user selects item from dropdown

View File

@ -1,5 +1,5 @@
<form> <form>
<p id="help_text">Some help text.</p> <p class="help_text">Some help text.</p>
<fieldset hz-login-finder> <fieldset hz-login-finder>
<div class="form-group"><input id="id_username"></div> <div class="form-group"><input id="id_username"></div>
<div class="form-group"><input id="id_password"></div> <div class="form-group"><input id="id_password"></div>

View File

@ -64,7 +64,7 @@
authType = element.find('#id_auth_type'); authType = element.find('#id_auth_type');
userInput = element.find("#id_username").parents('.form-group'); userInput = element.find("#id_username").parents('.form-group');
passwordInput = element.find("#id_password").parents('.form-group'); passwordInput = element.find("#id_password").parents('.form-group');
helpText = element.find('#help_text'); helpText = element.find('.help_text');
$rootScope.$apply(); $rootScope.$apply();
}); });
@ -96,7 +96,7 @@
passwordInput = element.find("#id_password").parents('.form-group'); passwordInput = element.find("#id_password").parents('.form-group');
domainInput = element.find("#id_domain").parents('.form-group'); domainInput = element.find("#id_domain").parents('.form-group');
regionInput = element.find("#id_region").parents('.form-group'); regionInput = element.find("#id_region").parents('.form-group');
helpText = element.find('#help_text'); helpText = element.find('.help_text');
$rootScope.$apply(); $rootScope.$apply();
}); });

View File

@ -1,5 +1,5 @@
<form> <form>
<p id="help_text">Some help text.</p> <p class="help_text">Some help text.</p>
<fieldset hz-login-finder> <fieldset hz-login-finder>
<div> <div>
<select id="id_auth_type"> <select id="id_auth_type">

View File

@ -4,10 +4,10 @@
This help text will only show up if websso is enabled This help text will only show up if websso is enabled
because websso introduces new authentication mechanisms. because websso introduces new authentication mechanisms.
{% endcomment %} {% endcomment %}
<p id="help_text"> <div class="help_text alert alert-info">
{% block websso-help-text %} {% block websso-help-text %}
{% blocktrans %} {% blocktrans %}
If you are not sure which authentication method to use, contact your administrator. If you are not sure which authentication method to use, contact your administrator.
{% endblocktrans %} {% endblocktrans %}
{% endblock %} {% endblock %}
</p> </div>

View File

@ -1,46 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %} {% load i18n %}
{% block modal-header %}{% trans "Log In" %}{% endblock %} {% if 'is_modal' in request.GET or 'is_modal' in request.POST %}
{% block modal_class %}login {% if hide %}modal{% endif %}{% endblock %} {% include 'auth/_login_modal.html' %}
{% else %}
{% block form_action %}{% url 'login' %}{% endblock %} {% include 'auth/_login_page.html' %}
{% block ng_controller %}hzLoginController{% endblock %} {% endif %}
{% block autocomplete %}{{ HORIZON_CONFIG.password_autocomplete }}{% endblock %}
{% block modal-body %}
{% comment %}
These fake fields are required to prevent Chrome v34+ from autofilling form.
{% endcomment %}
{% if HORIZON_CONFIG.password_autocomplete != "on" %}
<div class="fake_credentials" style="display: none">
<input type="text" name="fake_email" value="" />
<input type="password" name="fake_password" value="" />
</div>
{%endif%}
{% include "auth/_description.html" %}
<fieldset hz-login-finder>
{% if request.user.is_authenticated and 'next' in request.GET %}
<div class="form-group clearfix error">
<span class="help-block"><p>{% trans "You do not have permission to access the resource:" %}</p>
<p><b>{{ request.GET.next }}</b></p>
<p>{% url 'horizon:user_home' as home_url %}{% blocktrans %}Login as different user or go back to <a href="{{ home_url }}"> home page</a>{% endblocktrans %}</p>
</span>
</div>
{% endif %}
{% if request.COOKIES.logout_reason %}
<div class="form-group clearfix error" id="logout_reason">
<span class="help-block alert alert-danger"><p>{{ request.COOKIES.logout_reason }}</p></span>
</div>
{% endif %}
{% if next %}<input type="hidden" name="{{ redirect_field_name }}" value="{{ next }}" />{% endif %}
{% include "horizon/common/_form_fields.html" %}
</fieldset>
{% endblock %}
{% block modal-footer %}
<button id="loginBtn" type="submit" class="btn btn-primary pull-right">
<span ng-show="auth_type==='credentials'">{% trans "Sign In" %}</span>
<span ng-hide="auth_type==='credentials'" ng-cloak>{% trans "Connect" %}</span>
</button>
{% endblock %}

View File

@ -0,0 +1,80 @@
{% load i18n %}
{% load url from future %}
{% block pre_login %}
<form id="" class="ng-pristine ng-valid ng-scope"
method="POST"
action="{% url 'login' %}"
autocomplete="off"
ng-controller="hzLoginController">
{% csrf_token %}
{% endblock %}
<div class="panel panel-default">
<div class="panel-heading">
{% block login_header %}
<h3 class="login-title">
{% trans 'Log in' %}
</h3>
{% endblock %}
</div>
<div class="panel-body">
{% block login_body %}
{% comment %}
These fake fields are required to prevent Chrome v34+ from autofilling form.
{% endcomment %}
{% if HORIZON_CONFIG.password_autocomplete != "on" %}
<div class="fake_credentials" style="display: none">
<input type="text" name="fake_email" value="" />
<input type="password" name="fake_password" value="" />
</div>
{%endif%}
{% include "auth/_description.html" %}
<fieldset hz-login-finder>
{% if request.user.is_authenticated and 'next' in request.GET %}
<div class="form-group clearfix error help-block alert alert-danger">
<p>
{% trans "You do not have permission to access the resource:" %}
</p>
<p>
<strong>
{{ request.GET.next }}
</strong>
</p>
<p>
{% url 'horizon:user_home' as home_url %}
{% blocktrans %}
Login as different user or go back to <a href="{{ home_url }}">home page</a>
{% endblocktrans %}
</p>
</div>
{% endif %}
{% if request.COOKIES.logout_reason %}
<div class="form-group clearfix error help-block alert alert-danger" id="logout_reason">
<p>{{ request.COOKIES.logout_reason }}</p>
</div>
{% endif %}
{% if next %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ next }}" />
{% endif %}
{% include "horizon/common/_form_fields.html" %}
</fieldset>
{% endblock %}
</div>
<div class="panel-footer">
{% block login_footer %}
<button id="loginBtn" type="submit" class="btn btn-primary pull-right">
<span ng-show="auth_type==='credentials'">{% trans "Sign In" %}</span>
<span ng-hide="auth_type==='credentials'" ng-cloak>{% trans "Connect" %}</span>
</button>
<div class="clearfix"></div>
{% endblock %}
</div>
</div>
{% block post_login%}
</form>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends 'auth/_login_form.html' %}
{% load i18n %}
{% block pre_login %}
<div class="login modal">
<div class="modal-dialog">
<div class="modal-content">
{{ block.super }}
{% endblock %}
{% block login_header %}
{{ block.super }}
<button class="close" aria-hidden="true" data-dismiss="modal" type="button">
<span class="fa fa-close"></span>
</button>
{% endblock %}
{% block login_body %}
{{ block.super }}
<input type="hidden" value="" name="is_modal">
{% endblock %}
{% block post_login %}
{{ block.super }}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends 'auth/_login_form.html' %}
{% load i18n %}
{% block pre_login %}
<div class="container login">
<div class="row">
<div class="col-xs-11 col-sm-8 col-md-6 col-lg-5 horizontal-center">
{{ block.super }}
{% endblock %}
{% block login_header %}
<div class="text-center">
<img class="splash-logo" src="{{ STATIC_URL }}dashboard/img/logo-splash.png">
</div>
{{ block.super }}
{% endblock %}
{% block post_login %}
{{ block.super }}
</div>
</div>
</div>
{% endblock %}

View File

@ -20,7 +20,7 @@
</label> </label>
{% endif %} {% endif %}
{% for error in field.errors %} {% for error in field.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span> <span class="help-block {{ form.error_css_class }}">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -42,7 +42,7 @@
{% endfor %} {% endfor %}
{% for error in field.errors %} {% for error in field.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span> <span class="help-block {{ form.error_css_class }}">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
@ -70,7 +70,7 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
{% for error in field.errors %} {% for error in field.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span> <span class="help-block {{ form.error_css_class }}">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}

View File

@ -7,7 +7,7 @@
{% for region in regions.available %} {% for region in regions.available %}
{% if region.name != regions.current.name %} {% if region.name != regions.current.name %}
<li> <li>
<a class="ajax-modal" href="{% url 'login' %}?region={{ region.endpoint|urlencode }}"> <a class="ajax-modal" href="{% url 'login' %}?region={{ region.endpoint|urlencode }}&is_modal">
{{ region.name }} {{ region.name }}
</a> </a>
</li> </li>

View File

@ -1,33 +0,0 @@
/**
* Styling for the splash/login page.
* Restyled for federated login.
*/
#splash {
.login {
background: url(../img/logo-splash.png) no-repeat center 35px;
position: absolute;
top: 80px;
left: 50%;
margin: 0 0 0 -195px;
padding-top: 170px;
width: 390px;
border: 1px solid $border-color;
max-height: none;
border-radius: 6px;
@include box-shadow(0 3px 7px rgba(0, 0, 0, 0.3));
background-clip: padding-box;
p#help_text {
display: none;
padding: 1em 0.5em;
margin: 0;
}
}
p.help-block {
display: none;
}
}

View File

@ -0,0 +1,7 @@
/* Some utility classes useful everywhere */
.row .horizontal-center,
.horizontal-center {
float: none;
margin: 0 auto;
}

View File

@ -0,0 +1,28 @@
/**
* Styling for the splash/login page.
*/
.login {
margin-top: $navbar-height*2;
.splash-logo {
padding: $padding-large-horizontal $padding-large-vertical;
max-width: 100%;
}
.help_text {
display: none;
}
.login-title {
display: inline-block;
}
ul.errorlist {
padding-left: 0;
}
.modal-content .panel {
margin-bottom: 0;
}
}

View File

@ -7,13 +7,16 @@
// Horizon Mixins // Horizon Mixins
@import "mixins"; @import "mixins";
// Horizon Util
@import "util";
// Vendor Components // Vendor Components
@import "/bootstrap/scss/bootstrap"; @import "/bootstrap/scss/bootstrap";
@import "/horizon/lib/font-awesome/scss/font-awesome.scss"; @import "/horizon/lib/font-awesome/scss/font-awesome.scss";
@import "/horizon/lib/magic_search/magic_search.scss"; @import "/horizon/lib/magic_search/magic_search.scss";
// Dashboard Components // Dashboard Components
@import "splash"; @import "components/login";
@import "components/resource_browser"; @import "components/resource_browser";
@import "components/sidebar"; @import "components/sidebar";
@import "components/navbar"; @import "components/navbar";
@ -98,10 +101,6 @@ ul {
margin: 0; margin: 0;
} }
.login ul.errorlist {
padding-left: 0;
}
// Disc-styled list. This list should be used to build bullet-items lists. // Disc-styled list. This list should be used to build bullet-items lists.
.list-bullet { .list-bullet {
list-style: disc; list-style: disc;
@ -254,7 +253,6 @@ a.current_item:hover h4 {
} }
.modal > form, .modal > form,
.login > form,
.alert-actions > form { .alert-actions > form {
margin-bottom: 0; margin-bottom: 0;
} }
@ -318,17 +316,6 @@ a.current_item:hover h4 {
@extend .form-control; @extend .form-control;
} }
form label {
text-align: left;
color: $gray;
font-weight: bold;
display: inline-block;
}
.login.modal .modal-dialog {
width: 390px;
}
.modal.fullscreen .modal-dialog { .modal.fullscreen .modal-dialog {
width: 90%; width: 90%;
margin: auto; margin: auto;

View File

@ -24,7 +24,7 @@ class LoginPage(pageobject.PageObject):
_login_username_field_locator = (by.By.ID, 'id_username') _login_username_field_locator = (by.By.ID, 'id_username')
_login_password_field_locator = (by.By.ID, 'id_password') _login_password_field_locator = (by.By.ID, 'id_password')
_login_submit_button_locator = (by.By.CSS_SELECTOR, _login_submit_button_locator = (by.By.CSS_SELECTOR,
'div.modal-footer button.btn') 'div.panel-footer button.btn')
_login_logout_reason_locator = (by.By.ID, 'logout_reason') _login_logout_reason_locator = (by.By.ID, 'logout_reason')
def __init__(self, driver, conf): def __init__(self, driver, conf):

View File

@ -1,4 +1,7 @@
@import '/bootstrap/scss/bootstrap/mixins/vendor-prefixes';
@import 'components/dropdowns'; @import 'components/dropdowns';
@import 'components/navbar'; @import 'components/navbar';
@import 'components/navs'; @import 'components/navs';
@import 'components/panels';
@import 'components/tables'; @import 'components/tables';

View File

@ -689,16 +689,16 @@ $list-group-link-heading-color: #333 !default;
$panel-bg: #fff !default; $panel-bg: #fff !default;
$panel-body-padding: 15px !default; $panel-body-padding: 15px !default;
$panel-heading-padding: 10px 15px !default; $panel-heading-padding: 10px 15px !default;
$panel-footer-padding: $panel-heading-padding !default; $panel-footer-padding: 15px !default;
$panel-border-radius: $border-radius-base !default; $panel-border-radius: $border-radius-base !default;
//** Border color for elements within panels //** Border color for elements within panels
$panel-inner-border: #ddd !default; $panel-inner-border: #ddd !default;
$panel-footer-bg: #f5f5f5 !default; $panel-footer-bg: $panel-bg !default;
$panel-default-text: $gray-dark !default; $panel-default-text: $gray-dark !default;
$panel-default-border: #ddd !default; $panel-default-border: #ddd !default;
$panel-default-heading-bg: #f5f5f5 !default; $panel-default-heading-bg: $panel-bg !default;
$panel-primary-text: #fff !default; $panel-primary-text: #fff !default;
$panel-primary-border: $brand-primary !default; $panel-primary-border: $brand-primary !default;

View File

@ -0,0 +1,3 @@
.panel {
@include box-shadow(0 3px 7px rgba(0, 0, 0, 0.3));
}

View File

@ -10,4 +10,14 @@
// The 114 is a legacy value to push the context-menu over // The 114 is a legacy value to push the context-menu over
padding-right: 114px; padding-right: 114px;
} }
// TODO(hurgleburgler): This seems awfully global ... and strange
// need to look into why we are actually doing this.
form label {
text-align: left;
color: $gray;
font-weight: bold;
display: inline-block;
}