diff options
author | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-01-29 13:21:12 -0800 |
---|---|---|
committer | Gunnar Mills <gmills@us.ibm.com> | 2020-02-11 16:43:22 +0000 |
commit | 4148f2eee6313068d3223871005160b2902abb18 (patch) | |
tree | 5266cfee8f41eda2224479d2c911c3128988a38a | |
parent | b0a0847a8eb02ae21f755942799a81c6e3475e64 (diff) | |
download | phosphor-webui-4148f2eee6313068d3223871005160b2902abb18.tar.gz phosphor-webui-4148f2eee6313068d3223871005160b2902abb18.zip |
Adding a profile settings page so readonly and operator
roles are able to change their own password.
Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: Iee9536255ad47f4df4af8746c1e01da37c407f2b
-rw-r--r-- | app/assets/icons/icon-avatar.svg | 1 | ||||
-rw-r--r-- | app/common/directives/app-header.html | 11 | ||||
-rw-r--r-- | app/common/directives/app-header.js | 2 | ||||
-rw-r--r-- | app/common/styles/base/forms.scss | 1 | ||||
-rw-r--r-- | app/common/styles/directives/dropdown.scss | 5 | ||||
-rw-r--r-- | app/common/styles/directives/index.scss | 3 | ||||
-rw-r--r-- | app/common/styles/layout/header.scss | 51 | ||||
-rw-r--r-- | app/index.js | 5 | ||||
-rw-r--r-- | app/profile-settings/controllers/profile-settings-controller.html | 76 | ||||
-rw-r--r-- | app/profile-settings/controllers/profile-settings-controller.js | 75 | ||||
-rw-r--r-- | app/profile-settings/index.js | 25 |
11 files changed, 229 insertions, 26 deletions
diff --git a/app/assets/icons/icon-avatar.svg b/app/assets/icons/icon-avatar.svg new file mode 100644 index 0000000..665af99 --- /dev/null +++ b/app/assets/icons/icon-avatar.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16 2a14 14 0 1014 14A14 14 0 0016 2zm-6 24.38v-2A3.22 3.22 0 0113 21h6a3.22 3.22 0 013 3.39v2a11.92 11.92 0 01-12 0zm14-1.46v-.61A5.21 5.21 0 0019 19h-6a5.2 5.2 0 00-5 5.31v.59a12 12 0 1116 0z"/><path d="M16 7a5 5 0 105 5 5 5 0 00-5-5zm0 8a3 3 0 113-3 3 3 0 01-3 3z"/><path data-name="<Transparent Rectangle>" fill="none" d="M0 0h32v32H0z"/></svg>
\ No newline at end of file diff --git a/app/common/directives/app-header.html b/app/common/directives/app-header.html index bf4fb8f..ec03874 100644 --- a/app/common/directives/app-header.html +++ b/app/common/directives/app-header.html @@ -2,7 +2,16 @@ <!-- HEADER --> <div class="header__info-section"> <span class="header__title">OpenBMC</span> - <a href="" class="header__logout" ng-click="logout()">Log out</a> + <div class="header__actions" uib-dropdown> + <button id="user-actions" type="button" uib-dropdown-toggle> + <icon class="icon-user" file="icon-avatar.svg"></icon> + {{username}} + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="user-actions"> + <li role="menuitem"><a href="#/profile-settings" class="btn">Profile settings</a></li> + <li role="menuitem"><button ng-click="logout()" type="button" class="btn">Log out</button></li> + </ul> + </div> </div> <div class="header__functions-section"> <div class="logo__wrapper"> diff --git a/app/common/directives/app-header.js b/app/common/directives/app-header.js index 98d210f..df39772 100644 --- a/app/common/directives/app-header.js +++ b/app/common/directives/app-header.js @@ -14,6 +14,7 @@ window.angular && (function(angular) { function( $rootScope, $scope, dataService, userModel, $location, $route) { $scope.dataService = dataService; + $scope.username = ''; try { // Create a secure websocket with URL as /subscribe @@ -118,6 +119,7 @@ window.angular && (function(angular) { $scope.loadNetworkInfo(); $scope.loadServerHealth(); $scope.loadSystemName(); + $scope.username = dataService.getUser(); } loadData(); diff --git a/app/common/styles/base/forms.scss b/app/common/styles/base/forms.scss index c775c48..6699d44 100644 --- a/app/common/styles/base/forms.scss +++ b/app/common/styles/base/forms.scss @@ -1,3 +1,4 @@ +.label, label, legend { margin: 0; diff --git a/app/common/styles/directives/dropdown.scss b/app/common/styles/directives/dropdown.scss new file mode 100644 index 0000000..0c0add0 --- /dev/null +++ b/app/common/styles/directives/dropdown.scss @@ -0,0 +1,5 @@ +.dropdown.open { + .dropdown-menu { + display: block; + } +}
\ No newline at end of file diff --git a/app/common/styles/directives/index.scss b/app/common/styles/directives/index.scss index 5d9de6f..1fcbb65 100644 --- a/app/common/styles/directives/index.scss +++ b/app/common/styles/directives/index.scss @@ -1 +1,2 @@ -@import "./app-navigation.scss";
\ No newline at end of file +@import "./app-navigation.scss"; +@import "./dropdown.scss";
\ No newline at end of file diff --git a/app/common/styles/layout/header.scss b/app/common/styles/layout/header.scss index c034c82..b1665ca 100644 --- a/app/common/styles/layout/header.scss +++ b/app/common/styles/layout/header.scss @@ -21,37 +21,42 @@ $logoMaxWidth: 125px; z-index: 300; } -.header__title { - margin-left: 1em; - display: none; - float: left; - @include mediaQuery(x-small) { - display: inline-block; - position: absolute; - top: 50%; - transform: translateY(-50%); - } -} - .header__info-section { position: relative; background: $primary-dark; color: $primary-light; - overflow: hidden; -} - -.header__logout { - float: right; - color: $primary-light; - font-size: 0.9em; - text-decoration: none; - padding: 1em; - font-weight: 400; - &:visited { + width: 100%; + height: 50px; + display: flex; + justify-content: space-between; + .dropdown-menu { + left: unset; + right: 0; + border-radius: 0; + font-size: 0.9rem; + .btn { + color: $primary-dark; + } + } + .dropdown-toggle { color: $primary-light; + fill: $primary-light; + text-decoration: none; + font-weight: 400; + margin-right: 0.5rem; + height: 50px; //to vertically align in 50px header + &::after { + display: none; //hiding dropdown caret inserted by bootstrap + } } } +.header__title { + margin-left: 1rem; + display: block; + line-height: 50px; //to vertically align in 50px header +} + .header__functions-section { color: $primary-light; padding: 0 1.1em; diff --git a/app/index.js b/app/index.js index 57d031b..156fab6 100644 --- a/app/index.js +++ b/app/index.js @@ -78,6 +78,9 @@ import file_upload from './common/components/file-upload.js'; import login_index from './login/index.js'; import login_controller from './login/controllers/login-controller.js'; +import profile_settings_index from './profile-settings/index.js'; +import profile_settings_controller from './profile-settings/controllers/profile-settings-controller.js'; + import overview_index from './overview/index.js'; import system_overview_controller from './overview/controllers/system-overview-controller.js'; @@ -133,7 +136,7 @@ window.angular && (function(angular) { // Model resources 'app.login', 'app.overview', 'app.serverControl', 'app.serverHealth', 'app.configuration', 'app.accessControl', - 'app.redfish' + 'app.redfish', 'app.profileSettings' ]) // Route configuration .config([ diff --git a/app/profile-settings/controllers/profile-settings-controller.html b/app/profile-settings/controllers/profile-settings-controller.html new file mode 100644 index 0000000..365cf7f --- /dev/null +++ b/app/profile-settings/controllers/profile-settings-controller.html @@ -0,0 +1,76 @@ + +<div class="row column"> + <div class="page-header"> + <h1>Profile settings</h1> + </div> +</div> +<div class="row column"> + <div class="column medium-12 large-5 xlarge-4"> + <section class="section"> + <div class="section-header"> + <h2 class="section-title">Profile information</h2> + </div> + <dl> + <dt class="label">Username</dt> + <dd>{{username}}</dd> + </dl> + </section> + </div> +</div> +<div class="row column"> + <div class="column medium-12 large-5 xlarge-4"> + <section class="section"> + <div class="section-header"> + <h2 class="section-title">Change password</h2> + </div> + <form name="form"> + <!-- Password --> + <div class="field-group-container"> + <label for="password">New password</label> + <p class="label__helper-text">Password must between <span class="nowrap">{{minPasswordLength}} – {{maxPasswordLength}}</span> characters</p> + <input id="password" + name="password" + type="password" + required + ng-minlength="minPasswordLength" + ng-maxlength="maxPasswordLength" + autocomplete="new-password" + ng-model="password" + password-visibility-toggle/> + <div ng-if="form.password.$invalid && form.password.$dirty" class="form__validation-message"> + <span ng-show="form.password.$error.required"> + Field is required</span> + <span ng-show="form.password.$error.minlength || form.password.$error.maxlength"> + Length must be between <span class="nowrap">{{minPasswordLength}} – {{maxPasswordLength}}</span> characters</span> + </div> + </div> + <!-- Password confirm --> + <div class="field-group-container"> + <label for="passwordConfirm">Confirm new password</label> + <input id="passwordConfirm" + name="passwordConfirm" + type="password" + required + autocomplete="new-password" + ng-model="passwordConfirm" + password-visibility-toggle + password-confirm + first-password="form.password.$modelValue"/> + <div ng-if="form.passwordConfirm.$invalid && form.passwordConfirm.$dirty" class="form__validation-message"> + <span ng-show="form.passwordConfirm.$error.required"> + Field is required</span> + <span ng-show="form.passwordConfirm.$error.passwordConfirm" + ng-hide="form.passwordConfirm.$error.required"> + Passwords do not match</span> + </div> + </div> + <!-- Form actions --> + <div class="field-group-container"> + <button class="btn btn-primary" type="submit" ng-click="onSubmit(form)"> + Change password + </button> + </div> + </form> + </section> + </div> +</div>
\ No newline at end of file diff --git a/app/profile-settings/controllers/profile-settings-controller.js b/app/profile-settings/controllers/profile-settings-controller.js new file mode 100644 index 0000000..404e055 --- /dev/null +++ b/app/profile-settings/controllers/profile-settings-controller.js @@ -0,0 +1,75 @@ +/** + * Controller for the profile settings page + * + * @module app/profile-settings/controllers/index + * @exports ProfileSettingsController + * @name ProfileSettingsController + */ + +window.angular && (function(angular) { + 'use strict'; + + angular.module('app.profileSettings') + .controller('profileSettingsController', [ + '$scope', 'APIUtils', 'dataService', 'toastService', + function($scope, APIUtils, dataService, toastService) { + $scope.username; + $scope.minPasswordLength; + $scope.maxPasswordLength; + $scope.password; + $scope.passwordConfirm; + + /** + * Make API call to update user password + * @param {string} password + */ + const updatePassword = function(password) { + $scope.loading = true; + APIUtils.updateUser($scope.username, null, password) + .then( + () => toastService.success( + 'Password has been updated successfully.')) + .catch((error) => { + console.log(JSON.stringify(error)); + toastService.error('Unable to update password.') + }) + .finally(() => { + $scope.password = ''; + $scope.passwordConfirm = ''; + $scope.form.$setPristine(); + $scope.form.$setUntouched(); + $scope.loading = false; + }) + }; + + /** + * API call to get account settings for min/max + * password length requirement + */ + const getAllUserAccountProperties = function() { + APIUtils.getAllUserAccountProperties().then((accountSettings) => { + $scope.minPasswordLength = accountSettings.MinPasswordLength; + $scope.maxPasswordLength = accountSettings.MaxPasswordLength; + }) + }; + + /** + * Callback after form submitted + */ + $scope.onSubmit = function(form) { + if (form.$valid) { + const password = form.password.$viewValue; + updatePassword(password); + } + }; + + /** + * Callback after view loaded + */ + $scope.$on('$viewContentLoaded', () => { + getAllUserAccountProperties(); + $scope.username = dataService.getUser(); + }); + } + ]); +})(angular); diff --git a/app/profile-settings/index.js b/app/profile-settings/index.js new file mode 100644 index 0000000..77947a7 --- /dev/null +++ b/app/profile-settings/index.js @@ -0,0 +1,25 @@ +/** + * A module for the Profile Settings page + * + * @module app/profile-settings/index + * @exports app/profile-settings/index + */ + +window.angular && (function(angular) { + 'use strict'; + + angular + .module('app.profileSettings', ['ngRoute', 'app.common.services']) + // Route configuration + .config([ + '$routeProvider', + function($routeProvider) { + $routeProvider.when('/profile-settings', { + 'template': + require('./controllers/profile-settings-controller.html'), + 'controller': 'profileSettingsController', + authenticated: true + }) + } + ]); +})(window.angular); |