summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYoshie Muranaka <yoshiemuranaka@gmail.com>2020-01-29 13:21:12 -0800
committerGunnar Mills <gmills@us.ibm.com>2020-02-11 16:43:22 +0000
commit4148f2eee6313068d3223871005160b2902abb18 (patch)
tree5266cfee8f41eda2224479d2c911c3128988a38a
parentb0a0847a8eb02ae21f755942799a81c6e3475e64 (diff)
downloadphosphor-webui-4148f2eee6313068d3223871005160b2902abb18.tar.gz
phosphor-webui-4148f2eee6313068d3223871005160b2902abb18.zip
Create profile settings pageHEADmaster
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.svg1
-rw-r--r--app/common/directives/app-header.html11
-rw-r--r--app/common/directives/app-header.js2
-rw-r--r--app/common/styles/base/forms.scss1
-rw-r--r--app/common/styles/directives/dropdown.scss5
-rw-r--r--app/common/styles/directives/index.scss3
-rw-r--r--app/common/styles/layout/header.scss51
-rw-r--r--app/index.js5
-rw-r--r--app/profile-settings/controllers/profile-settings-controller.html76
-rw-r--r--app/profile-settings/controllers/profile-settings-controller.js75
-rw-r--r--app/profile-settings/index.js25
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="&lt;Transparent Rectangle&gt;" 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);
OpenPOWER on IntegriCloud