IGNITE-9941 Web Console: Added configuration parameter to disable signup and possibility to create user accounts by administrator.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/fe824a0e Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/fe824a0e Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/fe824a0e Branch: refs/heads/ignite-627 Commit: fe824a0e287f3b3432900357c3b09d939752ead7 Parents: 781d4e7 Author: Ilya Borisov <klast...@gmail.com> Authored: Mon Oct 29 15:36:46 2018 +0700 Committer: Alexey Kuznetsov <akuznet...@apache.org> Committed: Mon Oct 29 15:36:46 2018 +0700 ---------------------------------------------------------------------- modules/web-console/DEVNOTES.txt | 17 ++ modules/web-console/assembly/README.txt | 1 + modules/web-console/backend/app/settings.js | 4 +- .../backend/config/settings.json.sample | 6 +- modules/web-console/backend/routes/public.js | 22 +- modules/web-console/backend/services/mails.js | 24 ++- modules/web-console/backend/services/users.js | 12 +- modules/web-console/frontend/app/app.js | 6 +- .../dialog-admin-create-user/component.ts | 27 +++ .../dialog-admin-create-user/controller.ts | 78 +++++++ .../dialog-admin-create-user/index.ts | 23 ++ .../dialog-admin-create-user/state.ts | 29 +++ .../dialog-admin-create-user/template.pug | 37 ++++ .../app/components/form-signup/component.ts | 32 +++ .../app/components/form-signup/controller.ts | 46 ++++ .../app/components/form-signup/index.ts | 41 ++++ .../app/components/form-signup/style.scss | 31 +++ .../app/components/form-signup/template.pug | 105 +++++++++ .../input-dialog/input-dialog.service.ts | 12 +- .../input-dialog/input-dialog.tpl.pug | 14 ++ .../list-of-registered-users/column-defs.js | 23 -- .../list-of-registered-users/controller.js | 214 ++++++++++--------- .../list-of-registered-users/template.tpl.pug | 1 - .../app/components/page-admin/controller.ts | 28 +++ .../frontend/app/components/page-admin/index.js | 17 +- .../app/components/page-signup/component.js | 2 - .../app/components/page-signup/controller.js | 76 ------- .../app/components/page-signup/controller.ts | 66 ++++++ .../app/components/page-signup/index.js | 3 +- .../app/components/page-signup/style.scss | 10 - .../app/components/page-signup/template.pug | 94 +------- .../app/components/page-signup/types.ts | 36 ---- .../frontend/app/modules/user/Auth.service.js | 100 --------- .../frontend/app/modules/user/Auth.service.ts | 90 ++++++++ .../frontend/app/primitives/dropdown/index.pug | 3 +- .../app/primitives/form-field/checkbox.pug | 5 +- .../app/primitives/form-field/email.pug | 7 +- .../app/primitives/form-field/password.pug | 12 +- .../app/primitives/form-field/phone.pug | 5 +- .../frontend/app/utils/dialogState.ts | 56 +++++ 40 files changed, 934 insertions(+), 481 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/DEVNOTES.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/DEVNOTES.txt b/modules/web-console/DEVNOTES.txt index ba61150..a06608d 100644 --- a/modules/web-console/DEVNOTES.txt +++ b/modules/web-console/DEVNOTES.txt @@ -139,3 +139,20 @@ Unit tests are performed with Mocha framework - https://mochajs.org To launch tests on your local machine you will need: 1. In new terminal change directory to 'modules/web-console/backend' folder and execute: "npm install". 2. To start test environment and tests execute: "npm run test". + + +Web Console settings +==================== +Web Console backend could be configured with custom parameters. + +See "backend/config/settings.json.sample" for list of parameters with example value. + +If you need custom parameters, you will need create "backend/config/settings.json" and adjust values. + +Web Console settings for Docker +=============================== +Web Console backend could be configured with custom parameters. + +You may pass custom parameters with help of "-e" option. + +For example: docker run -e "server_disable_signup=true" -p 9090:80 $IMAGE_ID http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/assembly/README.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/assembly/README.txt b/modules/web-console/assembly/README.txt index dd32399..f1e114c 100644 --- a/modules/web-console/assembly/README.txt +++ b/modules/web-console/assembly/README.txt @@ -47,6 +47,7 @@ All available parameters with defaults: HTTPS key: --server:key "serve/keys/test.key" HTTPS certificate: --server:cert "serve/keys/test.crt" HTTPS passphrase: --server:keyPassphrase "password" + Disable self registration: --server:disable:signup false MongoDB URL: --mongodb:url mongodb://localhost/console Mail service: --mail:service "gmail" Signature text: --mail:sign "Kind regards, Apache Ignite Team" http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/backend/app/settings.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/app/settings.js b/modules/web-console/backend/app/settings.js index 104b66d..1c75041 100644 --- a/modules/web-console/backend/app/settings.js +++ b/modules/web-console/backend/app/settings.js @@ -68,7 +68,9 @@ module.exports = { key: fs.readFileSync(nconf.get('server:key')), cert: fs.readFileSync(nconf.get('server:cert')), passphrase: nconf.get('server:keyPassphrase') - } + }, + // eslint-disable-next-line eqeqeq + disableSignup: nconf.get('server:disable:signup') == 'true' }, mail, mongoUrl: nconf.get('mongodb:url') || 'mongodb://127.0.0.1/console', http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/backend/config/settings.json.sample ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/config/settings.json.sample b/modules/web-console/backend/config/settings.json.sample index aa93e07..4070163 100644 --- a/modules/web-console/backend/config/settings.json.sample +++ b/modules/web-console/backend/config/settings.json.sample @@ -5,8 +5,10 @@ "ssl": false, "key": "serve/keys/test.key", "cert": "serve/keys/test.crt", - "keyPassphrase": "password" - }, + "keyPassphrase": "password", + "disable": { + "signup": false + } }, "mongodb": { "url": "mongodb://localhost/console" }, http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/backend/routes/public.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/routes/public.js b/modules/web-console/backend/routes/public.js index 155b2e9..8aa3b43 100644 --- a/modules/web-console/backend/routes/public.js +++ b/modules/web-console/backend/routes/public.js @@ -17,6 +17,7 @@ 'use strict'; +const _ = require('lodash'); const express = require('express'); const passport = require('passport'); @@ -49,15 +50,22 @@ module.exports.factory = function(mongo, mailsService, usersService, authService * Register new account. */ router.post('/signup', (req, res) => { - usersService.create(req.origin(), req.body) - .then((user) => new Promise((resolve, reject) => { - req.logIn(user, {}, (err) => { - if (err) - reject(err); + const createdByAdmin = _.get(req, 'user.admin', false); - resolve(user); + usersService.create(req.origin(), req.body, createdByAdmin) + .then((user) => { + if (createdByAdmin) + return user; + + return new Promise((resolve, reject) => { + req.logIn(user, {}, (err) => { + if (err) + reject(err); + + resolve(user); + }); }); - })) + }) .then(res.api.ok) .catch(res.api.error); }); http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/backend/services/mails.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/services/mails.js b/modules/web-console/backend/services/mails.js index e0737cd..75da128 100644 --- a/modules/web-console/backend/services/mails.js +++ b/modules/web-console/backend/services/mails.js @@ -81,17 +81,27 @@ module.exports.factory = (settings) => { } /** - * Send email to user for password reset. - * @param host - * @param user + * Send email when user signed up. + * + * @param host Web Console host. + * @param user User that signed up. + * @param createdByAdmin Whether user was created by admin. */ - emailUserSignUp(host, user) { + emailUserSignUp(host, user, createdByAdmin) { const resetLink = `${host}/password/reset?token=${user.resetPasswordToken}`; - return this.send(user, `Thanks for signing up for ${settings.mail.greeting}.`, + const sbj = createdByAdmin + ? 'Account was created for' + : 'Thanks for signing up for'; + + const reason = createdByAdmin + ? 'administrator created account for you' + : 'you have signed up'; + + return this.send(user, `${sbj} ${settings.mail.greeting}.`, `Hello ${user.firstName} ${user.lastName}!<br><br>` + - `You are receiving this email because you have signed up to use <a href="${host}">${settings.mail.greeting}</a>.<br><br>` + - 'If you have not done the sign up and do not know what this email is about, please ignore it.<br>' + + `You are receiving this email because ${reason} to use <a href="${host}">${settings.mail.greeting}</a>.<br><br>` + + 'If you do not know what this email is about, please ignore it.<br>' + 'You may reset the password by clicking on the following link, or paste this into your browser:<br><br>' + `<a href="${resetLink}">${resetLink}</a>`); } http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/backend/services/users.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/services/users.js b/modules/web-console/backend/services/users.js index 43bceda..79578c5 100644 --- a/modules/web-console/backend/services/users.js +++ b/modules/web-console/backend/services/users.js @@ -42,17 +42,21 @@ module.exports.factory = (errors, settings, mongo, spacesService, mailsService, /** * Save profile information. * - * @param {String} host - The host - * @param {Object} user - The user + * @param {String} host - The host. + * @param {Object} user - The user. + * @param {Object} createdByAdmin - Whether user created by admin. * @returns {Promise.<mongo.ObjectId>} that resolves account id of merge operation. */ - static create(host, user) { + static create(host, user, createdByAdmin) { return mongo.Account.count().exec() .then((cnt) => { user.admin = cnt === 0; user.registered = new Date(); user.token = utilsService.randomString(settings.tokenLength); + if (settings.server.disableSignup && !user.admin && !createdByAdmin) + throw new errors.ServerErrorException('Sign-up is not allowed. Ask your Web Console administrator to create account for you.'); + return new mongo.Account(user); }) .then((created) => { @@ -74,7 +78,7 @@ module.exports.factory = (errors, settings, mongo, spacesService, mailsService, return registered.save() .then(() => mongo.Space.create({name: 'Personal space', owner: registered._id})) .then(() => { - mailsService.emailUserSignUp(host, registered); + mailsService.emailUserSignUp(host, registered, createdByAdmin); return registered; }); http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/app.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.js index c890fa4..47374aa 100644 --- a/modules/web-console/frontend/app/app.js +++ b/modules/web-console/frontend/app/app.js @@ -112,6 +112,7 @@ import id8 from './filters/id8.filter'; // Components import igniteListOfRegisteredUsers from './components/list-of-registered-users'; +import dialogAdminCreateUser from './components/dialog-admin-create-user'; import IgniteActivitiesUserDialog from './components/activities-user-dialog'; import './components/input-dialog'; import webConsoleHeader from './components/web-console-header'; @@ -156,6 +157,7 @@ import pagePasswordReset from './components/page-password-reset'; import pageSignup from './components/page-signup'; import pageSignin from './components/page-signin'; import pageForgotPassword from './components/page-forgot-password'; +import formSignup from './components/form-signup'; import igniteServices from './services'; @@ -245,6 +247,7 @@ export default angular.module('ignite-console', [ connectedClusters.name, connectedClustersDialog.name, igniteListOfRegisteredUsers.name, + dialogAdminCreateUser.name, pageProfile.name, pageLanding.name, pagePasswordChanged.name, @@ -260,7 +263,8 @@ export default angular.module('ignite-console', [ igniteChartSelector.name, statusOutput.name, progressLine.name, - formField.name + formField.name, + formSignup.name ]) .service('$exceptionHandler', $exceptionHandler) // Directives. http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/dialog-admin-create-user/component.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/dialog-admin-create-user/component.ts b/modules/web-console/frontend/app/components/dialog-admin-create-user/component.ts new file mode 100644 index 0000000..3a60d93 --- /dev/null +++ b/modules/web-console/frontend/app/components/dialog-admin-create-user/component.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +import template from './template.pug'; +import {DialogAdminCreateUser} from './controller'; + +export default { + template, + controller: DialogAdminCreateUser, + bindings: { + close: '&onHide' + } +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/dialog-admin-create-user/controller.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/dialog-admin-create-user/controller.ts b/modules/web-console/frontend/app/components/dialog-admin-create-user/controller.ts new file mode 100644 index 0000000..e8a29e1 --- /dev/null +++ b/modules/web-console/frontend/app/components/dialog-admin-create-user/controller.ts @@ -0,0 +1,78 @@ +/* + * 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. + */ + +import Auth from '../../modules/user/Auth.service'; +import MessagesFactory from '../../services/Messages.service'; +import FormUtilsFactoryFactory from '../../services/FormUtils.service'; +import LoadingServiceFactory from '../../modules/loading/loading.service'; +import {ISignupData} from '../form-signup'; + +export class DialogAdminCreateUser { + close: ng.ICompiledExpression; + + form: ng.IFormController; + + data: ISignupData = { + email: null, + password: null, + firstName: null, + lastName: null, + company: null, + country: null + }; + + serverError: string | null = null; + + static $inject = ['Auth', 'IgniteMessages', 'IgniteFormUtils', 'IgniteLoading']; + + constructor( + private Auth: Auth, + private IgniteMessages: ReturnType<typeof MessagesFactory>, + private IgniteFormUtils: ReturnType<typeof FormUtilsFactoryFactory>, + private loading: ReturnType<typeof LoadingServiceFactory> + ) {} + + canSubmitForm(form: DialogAdminCreateUser['form']) { + return form.$error.server ? true : !form.$invalid; + } + + setServerError(error: DialogAdminCreateUser['serverError']) { + this.serverError = error; + } + + createUser() { + this.IgniteFormUtils.triggerValidation(this.form); + + this.setServerError(null); + + if (!this.canSubmitForm(this.form)) + return; + + this.loading.start('createUser'); + + this.Auth.signup(this.data, false) + .then(() => { + this.IgniteMessages.showInfo(`User ${this.data.email} created`); + this.close({}); + }) + .catch((res) => { + this.loading.finish('createUser'); + this.IgniteMessages.showError(null, res.data); + this.setServerError(res.data); + }); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/dialog-admin-create-user/index.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/dialog-admin-create-user/index.ts b/modules/web-console/frontend/app/components/dialog-admin-create-user/index.ts new file mode 100644 index 0000000..5a23368 --- /dev/null +++ b/modules/web-console/frontend/app/components/dialog-admin-create-user/index.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import component from './component'; +import {registerState} from './state'; + +export default angular.module('ignite-console.dialog-admin-create-user', []) + .run(registerState) + .component('dialogAdminCreateUser', component); http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/dialog-admin-create-user/state.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/dialog-admin-create-user/state.ts b/modules/web-console/frontend/app/components/dialog-admin-create-user/state.ts new file mode 100644 index 0000000..c64238e --- /dev/null +++ b/modules/web-console/frontend/app/components/dialog-admin-create-user/state.ts @@ -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. + */ + +import {UIRouter} from '@uirouter/angularjs'; +import {dialogState} from '../../utils/dialogState'; + +registerState.$inject = ['$uiRouter']; + +export function registerState(router: UIRouter) { + router.stateRegistry.register({ + ...dialogState('dialog-admin-create-user'), + name: 'base.settings.admin.createUser', + url: '/create-user' + }); +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/dialog-admin-create-user/template.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/dialog-admin-create-user/template.pug b/modules/web-console/frontend/app/components/dialog-admin-create-user/template.pug new file mode 100644 index 0000000..0a9f2b4 --- /dev/null +++ b/modules/web-console/frontend/app/components/dialog-admin-create-user/template.pug @@ -0,0 +1,37 @@ +//- + 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. + +.modal-dialog(ng-click='$event.stopPropagation()' ) + form.modal-content( + name='$ctrl.form' novalidate + ignite-loading='createUser' + ignite-loading-text='Creating userâ¦' + ) + .modal-header + h4.modal-title Create User + + button.close(type='button' aria-label='Close' ng-click='$ctrl.close()') + svg(ignite-icon="cross") + .modal-body + form-signup( + outer-form='$ctrl.form' + ng-model='$ctrl.data' + server-error='$ctrl.serverError' + ) + .modal-footer + div + button.btn-ignite.btn-ignite--link-success(ng-click='$ctrl.close()') Cancel + button.btn-ignite.btn-ignite--success(ng-click='$ctrl.createUser()') Create user http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/form-signup/component.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-signup/component.ts b/modules/web-console/frontend/app/components/form-signup/component.ts new file mode 100644 index 0000000..79863db --- /dev/null +++ b/modules/web-console/frontend/app/components/form-signup/component.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import template from './template.pug'; +import './style.scss'; +import {FormSignup} from './controller'; + +export const component: ng.IComponentOptions = { + template, + controller: FormSignup, + bindings: { + outerForm: '<', + serverError: '<' + }, + require: { + ngModel: 'ngModel' + } +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/form-signup/controller.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-signup/controller.ts b/modules/web-console/frontend/app/components/form-signup/controller.ts new file mode 100644 index 0000000..eedf6d0 --- /dev/null +++ b/modules/web-console/frontend/app/components/form-signup/controller.ts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +import CountriesService from '../../services/Countries.service'; +import {ISignupFormController} from '.'; + +export class FormSignup implements ng.IPostLink, ng.IOnDestroy, ng.IOnChanges { + static $inject = ['IgniteCountries']; + + constructor(private Countries: ReturnType<typeof CountriesService>) {} + + countries = this.Countries.getAll(); + + innerForm: ISignupFormController; + outerForm: ng.IFormController; + ngModel: ng.INgModelController; + serverError: string | null = null; + + $postLink() { + this.outerForm.$addControl(this.innerForm); + this.innerForm.email.$validators.server = () => !this.serverError; + } + + $onDestroy() { + this.outerForm.$removeControl(this.innerForm); + } + + $onChanges(changes: {serverError: ng.IChangesObject<FormSignup['serverError']>}) { + if (changes.serverError && this.innerForm) + this.innerForm.email.$validate(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/form-signup/index.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-signup/index.ts b/modules/web-console/frontend/app/components/form-signup/index.ts new file mode 100644 index 0000000..1798aa0 --- /dev/null +++ b/modules/web-console/frontend/app/components/form-signup/index.ts @@ -0,0 +1,41 @@ +/* + * 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. + */ + +import {component} from './component'; + +export default angular.module('ignite-console.form-signup', []) + .component('formSignup', component); + +export interface ISignupData { + email: string, + password: string, + firstName: string, + lastName: string, + phone?: string, + company: string, + country: string +} + +export interface ISignupFormController extends ng.IFormController { + email: ng.INgModelController, + password: ng.INgModelController, + firstName: ng.INgModelController, + lastName: ng.INgModelController, + phone: ng.INgModelController, + company: ng.INgModelController, + country: ng.INgModelController +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/form-signup/style.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-signup/style.scss b/modules/web-console/frontend/app/components/form-signup/style.scss new file mode 100644 index 0000000..e80b510 --- /dev/null +++ b/modules/web-console/frontend/app/components/form-signup/style.scss @@ -0,0 +1,31 @@ +/* + * 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. + */ + +form-signup { + .form-signup__grid { + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 1fr; + + .span-1 { + grid-column: span 1; + } + .span-2 { + grid-column: span 2; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/form-signup/template.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-signup/template.pug b/modules/web-console/frontend/app/components/form-signup/template.pug new file mode 100644 index 0000000..375ea95 --- /dev/null +++ b/modules/web-console/frontend/app/components/form-signup/template.pug @@ -0,0 +1,105 @@ +//- + 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. + +include /app/helpers/jade/mixins + +.form-signup__grid(ng-form='signup' ng-ref='$ctrl.innerForm' ng-ref-read='form') + .span-2 + +form-field__email({ + label: 'Email:', + model: '$ctrl.ngModel.$viewValue.email', + name: '"email"', + placeholder: 'Input email', + required: true + })( + ng-model-options='{allowInvalid: true}' + autocomplete='email' + ignite-auto-focus + ) + +form-field__error({error: 'server', message: `{{$ctrl.serverError}}`}) + .span-1 + +form-field__password({ + label: 'Password:', + model: '$ctrl.ngModel.$viewValue.password', + name: '"password"', + placeholder: 'Input password', + required: true + })( + autocomplete='new-password' + ) + .span-1 + +form-field__password({ + label: 'Confirm:', + model: 'confirm', + name: '"confirm"', + placeholder: 'Confirm password', + required: true + })( + ignite-match='$ctrl.ngModel.$viewValue.password' + autocomplete='off' + ) + .span-1 + +form-field__text({ + label: 'First name:', + model: '$ctrl.ngModel.$viewValue.firstName', + name: '"firstName"', + placeholder: 'Input first name', + required: true + })( + autocomplete='given-name' + ) + .span-1 + +form-field__text({ + label: 'Last name:', + model: '$ctrl.ngModel.$viewValue.lastName', + name: '"lastName"', + placeholder: 'Input last name', + required: true + })( + autocomplete='family-name' + ) + .span-1 + +form-field__phone({ + label: 'Phone:', + model: '$ctrl.ngModel.$viewValue.phone', + name: '"phone"', + placeholder: 'Input phone (ex.: +15417543010)', + optional: true + })( + autocomplete='tel' + ) + .span-1 + +form-field__dropdown({ + label: 'Country:', + model: '$ctrl.ngModel.$viewValue.country', + name: '"country"', + required: true, + placeholder: 'Choose your country', + options: '$ctrl.countries' + })( + autocomplete='country' + ) + .span-2 + +form-field__text({ + label: 'Company:', + model: '$ctrl.ngModel.$viewValue.company', + name: '"company"', + placeholder: 'Input company name', + required: true + })( + ignite-on-enter-focus-move='countryInput' + autocomplete='organization' + ) http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.ts b/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.ts index 4d8ebd7..b2a241f 100644 --- a/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.ts +++ b/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.ts @@ -20,7 +20,7 @@ import controller from './input-dialog.controller'; import templateUrl from './input-dialog.tpl.pug'; import {CancellationError} from 'app/errors/CancellationError'; -type InputModes = 'text' | 'number' | 'date' | 'time' | 'date-and-time'; +type InputModes = 'text' | 'number' | 'email' | 'date' | 'time' | 'date-and-time'; interface ValidationFunction<T> { (value: T): boolean @@ -131,6 +131,16 @@ export default class InputDialog { } /** + * Open input dialog to configure custom e-mail. + * + * @param options Object with settings for rendering e-mail input. + * @return User input. + */ + email(options: InputOptions<string>) { + return this.dialogFabric({mode: 'email', ...options}); + } + + /** * Open input dialog to configure custom date value. * * @param options Settings for rendering date input. http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/input-dialog/input-dialog.tpl.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/input-dialog/input-dialog.tpl.pug b/modules/web-console/frontend/app/components/input-dialog/input-dialog.tpl.pug index 4674c97..d79cb56 100644 --- a/modules/web-console/frontend/app/components/input-dialog/input-dialog.tpl.pug +++ b/modules/web-console/frontend/app/components/input-dialog/input-dialog.tpl.pug @@ -39,6 +39,7 @@ include /app/helpers/jade/mixins ignite-form-field-input-autofocus='true' ignite-on-enter='form.$valid && ctrl.confirm()' ) + .row(ng-switch-when='number') .col-100 +form-field__number({ @@ -53,6 +54,19 @@ include /app/helpers/jade/mixins required: true }) + .row(ng-switch-when='email') + .col-100 + +form-field__email({ + label: '{{ ctrl.options.label }}', + model: 'ctrl.options.value', + name: '"email"', + required: true, + placeholder: 'Input email' + })( + ignite-form-field-input-autofocus='true' + ignite-on-enter='form.$valid && ctrl.confirm()' + ) + .row(ng-switch-when='date') .col-100 .form-field--inline http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/list-of-registered-users/column-defs.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/column-defs.js b/modules/web-console/frontend/app/components/list-of-registered-users/column-defs.js index 419a063..2094f0c 100644 --- a/modules/web-console/frontend/app/components/list-of-registered-users/column-defs.js +++ b/modules/web-console/frontend/app/components/list-of-registered-users/column-defs.js @@ -25,34 +25,11 @@ const MODEL_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip dat const CACHE_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-database'></i>${ICON_SORT}</div>`; const IGFS_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-folder-o'></i>${ICON_SORT}</div>`; -const ACTIONS_TEMPLATE = ` -<div class='text-center ui-grid-cell-actions'> - <a class='btn btn-default dropdown-toggle' bs-dropdown='' data-placement='bottom-right' data-container='.panel'> - <i class='fa fa-gear'></i> - <span class='caret'></span> - </a> - <ul class='dropdown-menu' role='menu'> - <li ng-show='row.entity._id != $root.user._id'> - <a ng-click='grid.api.becomeUser(row.entity)'>Become this user</a> - </li> - <li ng-show='row.entity._id != $root.user._id'> - <a ng-click='grid.api.toggleAdmin(row.entity)' ng-if='row.entity.admin && row.entity._id !== $root.user._id'>Revoke admin</a> - <a ng-click='grid.api.toggleAdmin(row.entity)' ng-if='!row.entity.admin && row.entity._id !== $root.user._id'>Grant admin</a> - </li> - <li ng-show='row.entity._id != $root.user._id'> - <a ng-click='grid.api.removeUser(row.entity)'>Remove user</a> - </li> - <li> - <a ng-click='grid.api.showActivities(row.entity)'>Activity detail</a> - </li> -</div>`; - const EMAIL_TEMPLATE = '<div class="ui-grid-cell-contents"><a bs-tooltip data-title="{{ COL_FIELD }}" ng-href="mailto:{{ COL_FIELD }}">{{ COL_FIELD }}</a></div>'; const DATE_WITH_TITLE = '<div class="ui-grid-cell-contents"><label bs-tooltip data-title="{{ COL_FIELD | date:\'M/d/yy HH:mm\' }}">{{ COL_FIELD | date:"M/d/yy HH:mm" }}</label></div>'; const VALUE_WITH_TITLE = '<div class="ui-grid-cell-contents"><label bs-tooltip data-title="{{ COL_FIELD }}">{{ COL_FIELD }}</label></div>'; export default [ - {name: 'actions', enableHiding: false, displayName: 'Actions', categoryDisplayName: 'Actions', cellTemplate: ACTIONS_TEMPLATE, field: 'actions', minWidth: 70, width: 70, enableFiltering: false, enableSorting: false, visible: false}, {name: 'user', enableHiding: false, displayName: 'User', categoryDisplayName: 'User', field: 'userName', cellTemplate: USER_TEMPLATE, minWidth: 160, enableFiltering: true, pinnedLeft: true, filter: { placeholder: 'Filter by name...' }}, {name: 'email', displayName: 'Email', categoryDisplayName: 'Email', field: 'email', cellTemplate: EMAIL_TEMPLATE, minWidth: 160, width: 220, enableFiltering: true, filter: { placeholder: 'Filter by email...' }}, {name: 'company', displayName: 'Company', categoryDisplayName: 'Company', field: 'company', cellTemplate: VALUE_WITH_TITLE, minWidth: 180, enableFiltering: true, filter: { placeholder: 'Filter by company...' }}, http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/list-of-registered-users/controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/controller.js b/modules/web-console/frontend/app/components/list-of-registered-users/controller.js index f8b2797..6a7e098 100644 --- a/modules/web-console/frontend/app/components/list-of-registered-users/controller.js +++ b/modules/web-console/frontend/app/components/list-of-registered-users/controller.js @@ -15,11 +15,13 @@ * limitations under the License. */ -import headerTemplate from 'app/primitives/ui-grid-header/index.tpl.pug'; +import _ from 'lodash'; import columnDefs from './column-defs'; import categories from './categories'; +import headerTemplate from 'app/primitives/ui-grid-header/index.tpl.pug'; + const rowTemplate = `<div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.uid" ui-grid-one-bind-id-grid="rowRenderIndex + '-' + col.uid + '-cell'" @@ -36,120 +38,80 @@ export default class IgniteListOfRegisteredUsersCtrl { static $inject = ['$scope', '$state', '$filter', 'User', 'uiGridGroupingConstants', 'uiGridPinningConstants', 'IgniteAdminData', 'IgniteNotebookData', 'IgniteConfirm', 'IgniteActivitiesUserDialog']; constructor($scope, $state, $filter, User, uiGridGroupingConstants, uiGridPinningConstants, AdminData, NotebookData, Confirm, ActivitiesUserDialog) { - const $ctrl = this; + this.$state = $state; + this.AdminData = AdminData; + this.ActivitiesDialogFactory = ActivitiesUserDialog; + this.Confirm = Confirm; + this.User = User; + this.NotebookData = NotebookData; const dtFilter = $filter('date'); - $ctrl.groupBy = 'user'; + this.groupBy = 'user'; - $ctrl.selected = []; + this.selected = []; - $ctrl.params = { + this.params = { startDate: new Date(), endDate: new Date() }; - $ctrl.uiGridPinningConstants = uiGridPinningConstants; - $ctrl.uiGridGroupingConstants = uiGridGroupingConstants; - - User.read().then((user) => $ctrl.user = user); - - const becomeUser = () => { - const user = this.gridApi.selection.legacyGetSelectedRows()[0]; - - AdminData.becomeUser(user._id) - .then(() => User.load()) - .then(() => $state.go('default-state')) - .then(() => NotebookData.load()); - }; - - const removeUser = () => { - const user = this.gridApi.selection.legacyGetSelectedRows()[0]; - - Confirm.confirm(`Are you sure you want to remove user: "${user.userName}"?`) - .then(() => AdminData.removeUser(user)) - .then(() => { - const i = _.findIndex($ctrl.gridOptions.data, (u) => u._id === user._id); + this.uiGridPinningConstants = uiGridPinningConstants; + this.uiGridGroupingConstants = uiGridGroupingConstants; - if (i >= 0) { - $ctrl.gridOptions.data.splice(i, 1); - $ctrl.gridApi.selection.clearSelectedRows(); - } - - $ctrl.adjustHeight($ctrl.gridOptions.data.length); - - return $ctrl._refreshRows(); - }); - }; - - const toggleAdmin = () => { - const user = this.gridApi.selection.legacyGetSelectedRows()[0]; - - if (user.adminChanging) - return; - - user.adminChanging = true; - - AdminData.toggleAdmin(user) - .finally(() => { - $ctrl._updateSelected(); - - user.adminChanging = false; - }); - }; - - const showActivities = () => { - const user = this.gridApi.selection.legacyGetSelectedRows()[0]; - - return new ActivitiesUserDialog({ user }); - }; + User.read().then((user) => this.user = user); const companiesExcludeFilter = (renderableRows) => { - if (_.isNil($ctrl.params.companiesExclude)) + if (_.isNil(this.params.companiesExclude)) return renderableRows; _.forEach(renderableRows, (row) => { - row.visible = _.isEmpty($ctrl.params.companiesExclude) || - row.entity.company.toLowerCase().indexOf($ctrl.params.companiesExclude.toLowerCase()) === -1; + row.visible = _.isEmpty(this.params.companiesExclude) || + row.entity.company.toLowerCase().indexOf(this.params.companiesExclude.toLowerCase()) === -1; }); return renderableRows; }; - $ctrl.actionOptions = [ + this.actionOptions = [ { action: 'Become this user', - click: becomeUser.bind(this), + click: () => this.becomeUser(), available: true }, { action: 'Revoke admin', - click: toggleAdmin.bind(this), + click: () => this.toggleAdmin(), available: true }, { action: 'Grant admin', - click: toggleAdmin.bind(this), + click: () => this.toggleAdmin(), available: false }, { + action: 'Add user', + sref: '.createUser', + available: true + }, + { action: 'Remove user', - click: removeUser.bind(this), + click: () => this.removeUser(), available: true }, { action: 'Activity detail', - click: showActivities.bind(this), + click: () => this.showActivities(), available: true } ]; - $ctrl._userGridOptions = { + this._userGridOptions = { columnDefs, categories }; - $ctrl.gridOptions = { + this.gridOptions = { data: [], columnDefs, @@ -176,18 +138,18 @@ export default class IgniteListOfRegisteredUsersCtrl { rowIdentity: (row) => row._id, getRowIdentity: (row) => row._id, onRegisterApi: (api) => { - $ctrl.gridApi = api; + this.gridApi = api; - api.selection.on.rowSelectionChanged($scope, $ctrl._updateSelected.bind($ctrl)); - api.selection.on.rowSelectionChangedBatch($scope, $ctrl._updateSelected.bind($ctrl)); + api.selection.on.rowSelectionChanged($scope, this._updateSelected.bind(this)); + api.selection.on.rowSelectionChangedBatch($scope, this._updateSelected.bind(this)); - api.core.on.filterChanged($scope, $ctrl._filteredRows.bind($ctrl)); - api.core.on.rowsVisibleChanged($scope, $ctrl._filteredRows.bind($ctrl)); + api.core.on.filterChanged($scope, this._filteredRows.bind(this)); + api.core.on.rowsVisibleChanged($scope, this._filteredRows.bind(this)); api.grid.registerRowsProcessor(companiesExcludeFilter, 50); - $scope.$watch(() => $ctrl.gridApi.grid.getVisibleRows().length, (rows) => $ctrl.adjustHeight(rows)); - $scope.$watch(() => $ctrl.params.companiesExclude, () => $ctrl.gridApi.grid.refreshRows()); + $scope.$watch(() => this.gridApi.grid.getVisibleRows().length, (rows) => this.adjustHeight(rows)); + $scope.$watch(() => this.params.companiesExclude, () => this.gridApi.grid.refreshRows()); } }; @@ -197,20 +159,20 @@ export default class IgniteListOfRegisteredUsersCtrl { const reloadUsers = (params) => { AdminData.loadUsers(params) .then((data) => { - $ctrl.gridOptions.data = data; + this.gridOptions.data = data; - $ctrl.companies = _.values(_.groupBy(data, 'company')); - $ctrl.countries = _.values(_.groupBy(data, 'countryCode')); + this.companies = _.values(_.groupBy(data, 'company')); + this.countries = _.values(_.groupBy(data, 'countryCode')); - $ctrl._refreshRows(); + this._refreshRows(); }); }; const filterDates = _.debounce(() => { - const sdt = $ctrl.params.startDate; - const edt = $ctrl.params.endDate; + const sdt = this.params.startDate; + const edt = this.params.endDate; - $ctrl.exporterCsvFilename = `web_console_users_${dtFilter(sdt, 'yyyy_MM')}.csv`; + this.exporterCsvFilename = `web_console_users_${dtFilter(sdt, 'yyyy_MM')}.csv`; const startDate = Date.UTC(sdt.getFullYear(), sdt.getMonth(), 1); const endDate = Date.UTC(edt.getFullYear(), edt.getMonth() + 1, 1); @@ -218,8 +180,9 @@ export default class IgniteListOfRegisteredUsersCtrl { reloadUsers({ startDate, endDate }); }, 250); - $scope.$watch(() => $ctrl.params.startDate, filterDates); - $scope.$watch(() => $ctrl.params.endDate, filterDates); + $scope.$on('userCreated', filterDates); + $scope.$watch(() => this.params.startDate, filterDates); + $scope.$watch(() => this.params.endDate, filterDates); } adjustHeight(rows) { @@ -233,36 +196,95 @@ export default class IgniteListOfRegisteredUsersCtrl { _filteredRows() { const filtered = _.filter(this.gridApi.grid.rows, ({ visible}) => visible); - const entities = _.map(filtered, 'entity'); - this.filteredRows = entities; + this.filteredRows = _.map(filtered, 'entity'); } _updateSelected() { const ids = this.gridApi.selection.legacyGetSelectedRows().map(({ _id }) => _id).sort(); + if (!_.isEqual(ids, this.selected)) + this.selected = ids; + if (ids.length) { const user = this.gridApi.selection.legacyGetSelectedRows()[0]; const other = this.user._id !== user._id; - this.actionOptions[1].available = other && user.admin; - this.actionOptions[2].available = other && !user.admin; - - this.actionOptions[0].available = other; - this.actionOptions[3].available = other; + this.actionOptions[0].available = other; // Become this user. + this.actionOptions[1].available = other && user.admin; // Revoke admin. + this.actionOptions[2].available = other && !user.admin; // Grant admin. + this.actionOptions[4].available = other; // Remove user. + this.actionOptions[5].available = true; // Activity detail. + } + else { + this.actionOptions[0].available = false; // Become this user. + this.actionOptions[1].available = false; // Revoke admin. + this.actionOptions[2].available = false; // Grant admin. + this.actionOptions[4].available = false; // Remove user. + this.actionOptions[5].available = false; // Activity detail. } - - if (!_.isEqual(ids, this.selected)) - this.selected = ids; } _refreshRows() { if (this.gridApi) { this.gridApi.grid.refreshRows() - .then(() => this.selected.length && this._updateSelected()); + .then(() => this._updateSelected()); } } + becomeUser() { + const user = this.gridApi.selection.legacyGetSelectedRows()[0]; + + this.AdminData.becomeUser(user._id) + .then(() => this.User.load()) + .then(() => this.$state.go('default-state')) + .then(() => this.NotebookData.load()); + } + + toggleAdmin() { + if (!this.gridApi) + return; + + const user = this.gridApi.selection.legacyGetSelectedRows()[0]; + + if (user.adminChanging) + return; + + user.adminChanging = true; + + this.AdminData.toggleAdmin(user) + .finally(() => { + this._updateSelected(); + + user.adminChanging = false; + }); + } + + removeUser() { + const user = this.gridApi.selection.legacyGetSelectedRows()[0]; + + this.Confirm.confirm(`Are you sure you want to remove user: "${user.userName}"?`) + .then(() => this.AdminData.removeUser(user)) + .then(() => { + const i = _.findIndex(this.gridOptions.data, (u) => u._id === user._id); + + if (i >= 0) { + this.gridOptions.data.splice(i, 1); + this.gridApi.selection.clearSelectedRows(); + } + + this.adjustHeight(this.gridOptions.data.length); + + return this._refreshRows(); + }); + } + + showActivities() { + const user = this.gridApi.selection.legacyGetSelectedRows()[0]; + + return new this.ActivitiesDialogFactory({ user }); + } + groupByUser() { this.groupBy = 'user'; http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/list-of-registered-users/template.tpl.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/template.tpl.pug b/modules/web-console/frontend/app/components/list-of-registered-users/template.tpl.pug index d32b64d..ff1c851 100644 --- a/modules/web-console/frontend/app/components/list-of-registered-users/template.tpl.pug +++ b/modules/web-console/frontend/app/components/list-of-registered-users/template.tpl.pug @@ -74,7 +74,6 @@ ul.tabs.tabs--blue label: 'Actions', model: '$ctrl.action', name: 'action', - disabled: '!$ctrl.selected.length', options: '$ctrl.actionOptions' }) http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-admin/controller.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-admin/controller.ts b/modules/web-console/frontend/app/components/page-admin/controller.ts new file mode 100644 index 0000000..45a5c0a --- /dev/null +++ b/modules/web-console/frontend/app/components/page-admin/controller.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +import UserNotificationsService from '../user-notifications/service'; + +export default class PageAdminCtrl { + static $inject = ['UserNotifications']; + + constructor(private UserNotifications: UserNotificationsService) {} + + changeUserNotifications() { + this.UserNotifications.editor(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-admin/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-admin/index.js b/modules/web-console/frontend/app/components/page-admin/index.js index 232d889..8b26f2c 100644 --- a/modules/web-console/frontend/app/components/page-admin/index.js +++ b/modules/web-console/frontend/app/components/page-admin/index.js @@ -17,23 +17,12 @@ import './style.scss'; +import controller from './controller'; import templateUrl from './template.tpl.pug'; -class PageAdminCtrl { - static $inject = ['UserNotifications']; - - constructor(UserNotifications) { - this.UserNotifications = UserNotifications; - } - - changeUserNotifications() { - this.UserNotifications.editor(); - } -} - export default angular .module('ignite-console.page-admin', []) .component('pageAdmin', { - templateUrl, - controller: PageAdminCtrl + controller, + templateUrl }); http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-signup/component.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/component.js b/modules/web-console/frontend/app/components/page-signup/component.js index 789a681..968ff39 100644 --- a/modules/web-console/frontend/app/components/page-signup/component.js +++ b/modules/web-console/frontend/app/components/page-signup/component.js @@ -15,8 +15,6 @@ * limitations under the License. */ -import angular from 'angular'; - import template from './template.pug'; import controller from './controller'; import './style.scss'; http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-signup/controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/controller.js b/modules/web-console/frontend/app/components/page-signup/controller.js deleted file mode 100644 index 1039776..0000000 --- a/modules/web-console/frontend/app/components/page-signup/controller.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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. - */ - -export default class PageSignup { - /** @type {import('./types').ISignupFormController} */ - form; - /** @type {import('./types').ISignupData} */ - data = { - email: null, - password: null, - firstName: null, - lastName: null, - company: null, - country: null - }; - /** @type {string} */ - serverError = null; - - static $inject = ['IgniteCountries', 'Auth', 'IgniteMessages', 'IgniteFormUtils']; - - /** - * @param {ReturnType<typeof import('app/services/Countries.service').default>} Countries - * @param {import('app/modules/user/Auth.service').default} Auth - * @param {ReturnType<typeof import('app/services/Messages.service').default>} IgniteMessages - * @param {ReturnType<typeof import('app/services/FormUtils.service').default>} IgniteFormUtils - */ - constructor(Countries, Auth, IgniteMessages, IgniteFormUtils) { - this.Auth = Auth; - this.IgniteMessages = IgniteMessages; - this.countries = Countries.getAll(); - this.IgniteFormUtils = IgniteFormUtils; - } - - /** @param {import('./types').ISignupFormController} form */ - canSubmitForm(form) { - return form.$error.server ? true : !form.$invalid; - } - - $postLink() { - this.form.email.$validators.server = () => !this.serverError; - } - - /** @param {string} error */ - setServerError(error) { - this.serverError = error; - this.form.email.$validate(); - } - - signup() { - this.IgniteFormUtils.triggerValidation(this.form); - - this.setServerError(null); - - if (!this.canSubmitForm(this.form)) - return; - - return this.Auth.signnup(this.data).catch((res) => { - this.IgniteMessages.showError(null, res.data); - this.setServerError(res.data); - }); - } -} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-signup/controller.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/controller.ts b/modules/web-console/frontend/app/components/page-signup/controller.ts new file mode 100644 index 0000000..ddf77be --- /dev/null +++ b/modules/web-console/frontend/app/components/page-signup/controller.ts @@ -0,0 +1,66 @@ +/* + * 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. + */ + +import Auth from '../../modules/user/Auth.service'; +import MessagesFactory from '../../services/Messages.service'; +import FormUtilsFactoryFactory from '../../services/FormUtils.service'; +import {ISignupData} from '../form-signup'; + +export default class PageSignup { + form: ng.IFormController; + + data: ISignupData = { + email: null, + password: null, + firstName: null, + lastName: null, + company: null, + country: null + }; + + serverError: string | null = null; + + static $inject = ['Auth', 'IgniteMessages', 'IgniteFormUtils']; + + constructor( + private Auth: Auth, + private IgniteMessages: ReturnType<typeof MessagesFactory>, + private IgniteFormUtils: ReturnType<typeof FormUtilsFactoryFactory> + ) {} + + canSubmitForm(form: PageSignup['form']) { + return form.$error.server ? true : !form.$invalid; + } + + setServerError(error: PageSignup['serverError']) { + this.serverError = error; + } + + signup() { + this.IgniteFormUtils.triggerValidation(this.form); + + this.setServerError(null); + + if (!this.canSubmitForm(this.form)) + return; + + return this.Auth.signup(this.data).catch((res) => { + this.IgniteMessages.showError(null, res.data); + this.setServerError(res.data); + }); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-signup/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/index.js b/modules/web-console/frontend/app/components/page-signup/index.js index 4efadb5..001d269 100644 --- a/modules/web-console/frontend/app/components/page-signup/index.js +++ b/modules/web-console/frontend/app/components/page-signup/index.js @@ -22,7 +22,8 @@ import {registerState} from './run'; export default angular .module('ignite-console.page-signup', [ 'ui.router', - 'ignite-console.user' + 'ignite-console.user', + 'ignite-console.form-signup' ]) .component('pageSignup', component) .run(registerState); http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-signup/style.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/style.scss b/modules/web-console/frontend/app/components/page-signup/style.scss index 93167ce..98c4729 100644 --- a/modules/web-console/frontend/app/components/page-signup/style.scss +++ b/modules/web-console/frontend/app/components/page-signup/style.scss @@ -41,16 +41,6 @@ page-signup { margin-left: auto; } } - - form { - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr 1fr; - - .full-width { - grid-column: 1 / 3; - } - } } .page-signup__has-account-message { http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-signup/template.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/template.pug b/modules/web-console/frontend/app/components/page-signup/template.pug index 11bb50a..5584dfc 100644 --- a/modules/web-console/frontend/app/components/page-signup/template.pug +++ b/modules/web-console/frontend/app/components/page-signup/template.pug @@ -22,95 +22,13 @@ web-console-header .container--responsive.body-container section - -var form = '$ctrl.form' h3 Don't Have An Account? - form(name=form novalidate ng-submit='$ctrl.signup()') - .full-width - +form-field__email({ - label: 'Email:', - model: '$ctrl.data.email', - name: '"email"', - placeholder: 'Input email', - required: true - })( - ng-model-options='{allowInvalid: true}' - autocomplete='email' - ignite-auto-focus - ) - +form-field__error({error: 'server', message: `{{$ctrl.serverError}}`}) - div - +form-field__password({ - label: 'Password:', - model: '$ctrl.data.password', - name: '"password"', - placeholder: 'Input password', - required: true - })( - autocomplete='new-password' - ) - div - +form-field__password({ - label: 'Confirm:', - model: 'confirm', - name: '"confirm"', - placeholder: 'Confirm password', - required: true - })( - ignite-match='$ctrl.data.password' - autocomplete='off' - ) - div - +form-field__text({ - label: 'First name:', - model: '$ctrl.data.firstName', - name: '"firstName"', - placeholder: 'Input first name', - required: true - })( - autocomplete='given-name' - ) - div - +form-field__text({ - label: 'Last name:', - model: '$ctrl.data.lastName', - name: '"lastName"', - placeholder: 'Input last name', - required: true - })( - autocomplete='family-name' - ) - div - +form-field__phone({ - label: 'Phone:', - model: '$ctrl.data.phone', - name: '"phone"', - placeholder: 'Input phone (ex.: +15417543010)', - optional: true - })( - autocomplete='tel' - ) - div - +form-field__dropdown({ - label: 'Country:', - model: '$ctrl.data.country', - name: '"country"', - required: true, - placeholder: 'Choose your country', - options: '$ctrl.countries' - })( - autocomplete='country' - ) - .full-width - +form-field__text({ - label: 'Company:', - model: '$ctrl.data.company', - name: '"company"', - placeholder: 'Input company name', - required: true - })( - ignite-on-enter-focus-move='countryInput' - autocomplete='organization' - ) + form(name='$ctrl.form' novalidate ng-submit='$ctrl.signup()') + form-signup( + outer-form='$ctrl.form' + ng-model='$ctrl.data' + server-error='$ctrl.serverError' + ) footer.full-width.form-footer button.btn-ignite.btn-ignite--primary( type='submit' http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/components/page-signup/types.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/types.ts b/modules/web-console/frontend/app/components/page-signup/types.ts deleted file mode 100644 index 49e8d5d..0000000 --- a/modules/web-console/frontend/app/components/page-signup/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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. - */ - -export interface ISignupData { - email: string, - password: string, - firstName: string, - lastName: string, - phone?: string, - company: string, - country: string -} - -export interface ISignupFormController extends ng.IFormController { - email: ng.INgModelController, - password: ng.INgModelController, - firstName: ng.INgModelController, - lastName: ng.INgModelController, - phone: ng.INgModelController, - company: ng.INgModelController, - country: ng.INgModelController -} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/modules/user/Auth.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/user/Auth.service.js b/modules/web-console/frontend/app/modules/user/Auth.service.js deleted file mode 100644 index 8c3cc4e..0000000 --- a/modules/web-console/frontend/app/modules/user/Auth.service.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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. - */ - -/** - * @typedef {object} SignupUserInfo - * @prop {string} email - * @prop {string} password - * @prop {string} firstName - * @prop {string} lastName - * @prop {string} company - * @prop {string} country - */ - -export default class AuthService { - static $inject = ['$http', '$rootScope', '$state', '$window', 'IgniteMessages', 'gettingStarted', 'User']; - /** - * @param {ng.IHttpService} $http - * @param {ng.IRootScopeService} $root - * @param {import('@uirouter/angularjs').StateService} $state - * @param {ng.IWindowService} $window - * @param {ReturnType<typeof import('app/services/Messages.service').default>} Messages - * @param {ReturnType<typeof import('app/modules/getting-started/GettingStarted.provider').service>} gettingStarted - * @param {ReturnType<typeof import('./User.service').default>} User - */ - constructor($http, $root, $state, $window, Messages, gettingStarted, User) { - this.$http = $http; - this.$root = $root; - this.$state = $state; - this.$window = $window; - this.Messages = Messages; - this.gettingStarted = gettingStarted; - this.User = User; - } - /** - * @param {SignupUserInfo} userInfo - */ - signnup(userInfo) { - return this._auth('signup', userInfo); - } - /** - * @param {string} email - * @param {string} password - */ - signin(email, password) { - return this._auth('signin', {email, password}); - } - /** - * @param {string} email - */ - remindPassword(email) { - return this._auth('password/forgot', {email}).then(() => this.$state.go('password.send')); - } - - // TODO IGNITE-7994: Remove _auth and move API calls to corresponding methods - /** - * Performs the REST API call. - * @private - * @param {('signin'|'signup'|'password/forgot')} action - * @param {{email:string,password:string}|SignupUserInfo|{email:string}} userInfo - */ - _auth(action, userInfo) { - return this.$http.post('/api/v1/' + action, userInfo) - .then(() => { - if (action === 'password/forgot') - return; - - this.User.read() - .then((user) => { - this.$root.$broadcast('user', user); - - this.$state.go('default-state'); - - this.$root.gettingStarted.tryShow(); - }); - }); - } - logout() { - return this.$http.post('/api/v1/logout') - .then(() => { - this.User.clean(); - - this.$window.open(this.$state.href('signin'), '_self'); - }) - .catch((e) => this.Messages.showError(e)); - } -} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/modules/user/Auth.service.ts ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/user/Auth.service.ts b/modules/web-console/frontend/app/modules/user/Auth.service.ts new file mode 100644 index 0000000..55956ad --- /dev/null +++ b/modules/web-console/frontend/app/modules/user/Auth.service.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +import {StateService} from '@uirouter/angularjs'; +import MessagesFactory from '../../services/Messages.service'; +import {service as GettingsStartedFactory} from '../../modules/getting-started/GettingStarted.provider'; +import UserServiceFactory from './User.service'; + +type SignupUserInfo = { + email: string, + password: string, + firstName: string, + lastName: string, + company: string, + country: string, +}; + +type AuthActions = 'signin' | 'signup' | 'password/forgot'; +type AuthOptions = {email:string, password:string}|SignupUserInfo|{email:string}; + +export default class AuthService { + static $inject = ['$http', '$rootScope', '$state', '$window', 'IgniteMessages', 'gettingStarted', 'User']; + + constructor( + private $http: ng.IHttpService, + private $root: ng.IRootScopeService, + private $state: StateService, + private $window: ng.IWindowService, + private Messages: ReturnType<typeof MessagesFactory>, + private gettingStarted: ReturnType<typeof GettingsStartedFactory>, + private User: ReturnType<typeof UserServiceFactory> + ) {} + + signup(userInfo: SignupUserInfo, loginAfterSignup: boolean = true) { + return this._auth('signup', userInfo, loginAfterSignup); + } + + signin(email: string, password: string) { + return this._auth('signin', {email, password}); + } + + remindPassword(email: string) { + return this._auth('password/forgot', {email}).then(() => this.$state.go('password.send')); + } + + // TODO IGNITE-7994: Remove _auth and move API calls to corresponding methods + /** + * Performs the REST API call. + */ + private _auth(action: AuthActions, userInfo: AuthOptions, loginAfterwards: boolean = true) { + return this.$http.post('/api/v1/' + action, userInfo) + .then(() => { + if (action === 'password/forgot') + return; + + this.User.read() + .then((user) => { + if (loginAfterwards) { + this.$root.$broadcast('user', user); + this.$state.go('default-state'); + this.$root.gettingStarted.tryShow(); + } else + this.$root.$broadcast('userCreated'); + }); + }); + } + logout() { + return this.$http.post('/api/v1/logout') + .then(() => { + this.User.clean(); + + this.$window.open(this.$state.href('signin'), '_self'); + }) + .catch((e) => this.Messages.showError(e)); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/primitives/dropdown/index.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/dropdown/index.pug b/modules/web-console/frontend/app/primitives/dropdown/index.pug index 0099457..aad6efd 100644 --- a/modules/web-console/frontend/app/primitives/dropdown/index.pug +++ b/modules/web-console/frontend/app/primitives/dropdown/index.pug @@ -37,5 +37,6 @@ mixin ignite-form-field-bsdropdown({label, model, name, disabled, required, opti ul.dropdown-menu(role='menu') li(ng-repeat=`item in ${options}` ng-if='item.available') - a(ng-click='item.click()') {{ item.action }} + a(ng-if='item.click' ng-click='item.click()') {{ item.action }} + a(ng-if='item.sref' ui-sref='{{:: item.sref}}') {{ item.action }} http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/primitives/form-field/checkbox.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/form-field/checkbox.pug b/modules/web-console/frontend/app/primitives/form-field/checkbox.pug index 88b8f5a..fe0f808 100644 --- a/modules/web-console/frontend/app/primitives/form-field/checkbox.pug +++ b/modules/web-console/frontend/app/primitives/form-field/checkbox.pug @@ -15,7 +15,7 @@ limitations under the License. mixin form-field__checkbox({ label, model, name, disabled, required, tip, tipOpts }) - .form-field.form-field__checkbox(id=`{{ ${name} }}Field`) + .form-field.ignite-form-field.form-field__checkbox(id=`{{ ${name} }}Field`) +form-field__label({ label, name, required, disabled }) +form-field__tooltip({ title: tip, options: tipOpts }) @@ -24,7 +24,8 @@ mixin form-field__checkbox({ label, model, name, disabled, required, tip, tipOpt +form-field__input({ name, model, disabled, required, placeholder })(attributes=attributes) .form-field__errors( - ng-messages=`(${form}[${name}].$dirty || ${form}[${name}].$touched || ${form}[${name}].$submitted) && ${form}[${name}].$invalid ? ${form}[${name}].$error : {}` + ng-messages=`$input.$error` + ng-show=`($input.$dirty || $input.$touched || $input.$submitted) && $input.$invalid` ) if block block http://git-wip-us.apache.org/repos/asf/ignite/blob/fe824a0e/modules/web-console/frontend/app/primitives/form-field/email.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/form-field/email.pug b/modules/web-console/frontend/app/primitives/form-field/email.pug index b68a520..5fb0c30 100644 --- a/modules/web-console/frontend/app/primitives/form-field/email.pug +++ b/modules/web-console/frontend/app/primitives/form-field/email.pug @@ -15,9 +15,9 @@ limitations under the License. mixin form-field__email({ label, model, name, disabled, required, placeholder, tip }) - -var errLbl = label.substring(0, label.length - 1) + -let errLbl = label[label.length - 1] === ':' ? label.substring(0, label.length - 1) : label - .form-field + .form-field.ignite-form-field +form-field__label({ label, name, required, disabled }) +form-field__tooltip({ title: tip, options: tipOpts }) @@ -26,7 +26,8 @@ mixin form-field__email({ label, model, name, disabled, required, placeholder, t +form-field__input({ name, model, disabled, required, placeholder })(attributes=attributes) .form-field__errors( - ng-messages=`(${form}[${name}].$dirty || ${form}[${name}].$touched || ${form}[${name}].$submitted) && ${form}[${name}].$invalid ? ${form}[${name}].$error : {}` + ng-messages=`$input.$error` + ng-show=`($input.$dirty || $input.$touched || $input.$submitted) && $input.$invalid` ) if block block