IGNITE-8519 Web Console: Refactored security token UI/UX.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/81b6b2d4 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/81b6b2d4 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/81b6b2d4 Branch: refs/heads/ignite-8446 Commit: 81b6b2d49284da67ff279e5f8e0062934d73eaab Parents: 219bc81 Author: Ilya Borisov <[email protected]> Authored: Fri Jun 29 11:13:16 2018 +0700 Committer: Alexey Kuznetsov <[email protected]> Committed: Fri Jun 29 11:13:16 2018 +0700 ---------------------------------------------------------------------- .../fixtures/user-profile/credentials.js | 9 +- .../e2e/testcafe/page-models/pageProfile.js | 4 +- .../copyInputValueButton.directive.js | 86 ++++++++++++++++++++ .../frontend/app/components/form-field/index.js | 5 +- .../app/components/form-field/style.scss | 29 +++++++ .../app/components/page-profile/controller.js | 2 +- .../app/components/page-profile/template.pug | 22 +++-- .../app/modules/agent/AgentModal.service.js | 9 +- .../frontend/public/images/icons/copy.svg | 3 + .../frontend/public/images/icons/index.js | 41 +++++----- .../views/templates/agent-download.tpl.pug | 17 +++- 11 files changed, 187 insertions(+), 40 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/e2e/testcafe/fixtures/user-profile/credentials.js ---------------------------------------------------------------------- diff --git a/modules/web-console/e2e/testcafe/fixtures/user-profile/credentials.js b/modules/web-console/e2e/testcafe/fixtures/user-profile/credentials.js index b76a736..ea18aaf 100644 --- a/modules/web-console/e2e/testcafe/fixtures/user-profile/credentials.js +++ b/modules/web-console/e2e/testcafe/fixtures/user-profile/credentials.js @@ -39,13 +39,16 @@ fixture('Checking user credentials change') test('Testing secure token change', async(t) => { await t.click(pageProfile.securityToken.panel.heading); - const currentToken = await pageProfile.securityToken.value.innerText; + const currentToken = await pageProfile.securityToken.value.control.value; await t .click(pageProfile.securityToken.generateTokenButton) - .expect(confirmation.body.innerText).contains('Are you sure you want to change security token?') + .expect(confirmation.body.innerText).contains( +`Are you sure you want to change security token? +If you change the token you will need to restart the agent.` + ) .click(confirmation.confirmButton) - .expect(pageProfile.securityToken.value.innerText).notEql(currentToken); + .expect(pageProfile.securityToken.value.control.value).notEql(currentToken); }); test('Testing password change', async(t) => { http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/e2e/testcafe/page-models/pageProfile.js ---------------------------------------------------------------------- diff --git a/modules/web-console/e2e/testcafe/page-models/pageProfile.js b/modules/web-console/e2e/testcafe/page-models/pageProfile.js index a2eb1db..8a81f36 100644 --- a/modules/web-console/e2e/testcafe/page-models/pageProfile.js +++ b/modules/web-console/e2e/testcafe/page-models/pageProfile.js @@ -28,8 +28,8 @@ export const pageProfile = { company: new CustomFormField({id: 'companyInput'}), securityToken: { panel: new PanelCollapsible('security token'), - generateTokenButton: Selector('i').withAttribute('ng-click', '$ctrl.generateToken()'), - value: Selector('#current-security-token') + generateTokenButton: Selector('a').withText('Generate Random Security Token?'), + value: new CustomFormField({id: 'securityTokenInput'}) }, password: { panel: new PanelCollapsible('password'), http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/app/components/form-field/copyInputValueButton.directive.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-field/copyInputValueButton.directive.js b/modules/web-console/frontend/app/components/form-field/copyInputValueButton.directive.js new file mode 100644 index 0000000..06500e4 --- /dev/null +++ b/modules/web-console/frontend/app/components/form-field/copyInputValueButton.directive.js @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const template = ` + <svg + class='copy-input-value-button' + ignite-icon='copy' + ignite-copy-to-clipboard='{{ $ctrl.value }}' + bs-tooltip='' + data-title='{{::$ctrl.title}}' + ng-show='$ctrl.value' + ></svg> +`; + +class CopyInputValueButtonController { + /** @type {ng.INgModelController} */ + ngModel; + + /** + * Tooltip title + * @type {string} + */ + title; + + static $inject = ['$element', '$compile', '$scope']; + + /** + * @param {JQLite} $element + * @param {ng.ICompileService} $compile + * @param {ng.IScope} $scope + */ + constructor($element, $compile, $scope) { + this.$element = $element; + this.$compile = $compile; + this.$scope = $scope; + } + + $postLink() { + this.buttonScope = this.$scope.$new(true); + this.buttonScope.$ctrl = this; + this.$compile(template)(this.buttonScope, (clone) => { + this.$element[0].parentElement.appendChild(clone[0]); + this.buttonElement = clone; + }); + } + + $onDestroy() { + this.buttonScope.$ctrl = null; + this.buttonScope.$destroy(); + this.buttonElement.remove(); + this.buttonElement = this.$element = this.ngModel = null; + } + + get value() { + return this.ngModel + ? this.ngModel.$modelValue + : void 0; + } +} + +export function directive() { + return { + scope: false, + bindToController: { + title: '@copyInputValueButton' + }, + controller: CopyInputValueButtonController, + require: { + ngModel: 'ngModel' + } + }; +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/app/components/form-field/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-field/index.js b/modules/web-console/frontend/app/components/form-field/index.js index 323c30a..077ace0 100644 --- a/modules/web-console/frontend/app/components/form-field/index.js +++ b/modules/web-console/frontend/app/components/form-field/index.js @@ -16,8 +16,11 @@ */ import angular from 'angular'; +import './style.scss'; import {directive as showValidationError} from './showValidationError.directive'; +import {directive as copyInputValue} from './copyInputValueButton.directive'; export default angular .module('ignite-console.form-field', []) - .directive('ngModel', showValidationError); + .directive('ngModel', showValidationError) + .directive('copyInputValueButton', copyInputValue); http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/app/components/form-field/style.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-field/style.scss b/modules/web-console/frontend/app/components/form-field/style.scss new file mode 100644 index 0000000..2cf767f --- /dev/null +++ b/modules/web-console/frontend/app/components/form-field/style.scss @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.copy-input-value-button { + position: absolute; + top: 31px; + right: 10px; + + &:hover { + @import 'public/stylesheets/variables'; + + color: $ignite-brand-success; + cursor: pointer; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/app/components/page-profile/controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-profile/controller.js b/modules/web-console/frontend/app/components/page-profile/controller.js index c67a603..2c786f0 100644 --- a/modules/web-console/frontend/app/components/page-profile/controller.js +++ b/modules/web-console/frontend/app/components/page-profile/controller.js @@ -40,7 +40,7 @@ export default class PageProfileController { } generateToken() { - this.Confirm.confirm('Are you sure you want to change security token?') + this.Confirm.confirm('Are you sure you want to change security token?<br>If you change the token you will need to restart the agent.') .then(() => this.ui.user.token = this.LegacyUtils.randomString(20)); } http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/app/components/page-profile/template.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-profile/template.pug b/modules/web-console/frontend/app/components/page-profile/template.pug index eafde6b..fb95793 100644 --- a/modules/web-console/frontend/app/components/page-profile/template.pug +++ b/modules/web-console/frontend/app/components/page-profile/template.pug @@ -99,14 +99,20 @@ div | {{ $panel.opened ? 'Cancel security token changing...' : 'Show security token...' }} panel-content .row - .col-25 - label.required Security token: - .col-75 - label#current-security-token {{$ctrl.ui.user.token || 'No security token. Regenerate please.'}} - label - i.tipLabel.fa.fa-refresh(ng-click='$ctrl.generateToken()' bs-tooltip='' data-title='Generate random security token') - i.tipLabel.fa.fa-clipboard(ignite-copy-to-clipboard='{{$ctrl.ui.user.token}}' bs-tooltip='' data-title='Copy security token to clipboard') - i.tipLabel.icon-help(ng-if=lines bs-tooltip='' data-title='The security token is used for authorization of web agent') + .col-50 + +form-field__text({ + label: 'Security Token:', + model: '$ctrl.ui.user.token', + tip: 'The security token is used for authentication of Web agent', + name: '"securityToken"', + placeholder: 'No security token. Regenerate please.' + })( + autocomplete='security-token' + ng-disabled='::true' + copy-input-value-button='Copy security token to clipboard' + ) + .col-50 + a(ng-click='$ctrl.generateToken()') Generate Random Security Token? .row .col-50 http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/app/modules/agent/AgentModal.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/agent/AgentModal.service.js b/modules/web-console/frontend/app/modules/agent/AgentModal.service.js index 52abf6d..67e5cda 100644 --- a/modules/web-console/frontend/app/modules/agent/AgentModal.service.js +++ b/modules/web-console/frontend/app/modules/agent/AgentModal.service.js @@ -23,6 +23,7 @@ export default class AgentModal { constructor($root, $state, $modal, Messages) { const self = this; + this.$root = $root; self.$state = $state; self.Messages = Messages; @@ -36,11 +37,13 @@ export default class AgentModal { controllerAs: 'ctrl' }); - $root.$on('user', (event, user) => self.user = user); + this.off = $root.$on('user', (event, user) => self.user = user); } hide() { this.modal.hide(); + this.off(); + this.off = null; } /** @@ -86,4 +89,8 @@ export default class AgentModal { self.modal.$promise.then(self.modal.show); } + + get securityToken() { + return this.$root.user.becameToken || this.$root.user.token; + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/public/images/icons/copy.svg ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/public/images/icons/copy.svg b/modules/web-console/frontend/public/images/icons/copy.svg new file mode 100644 index 0000000..b04d4ea --- /dev/null +++ b/modules/web-console/frontend/public/images/icons/copy.svg @@ -0,0 +1,3 @@ +<svg version="1.1" viewBox="0 0 16 19" xmlns="http://www.w3.org/2000/svg"> + <path class="st0" d="m11.8 0h-10.1c-0.9 0-1.7 0.8-1.7 1.7v11.8h1.7v-11.8h10.1v-1.7zm2.5 3.4h-9.3c-0.9 0-1.7 0.8-1.7 1.7v11.8c0 0.9 0.8 1.7 1.7 1.7h9.3c0.9 0 1.7-0.8 1.7-1.7v-11.8c0-1-0.8-1.7-1.7-1.7zm-9.2 1.7h9.3v11.8h-9.3z" fill="currentColor"/> +</svg> http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/public/images/icons/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/public/images/icons/index.js b/modules/web-console/frontend/public/images/icons/index.js index 5a8d851..d101901 100644 --- a/modules/web-console/frontend/public/images/icons/index.js +++ b/modules/web-console/frontend/public/images/icons/index.js @@ -15,30 +15,31 @@ * limitations under the License. */ -export csv from './csv.svg'; -export cross from './cross.svg'; -export gear from './gear.svg'; +export alert from './alert.svg'; +export attention from './attention.svg'; +export check from './check.svg'; +export checkmark from './checkmark.svg'; export clock from './clock.svg'; -export manual from './manual.svg'; +export collapse from './collapse.svg'; +export connectedClusters from './connectedClusters.svg'; +export copy from './copy.svg'; +export cross from './cross.svg'; +export csv from './csv.svg'; export download from './download.svg'; +export exclamation from './exclamation.svg'; +export exit from './exit.svg'; +export expand from './expand.svg'; +export eyeClosed from './eyeClosed.svg'; +export eyeOpened from './eyeOpened.svg'; export filter from './filter.svg'; +export gear from './gear.svg'; +export home from './home.svg'; +export info from './info.svg'; +export lockClosed from './lockClosed.svg'; +export lockOpened from './lockOpened.svg'; +export manual from './manual.svg'; export plus from './plus.svg'; +export refresh from './refresh.svg'; export search from './search.svg'; -export checkmark from './checkmark.svg'; export sort from './sort.svg'; -export info from './info.svg'; -export connectedClusters from './connectedClusters.svg'; -export check from './check.svg'; export structure from './structure.svg'; -export alert from './alert.svg'; -export attention from './attention.svg'; -export exclamation from './exclamation.svg'; -export collapse from './collapse.svg'; -export expand from './expand.svg'; -export home from './home.svg'; -export refresh from './refresh.svg'; -export eyeOpened from './eyeOpened.svg'; -export eyeClosed from './eyeClosed.svg'; -export lockOpened from './lockOpened.svg'; -export lockClosed from './lockClosed.svg'; -export exit from './exit.svg'; http://git-wip-us.apache.org/repos/asf/ignite/blob/81b6b2d4/modules/web-console/frontend/views/templates/agent-download.tpl.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/views/templates/agent-download.tpl.pug b/modules/web-console/frontend/views/templates/agent-download.tpl.pug index fdb3a05..4da9fad 100644 --- a/modules/web-console/frontend/views/templates/agent-download.tpl.pug +++ b/modules/web-console/frontend/views/templates/agent-download.tpl.pug @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. +include /app/helpers/jade/mixins + .modal.modal--ignite.theme--ignite.center(tabindex='-1' role='dialog') .modal-dialog(ng-switch='ctrl.status') .modal-content(ng-switch-when='agentMissing') @@ -30,10 +32,17 @@ .modal-advanced-options i.fa(ng-class='showToken ? "fa-chevron-circle-down" : "fa-chevron-circle-right"' ng-click='showToken = !showToken') a(ng-click='showToken = !showToken') {{showToken ? 'Hide security token...' : 'Show security token...'}} - p(ng-show='showToken') - label.labelField Security token: {{user.becameToken || user.token}} - i.tipLabel.fa.fa-clipboard(ignite-copy-to-clipboard='{{user.becameToken || user.token}}' bs-tooltip='' data-title='Copy security token to clipboard') - i.tipLabel.icon-help(ng-if=lines bs-tooltip='' data-title='The security token is used for authorization of web agent') + div(ng-show='showToken') + +form-field__text({ + label: 'Security Token:', + model: 'ctrl.securityToken', + tip: 'The security token is used for authentication of web agent', + name: '"securityToken"' + })( + autocomplete='security-token' + ng-disabled='::true' + copy-input-value-button='Copy security token to clipboard' + ) .modal-footer button.btn-ignite.btn-ignite--link-success(ng-click='ctrl.back()') {{::ctrl.backText}}
