summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbeccabroek <beccabroek@gmail.com>2019-01-23 14:26:55 -0600
committerEd Tanous <ed.tanous@intel.com>2019-02-02 01:09:14 +0000
commita09cc2da1b3a9bad0eaf34fbfdec8a3ee32d47f8 (patch)
tree596738144339961f447244c709d904dc7725f362
parent97280b3efde6a19a517096940fd8e68c3c192331 (diff)
downloadphosphor-webui-a09cc2da1b3a9bad0eaf34fbfdec8a3ee32d47f8.tar.gz
phosphor-webui-a09cc2da1b3a9bad0eaf34fbfdec8a3ee32d47f8.zip
Form validation on login page
Using ngMessages, adds form validation to login page. Also creates a directive, hasError, to be used to validate form field by passing in a boolean. This is a proposed pattern to be used moving forward, as form validation is added to additional pages. Validation error messages are shown on $touched and on submit. Unreachable Server and Invalid username and password error messages remain until input is no longer $pristine after form submission. In addition, this removes unneeded and unused css styling Resolves openbmc/phosphor-webui#47 Change-Id: I7a067af67ac74d4cf2977d10f66445720ecae9eb Signed-off-by: beccabroek <beccabroek@gmail.com>
-rw-r--r--app/common/directives/form-input-error.js11
-rw-r--r--app/common/styles/base/colors.scss1
-rw-r--r--app/common/styles/base/forms.scss31
-rw-r--r--app/common/styles/base/foundation.scss25
-rw-r--r--app/common/styles/elements/alerts.scss68
-rw-r--r--app/common/styles/elements/errors.scss11
-rw-r--r--app/common/styles/elements/index.scss1
-rw-r--r--app/index.js1
-rw-r--r--app/login/controllers/login-controller.html36
-rw-r--r--app/login/controllers/login-controller.js14
-rw-r--r--app/login/styles/index.scss5
11 files changed, 81 insertions, 123 deletions
diff --git a/app/common/directives/form-input-error.js b/app/common/directives/form-input-error.js
new file mode 100644
index 0000000..69b0487
--- /dev/null
+++ b/app/common/directives/form-input-error.js
@@ -0,0 +1,11 @@
+angular.module('app.common.directives').directive('hasError', function() {
+ return {
+ scope: {hasError: '='},
+ require: 'ngModel',
+ link: function(scope, elm, attrs, ngModel) {
+ scope.$watch('hasError', function(value) {
+ ngModel.$setValidity('hasError', value ? false : true);
+ });
+ }
+ };
+});
diff --git a/app/common/styles/base/colors.scss b/app/common/styles/base/colors.scss
index 46285c8..405dad4 100644
--- a/app/common/styles/base/colors.scss
+++ b/app/common/styles/base/colors.scss
@@ -52,6 +52,7 @@ $status-warn: #ffb000;
$alert__error: rgb(230, 35, 37);
$alert__warning: rgb(255, 127, 0);
$alert__message: rgb(203, 221, 235);
+$alert__danger: #fad3D3;
// Severity
$critical-lightbg: #e62325;
diff --git a/app/common/styles/base/forms.scss b/app/common/styles/base/forms.scss
index 0957609..73c25c1 100644
--- a/app/common/styles/base/forms.scss
+++ b/app/common/styles/base/forms.scss
@@ -21,13 +21,13 @@ textarea {
border-radius: 0px;
border: 1px solid $input-border;
height: 3.1em;
- margin: 0 0 1em 0;
+ margin: 0;
background: $white;
box-shadow: 0 0 0;
transition: none !important;
&:focus {
border-color: $medgrey;
- box-shadow: 0 -5px $field__focus inset;
+ box-shadow: 0 -3px $field__focus inset;
}
&:disabled,
.disabled {
@@ -43,6 +43,33 @@ textarea {
}
}
+//input validation
+.ng-invalid.ng-touched {
+ box-shadow: 0 -3px $error-color inset;
+ &:focus {
+ border-color: $medgrey;
+ box-shadow: 0 -3px $error-color inset;
+ }
+}
+.submitted .ng-invalid {
+ box-shadow: 0 -3px $error-color inset;
+ &:focus {
+ border-color: $medgrey;
+ box-shadow: 0 -3px $error-color inset;
+ }
+}
+
+.form-error {
+ margin-bottom: .7em;
+ font-size: 0.8rem;
+ color: #c60f13;
+ height:1rem;
+ display: block;
+ visibility: hidden;
+ .visible {
+ visibility: visible;
+ }
+}
//Foundation overwrite
[type=color], [type=date], [type=datetime-local], [type=datetime], [type=email], [type=month], [type=number], [type=password], [type=search], [type=tel], [type=text], [type=time], [type=url], [type=week], textarea {
border-color: $input-border;
diff --git a/app/common/styles/base/foundation.scss b/app/common/styles/base/foundation.scss
index f1bcb2f..a422dec 100644
--- a/app/common/styles/base/foundation.scss
+++ b/app/common/styles/base/foundation.scss
@@ -705,31 +705,6 @@ select[multiple] {
height: auto;
background-image: none; }
-.is-invalid-input:not(:focus) {
- border-color: #c60f13;
- background-color: #f8e6e7; }
-.is-invalid-input:not(:focus)::-webkit-input-placeholder {
- color: #c60f13; }
-.is-invalid-input:not(:focus)::-moz-placeholder {
- color: #c60f13; }
-.is-invalid-input:not(:focus):-ms-input-placeholder {
- color: #c60f13; }
-.is-invalid-input:not(:focus)::placeholder {
- color: #c60f13; }
-
-.is-invalid-label {
- color: #c60f13; }
-
-.form-error {
- display: none;
- margin-top: -0.5rem;
- margin-bottom: 1rem;
- font-size: 0.75rem;
- font-weight: bold;
- color: #c60f13; }
-.form-error.is-visible {
- display: block; }
-
.hide {
display: none !important; }
diff --git a/app/common/styles/elements/alerts.scss b/app/common/styles/elements/alerts.scss
index 96f0a4c..6242257 100644
--- a/app/common/styles/elements/alerts.scss
+++ b/app/common/styles/elements/alerts.scss
@@ -1,67 +1,11 @@
//Fixed alerts
-.alert__error,
-.alert__warning,
-.alert__message {
- color: $darkbg__primary;
- padding: 1em;
- margin: .5em 0;
- position: relative;
- display: flex;
- justify-content: center;
- flex-direction: column;
- .close {
- color: $lightbg__primary;
- position: absolute;
- right: 0;
- top: 50%;
- transform: translateY(-50%);
- font-size: 1.5em;
- padding: 1em;
- box-sizing: border-box;
- line-height: 0;
- display: flex;
- justify-content: center;
- flex-direction: column;
- background: transparent;
- border: 0;
- margin: 0;
- &:hover {
- color: $lightbg__accent;
- }
- }
-}
-
-.alert__error {
- background: $alert__error;
-
-}
-
-.alert__warning {
- background: $alert__warning;
-
-}
-.alert__message {
- background: $alert__message;
-}
-
-// Power confirmation buttons
-.power__confirm-buttons {
- .btn-primary {
- background: transparent;
- border: 2px solid $white;
- padding: 1em 2.2em;
- margin: 0 10px;
- border-radius: 4px;
- &:focus,
- &:hover {
- background: $primebtn__bg;
- border: 2px solid $primebtn__bg;
- }
- }
- @include mediaQuery(large) {
- float: right;
- }
+.alert-danger{
+ background-color: $alert__danger;
+ border-color: $critical-lightbg;
+ border-radius: 0;
+ color: #333;
+ text-align: left;
}
diff --git a/app/common/styles/elements/errors.scss b/app/common/styles/elements/errors.scss
deleted file mode 100644
index 3076a3d..0000000
--- a/app/common/styles/elements/errors.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.error-msg {
- background: lighten($error-color, 20%);
- padding: 1em;
- text-align: center;
- font-size: 1em;
- border: 1px solid $error-color;
- color: $black;
- font-family: "Courier New", Helvetica, Arial, sans-serif;
- font-weight: 700;
- max-width: 325px;
-}
diff --git a/app/common/styles/elements/index.scss b/app/common/styles/elements/index.scss
index 3c5754e..25162bb 100644
--- a/app/common/styles/elements/index.scss
+++ b/app/common/styles/elements/index.scss
@@ -11,5 +11,4 @@
@import "export";
@import "modals";
@import "quicklinks";
-@import "errors";
@import "toast";
diff --git a/app/index.js b/app/index.js
index 37e27c1..2313016 100644
--- a/app/index.js
+++ b/app/index.js
@@ -54,6 +54,7 @@ import loader from './common/directives/loader.js';
import paginate from './common/directives/paginate.js';
import serial_console from './common/directives/serial-console.js';
import dir_paginate from './common/directives/dirPagination.js';
+import form_input_error from './common/directives/form-input-error.js';
import login_index from './login/index.js';
import login_controller from './login/controllers/login-controller.js';
diff --git a/app/login/controllers/login-controller.html b/app/login/controllers/login-controller.html
index b8fdf09..b89c2c4 100644
--- a/app/login/controllers/login-controller.html
+++ b/app/login/controllers/login-controller.html
@@ -4,20 +4,34 @@
<img src="../../assets/images/logo.svg" class="login__logo" alt="OpenBMC logo"/>
<h1 class="login__desc">OpenBMC</h1>
</div>
- <div class="columns large-6 disabled">
- <form id="login__form" action="">
- <label >BMC Host or BMC IP Address</label>
- <input type="text" ng-model="host" required ng-class="{'error': error && description != 'Invalid username or password'}" autofocus ng-keydown="tryLogin(host, username, password, $event)" ng-disabled="dataService.loading">
-
- <label for="username">Username</label>
- <input type="text" id="username" name="username" required ng-model="username" ng-class="{'error': description == 'Invalid username or password'}" ng-keydown="tryLogin(host, username, password, $event)" ng-disabled="dataService.loading" autocomplete="off">
+ <div class="columns large-6">
+ <form id="login__form" name="login__form" action="" ng-class="{'submitted' : submitted}">
+ <fieldset ng-disabled="dataService.loading">
+ <div class="alert alert-danger" role="alert" ng-if="invalidCredentials">
+ <b>Invalid username or password.</b>
+ <br>Please try again.
+ </div>
+ <label for="host">BMC Host or BMC IP Address</label>
+ <input type="text" id="host" name="host" class="validate-input" ng-model="host" has-error="serverUnreachable && login__form.host.$pristine" required autofocus ng-keydown="tryLogin(host, username, password, $event)">
+ <div ng-messages="login__form.host.$error" class="form-error" ng-class="{'visible' : login__form.host.$touched || submitted}">
+ <p ng-message="required">Field is required</p>
+ <p ng-message="hasError">Server unreachable</p>
+ </div>
- <label for="password">Password</label>
- <input type="password" id="password" name="password" required ng-class="{'error': description == 'Invalid username or password'}" ng-model="password" ng-keydown="tryLogin(host, username, password, $event)" ng-disabled="dataService.loading" autocomplete="off">
+ <label for="username">Username</label>
+ <input type="text" id="username" name="username" has-error="invalidCredentials && login__form.$pristine" required ng-model="username" ng-keydown="tryLogin(host, username, password, $event)" autocomplete="off">
+ <div ng-messages="login__form.username.$error" class="form-error" ng-class="{'visible' : login__form.username.$touched || submitted}">
+ <p ng-message="required">Field is required</p>
+ </div>
- <input id="login__submit" class="btn-primary submit" type="button" value="Log in" role="button" ng-click="login(host, username, password)" ng-class="{error: error}" ng-disabled="dataService.loading">
+ <label for="password">Password</label>
+ <input type="password" id="password" name="password" has-error="invalidCredentials && login__form.$pristine" required ng-model="password" ng-keydown="tryLogin(host, username, password, $event)" autocomplete="off">
+ <div ng-messages="login__form.password.$error" class="form-error" ng-class="{'visible': login__form.password.$touched || submitted}">
+ <p ng-message="required">Field is required</p>
+ </div>
- <p class="login__error-msg error-msg" role="alert" ng-if="error">{{description}}</p>
+ <input id="login__submit" class="btn-primary submit" type="button" value="Log in" role="button" ng-click="login(host, username, password); submitted = true; login__form.$setPristine()" ng-class="{error: error}" ng-disabled="dataService.loading">
+ </fieldset>
</form>
</div>
</div>
diff --git a/app/login/controllers/login-controller.js b/app/login/controllers/login-controller.js
index e31a67e..518ede8 100644
--- a/app/login/controllers/login-controller.js
+++ b/app/login/controllers/login-controller.js
@@ -17,6 +17,8 @@ window.angular && (function(angular) {
'$location',
function($scope, $window, dataService, userModel, $location) {
$scope.dataService = dataService;
+ $scope.serverUnreachable = false;
+ $scope.invalidCredentials = false;
$scope.host = $scope.dataService.host.replace(/^https?\:\/\//ig, '');
$scope.tryLogin = function(host, username, password, event) {
@@ -27,9 +29,8 @@ window.angular && (function(angular) {
}
};
$scope.login = function(host, username, password) {
- $scope.error = false;
- $scope.description = 'Error logging in';
-
+ $scope.serverUnreachable = false;
+ $scope.invalidCredentials = false;
if (!username || username == '' || !password || password == '' ||
!host || host == '') {
return false;
@@ -45,9 +46,10 @@ window.angular && (function(angular) {
$window.location.href = next;
}
} else {
- $scope.error = true;
- if (description) {
- $scope.description = description;
+ if (description === 'Invalid username or password') {
+ $scope.invalidCredentials = true;
+ } else {
+ $scope.serverUnreachable = true;
}
}
});
diff --git a/app/login/styles/index.scss b/app/login/styles/index.scss
index 0bc424b..07d45bf 100644
--- a/app/login/styles/index.scss
+++ b/app/login/styles/index.scss
@@ -99,8 +99,3 @@
}
}
-.login__error-msg {
- @include mediaQuery(medium) {
- max-width: 100%;
- }
-}
OpenPOWER on IntegriCloud