diff --git a/bower.json b/bower.json index 35c2b965..b9b03063 100644 --- a/bower.json +++ b/bower.json @@ -40,7 +40,8 @@ "mno-ui-elements": "maestrano/mno-ui-elements#master", "textAngular": "^1.5.16", "json-formatter": "^0.6.0", - "es6-shim": "^0.35.3" + "es6-shim": "^0.35.3", + "AngularDevise": "angular-devise#^1.3.0" }, "devDependencies": { "angular-mocks": "~1.6.0" diff --git a/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee b/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee index fa0ec236..ed2181c8 100644 --- a/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee +++ b/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee @@ -6,29 +6,32 @@ angular.module 'mnoEnterpriseAngular' errorCache = null mnoErrorHandler.processServerError = (serverError, form) -> $log.error('An error occurred:', serverError) + return unless form + return if _.startsWith(serverError.data, '') if 400 <= serverError.status <= 499 # Error in the request # Save the errors object in the scope - errorCache = serverError + errorCache = serverError.data.errors # Set each error fields as not valid - _.each errorCache.data, (errors, key) -> - _.each errors, -> - if form[key]? - form[key].$setValidity 'server', false - else - $log.error('MnoErrorsHandler: cannot find field:' + key) + if form? && errorCache + _.each errorCache, (errors, key) -> + _.each errors, -> + if form[key]? + form[key].$setValidity 'server', false + else + $log.error('MnoErrorsHandler: cannot find field:' + key) mnoErrorHandler.errorMessage = (name) -> result = [] if errorCache? - _.each errorCache.data[name], (msg) -> - result.push msg + _.each errorCache[name], (msg) -> + result.push [ _.capitalize(name), msg ].join ' ' result.join ', ' mnoErrorHandler.resetErrors = (form) -> if errorCache? - _.each errorCache.data, (errors, key) -> + _.each errorCache, (errors, key) -> _.each errors, -> if form[key]? form[key].$setValidity 'server', null diff --git a/src/app/components/mno-locale-config/mno-locale-config.svc.coffee b/src/app/components/mno-locale-config/mno-locale-config.svc.coffee index 29163ded..9f2bf37d 100644 --- a/src/app/components/mno-locale-config/mno-locale-config.svc.coffee +++ b/src/app/components/mno-locale-config/mno-locale-config.svc.coffee @@ -37,7 +37,7 @@ angular.module 'mnoEnterpriseAngular' # Find the locale from the User#settings localeFromUser = -> - MnoeCurrentUser.get().then( + MnoeCurrentUser.get(skip_login_check: true).then( (response) -> response.settings?.locale ) diff --git a/src/app/components/mno-password-strength/mno-password-strength.coffee b/src/app/components/mno-password-strength/mno-password-strength.coffee new file mode 100644 index 00000000..205d0755 --- /dev/null +++ b/src/app/components/mno-password-strength/mno-password-strength.coffee @@ -0,0 +1,149 @@ +angular.module 'mnoEnterpriseAngular' + .directive 'mnoPasswordStrength', (MnoErrorsHandler) -> + require: "ngModel" + restrict: 'A' + scope: + passwordScore: '=' #outject score + + link: (scope, element, attrs, ctrl) -> + measureStrength = (p) -> + matches = + pos: {} + neg: {} + + counts = + pos: {} + neg: + seqLetter: 0 + seqNumber: 0 + seqSymbol: 0 + + tmp = undefined + strength = 0 + letters = "abcdefghijklmnopqrstuvwxyz" + numbers = "01234567890" + symbols = "\\!@#$%&/()=?¿" + back = undefined + forth = undefined + + if p + # Benefits + matches.pos.lower = p.match(/[a-z]/g) + matches.pos.upper = p.match(/[A-Z]/g) + matches.pos.numbers = p.match(/\d/g) + matches.pos.symbols = p.match(/[$-/:-?{-~!^_`\[\]]/g) + matches.pos.middleNumber = p.slice(1, -1).match(/\d/g) + matches.pos.middleSymbol = p.slice(1, -1).match(/[$-/:-?{-~!^_`\[\]]/g) + counts.pos.lower = (if matches.pos.lower then matches.pos.lower.length else 0) + counts.pos.upper = (if matches.pos.upper then matches.pos.upper.length else 0) + counts.pos.numbers = (if matches.pos.numbers then matches.pos.numbers.length else 0) + counts.pos.symbols = (if matches.pos.symbols then matches.pos.symbols.length else 0) + tmp = _.reduce(counts.pos, (memo, val) -> + # if has count will add 1 + memo + Math.min(1, val) + , 0) + counts.pos.numChars = p.length + tmp += (if (counts.pos.numChars >= 8) then 1 else 0) + counts.pos.requirements = (if (tmp >= 3) then tmp else 0) + counts.pos.middleNumber = (if matches.pos.middleNumber then matches.pos.middleNumber.length else 0) + counts.pos.middleSymbol = (if matches.pos.middleSymbol then matches.pos.middleSymbol.length else 0) + + # Deductions + matches.neg.consecLower = p.match(/(?=([a-z]{2}))/g) + matches.neg.consecUpper = p.match(/(?=([A-Z]{2}))/g) + matches.neg.consecNumbers = p.match(/(?=(\d{2}))/g) + matches.neg.onlyNumbers = p.match(/^[0-9]*$/g) + matches.neg.onlyLetters = p.match(/^([a-z]|[A-Z])*$/g) + counts.neg.consecLower = (if matches.neg.consecLower then matches.neg.consecLower.length else 0) + counts.neg.consecUpper = (if matches.neg.consecUpper then matches.neg.consecUpper.length else 0) + counts.neg.consecNumbers = (if matches.neg.consecNumbers then matches.neg.consecNumbers.length else 0) + + # sequential letters (back and forth) + i = 0 + while i < letters.length - 2 + p2 = p.toLowerCase() + forth = letters.substring(i, parseInt(i + 3)) + back = _(forth).split("").reverse() + counts.neg.seqLetter++ if p2.indexOf(forth) isnt -1 or p2.indexOf(back) isnt -1 + i++ + + # sequential numbers (back and forth) + i = 0 + while i < numbers.length - 2 + forth = numbers.substring(i, parseInt(i + 3)) + back = _(forth).split("").reverse() + counts.neg.seqNumber++ if p.indexOf(forth) isnt -1 or p.toLowerCase().indexOf(back) isnt -1 + i++ + + # sequential symbols (back and forth) + i = 0 + while i < symbols.length - 2 + forth = symbols.substring(i, parseInt(i + 3)) + back = _(forth).split("").reverse() + counts.neg.seqSymbol++ if p.indexOf(forth) isnt -1 or p.toLowerCase().indexOf(back) isnt -1 + i++ + + # repeated chars + counts.neg.repeated = _.chain(p.toLowerCase().split("")).countBy((val) -> + val + ).reject((val) -> + val is 1 + ).reduce((memo, val) -> + memo + val + , 0).value() + + # Calculations + strength += counts.pos.numChars * 4 + strength += (counts.pos.numChars - counts.pos.upper) * 2 if counts.pos.upper + strength += (counts.pos.numChars - counts.pos.lower) * 2 if counts.pos.lower + strength += counts.pos.numbers * 4 if counts.pos.upper or counts.pos.lower + strength += counts.pos.symbols * 6 + strength += (counts.pos.middleSymbol + counts.pos.middleNumber) * 2 + strength += counts.pos.requirements * 2 + strength -= counts.neg.consecLower * 2 + strength -= counts.neg.consecUpper * 2 + strength -= counts.neg.consecNumbers * 2 + strength -= counts.neg.seqNumber * 3 + strength -= counts.neg.seqLetter * 3 + strength -= counts.neg.seqSymbol * 3 + strength -= counts.pos.numChars if matches.neg.onlyNumbers + strength -= counts.pos.numChars if matches.neg.onlyLetters + strength -= (counts.neg.repeated / counts.pos.numChars) * 10 if counts.neg.repeated + Math.max 0, Math.min(100, Math.round(strength)) + + getPwStrength = (s) -> + switch Math.round(s / 20) + when 0, 1 + "weak" + when 2,3 + "good" + when 4,5 + "secure" + + getClass = (s) -> + switch getPwStrength(s) + when 'weak' + "danger" + when 'good' + "warning" + when 'secure' + "success" + + isPwStrong = (s) -> + switch getPwStrength(s) + when 'weak' + false + else + true + + scope.$watch (-> ctrl.$modelValue), -> + scope.value = measureStrength(ctrl.$modelValue) + scope.pwStrength = getPwStrength(scope.value) + ctrl.$setValidity('password-strength', isPwStrong(scope.value)) + if scope.passwordScore? + scope.passwordScore.value = scope.pwStrength + scope.passwordScore.class = getClass(scope.value) + scope.passwordScore.showTip = (ctrl.$modelValue? && ctrl.$modelValue != '' && !isPwStrong(scope.value)) + + + return diff --git a/src/app/components/mnoe-api/auth.svc.coffee b/src/app/components/mnoe-api/auth.svc.coffee new file mode 100644 index 00000000..3ed837f1 --- /dev/null +++ b/src/app/components/mnoe-api/auth.svc.coffee @@ -0,0 +1,6 @@ +angular.module 'mnoEnterpriseAngular' + .factory 'MnoeAuthSvc', (Restangular, inflector) -> + return Restangular.withConfig((RestangularProvider) -> + RestangularProvider.setBaseUrl('/mnoe/auth') + RestangularProvider.setDefaultHeaders({Accept: "application/json"}) + ) diff --git a/src/app/components/mnoe-api/current-user.svc.coffee b/src/app/components/mnoe-api/current-user.svc.coffee index 85c0217a..992112a5 100644 --- a/src/app/components/mnoe-api/current-user.svc.coffee +++ b/src/app/components/mnoe-api/current-user.svc.coffee @@ -13,7 +13,7 @@ # => PUT /mnoe/jpi/v1/current_user/update_password angular.module 'mnoEnterpriseAngular' - .service 'MnoeCurrentUser', (MnoeApiSvc, $window, $state, $rootScope, URI, MnoeConfig) -> + .service 'MnoeCurrentUser', (MnoeApiSvc, $window, $state, $q, $timeout, $rootScope, URI, Auth, MnoeConfig) -> _self = @ # Store the current_user promise @@ -23,12 +23,34 @@ angular.module 'mnoEnterpriseAngular' # Save the current user in variable to be able to reference it directly @user = {} + # Redirect if user is already logged in + @skipIfLoggedIn = -> + Auth.currentUser().then( + (response) -> + $timeout( -> $state.go('home.impac') ) + $q.reject() + -> + $q.resolve() + ) + + @loginRequired = -> + Auth.currentUser().catch( + -> + $timeout( -> $state.go('login') ) + $q.reject() + ) + # Get the current user - @get = -> + @get = (opts = {})-> return userPromise if userPromise? + userPromise = MnoeApiSvc.one('current_user').get().then( (response) -> response = response.plain() + + if ! (opts.skip_login_check || response.logged_in) + $state.go('login') + angular.copy(response, _self.user) response ) @@ -55,15 +77,4 @@ angular.module 'mnoEnterpriseAngular' @updatePassword = (passwordData) -> MnoeApiSvc.all('/current_user').doPUT({user: passwordData}, 'update_password') - # Ensure user is logged in - @loginRequired = -> - _self.get().then( - (response) -> - unless response.logged_in - if MnoeConfig.arePublicApplicationsEnabled() - $state.go('public.landing') - else - $window.location = URI.login - ) - return @ diff --git a/src/app/index.config.coffee b/src/app/index.config.coffee index fca99d6e..5ce35ad4 100644 --- a/src/app/index.config.coffee +++ b/src/app/index.config.coffee @@ -25,13 +25,15 @@ angular.module 'mnoEnterpriseAngular' ) .config ($httpProvider) -> - $httpProvider.interceptors.push ($q, $window) -> + $httpProvider.interceptors.push ($q, $window, $injector, $log) -> { responseError: (rejection) -> if rejection.status == 401 # Redirect to login page - console.log "User is not connected!" - $window.location.href = '/' + toastr = $injector.get('toastr') + + toastr.error('User is not connected!') + $log.error('User is not connected!') $q.reject rejection } @@ -84,3 +86,22 @@ angular.module 'mnoEnterpriseAngular' # TODO: Activate in "developer mode" only (spams the console and makes the application lag) # $translateProvider.useMissingTranslationHandlerLog() ) + # Configure auth routes + .config((AuthProvider, AuthInterceptProvider) -> + AuthProvider.loginPath('/mnoe/auth/users/sign_in') + AuthProvider.logoutPath('/mnoe/auth/users/sign_out') + AuthProvider.registerPath('/mnoe/auth/users/') + AuthProvider.sendResetPasswordInstructionsPath('/mnoe/auth/users/password') + AuthProvider.resetPasswordPath('/mnoe/auth/users/password') + AuthInterceptProvider.interceptAuth(true) + ) + + + + + + + + + + diff --git a/src/app/index.constants.coffee b/src/app/index.constants.coffee index 6e7b03ef..834f3220 100644 --- a/src/app/index.constants.coffee +++ b/src/app/index.constants.coffee @@ -1,10 +1,10 @@ angular.module 'mnoEnterpriseAngular' .constant('URI', { - login: '/mnoe/auth/users/sign_in', + login: '/login', dashboard: '/dashboard/', - logout: '/mnoe/auth/users/sign_out', signup: '/mnoe/auth/users/sign_up', - api_root: '/mnoe/jpi/v1' + api_root: '/mnoe/jpi/v1', + logout: '/logout' }) .constant('DOC_LINKS', { connecDoc: 'https://maestrano.atlassian.net/wiki/x/BIHLAQ' diff --git a/src/app/index.default-config.coffee b/src/app/index.default-config.coffee index 373c9cb4..0850ebf1 100644 --- a/src/app/index.default-config.coffee +++ b/src/app/index.default-config.coffee @@ -11,3 +11,4 @@ angular.module('mnoEnterprise.defaultConfiguration', []) .constant('APP_NAME', null) .constant('INTERCOM_ID', null) .constant('URL_CONFIG', {}) + .constant('DEVISE_CONFIG', {}) diff --git a/src/app/index.less b/src/app/index.less index 4d6007a9..169db2c4 100644 --- a/src/app/index.less +++ b/src/app/index.less @@ -39,7 +39,7 @@ body { } } -.myspace { +.myspace, .page-wrapper { background-color: @dashboard-bg-color; min-height:100%; } diff --git a/src/app/index.module.coffee b/src/app/index.module.coffee index dce3a08e..98c7bdaf 100644 --- a/src/app/index.module.coffee +++ b/src/app/index.module.coffee @@ -22,5 +22,7 @@ angular.module 'mnoEnterpriseAngular', [ 'schemaForm', 'angular.filter', 'textAngular', - 'jsonFormatter' + 'jsonFormatter', + 'mnoUiElements', + 'Devise' ] diff --git a/src/app/index.route.coffee b/src/app/index.route.coffee index 93aecce1..a86e5ddf 100644 --- a/src/app/index.route.coffee +++ b/src/app/index.route.coffee @@ -28,6 +28,92 @@ angular.module 'mnoEnterpriseAngular' controller: 'LandingProductCtrl' controllerAs: 'vm' public: true + .state 'login', + data: + pageTitle: 'Login' + url: '/login' + templateUrl: 'app/views/auth/login/login.html' + controller: 'AuthLoginCtrl' + controllerAs: 'vm' + resolve: + # Redirect the user to the platform if he is already logged in + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'confirmation_lounge', + data: + pageTitle: 'Lounge' + params: { + email: null + } + url: '/confirmation/lounge' + templateUrl: 'app/views/auth/confirmation/lounge.html' + controller: 'AuthLoungeCtrl' + controllerAs: 'vm' + resolve: + # Redirect the user to the platform if he is already logged in + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'confirmation', + data: + pageTitle: 'Confirmation' + url: '/confirmation?:confirmation_token' + templateUrl: 'app/views/auth/confirmation/confirm.html' + controller: 'AuthConfirmCtrl' + controllerAs: 'vm' + public: true + .state 'confirmation_recovery', + data: + pageTitle: 'ConfirmationRecovery' + url: '/confirmation/new' + templateUrl: 'app/views/auth/confirmation/new.html' + controller: 'AuthConfirmRecoveryCtrl' + controllerAs: 'vm' + public: true + .state 'unlock_recovery', + data: + pageTitle: "UnlockRecovery" + url: '/unlock/new', + templateUrl: 'app/views/auth/unlock/unlock.html' + controller: 'AuthUnlockRecoveryCtrl' + controllerAs: 'vm' + public: true + .state 'password_recovery', + data: + pageTitle: 'PasswordRecovery' + url: '/password/new' + templateUrl: 'app/views/auth/password/recovery.html' + controller: 'PasswordRecoveryCtrl' + controllerAs: 'vm' + resolve: + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'password_reset', + data: + pageTitle: 'PasswordReset' + url: '/password/reset' + templateUrl: 'app/views/auth/password/reset.html' + controller: 'PasswordResetCtrl' + controllerAs: 'vm' + resolve: + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'signup', + data: + pageTitle: 'SignUp' + url: '/signup' + templateUrl: 'app/views/auth/signup/signup.html' + controller: 'AuthSignUpCtrl' + controllerAs: 'vm' + resolve: + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'authorize', + data: + pageTitle: 'Authorization' + url: '/authorize?:redirect_path' + templateUrl: 'app/views/apps/authorize.html' + controller: 'AppAuthController' + controllerAs: 'vm' .state 'home', data: pageTitle:'Home' @@ -45,6 +131,9 @@ angular.module 'mnoEnterpriseAngular' url: '/apps' templateUrl: 'app/views/apps/dashboard-apps-list.html' controller: 'DashboardAppsListCtrl' + resolve: + resourcesLoaded: ($rootScope) -> + $rootScope.resourcesLoaded .state 'home.impac', data: pageTitle:'Impac' @@ -68,22 +157,14 @@ angular.module 'mnoEnterpriseAngular' controllerAs: 'vm' .state 'logout', url: '/logout' - controller: ($window, $http, $translate, AnalyticsSvc, URL_CONFIG) -> + controller: ($state, Auth, toastr, AnalyticsSvc, MnoeCurrentUser) -> 'ngInject' # Logout and redirect the user - $http.delete(URI.logout).then( -> + Auth.logout().then(-> AnalyticsSvc.logOut() - - logout_url = URL_CONFIG.after_sign_out_url || URI.login - - if I18N_CONFIG.enabled - if URL_CONFIG.after_sign_out_url - $window.location.href = logout_url - else - $window.location.href = "/#{$translate.use()}#{URI.login}" - else - $window.location.href = logout_url + toastr.info('You have been logged out.') + $state.go('login') ) if MnoeConfigProvider.$get().isOnboardingWizardEnabled() @@ -128,6 +209,9 @@ angular.module 'mnoEnterpriseAngular' templateUrl: 'app/views/marketplace/marketplace.html' controller: 'DashboardMarketplaceCtrl' controllerAs: 'vm' + resolve: + resourcesLoaded: ($rootScope) -> + $rootScope.resourcesLoaded .state 'home.marketplace.app', data: pageTitle:'Marketplace' @@ -211,16 +295,15 @@ angular.module 'mnoEnterpriseAngular' MnoeCurrentUser = $injector.get('MnoeCurrentUser') MnoeOrganizations = $injector.get('MnoeOrganizations') MnoeAppInstances = $injector.get('MnoeAppInstances') - $window = $injector.get('$window') - MnoeCurrentUser.get().then( + MnoeCurrentUser.get(skip_login_check: true).then( (response) -> # Same as MnoeCurrentUser.loginRequired unless response.logged_in if MnoeConfig.arePublicApplicationsEnabled() $location.url('/landing') else - $window.location = URI.login + $location.url('/login') else if MnoeConfig.isOnboardingWizardEnabled() MnoeOrganizations.getCurrentOrganisation().then( diff --git a/src/app/index.run.coffee b/src/app/index.run.coffee index b7bcf91d..5533a539 100644 --- a/src/app/index.run.coffee +++ b/src/app/index.run.coffee @@ -49,54 +49,3 @@ angular.module 'mnoEnterpriseAngular' toastr[message.type](message.msg, _.capitalize(message.type), timeout: 10000) $location.search('flash', null) # remove the flash from url ) - - .run(($rootScope, $timeout, AnalyticsSvc) -> - $timeout ( -> AnalyticsSvc.init() ) - - $rootScope.$on('$stateChangeSuccess', -> - AnalyticsSvc.update() - ) - ) - - # App initialization: Retrieve current user and current organization, then preload marketplace - .run(($rootScope, $q, $location, $stateParams, MnoeCurrentUser, MnoeOrganizations, MnoeMarketplace, MnoeAppInstances, MnoeConfig) -> - - _self = this - - # Hide the layout with a loader - $rootScope.isLoggedIn = false - - # Load the current user - userPromise = MnoeCurrentUser.get() - - # Load the current organization if defined (url param, cookie or first) - _self.appInstancesDeferred = $q.defer() - orgPromise = MnoeOrganizations.getCurrentOrganisation().then( - (response) -> - # Pre-load the marketplace, the products and the local products - MnoeMarketplace.getApps() - MnoeMarketplace.getProducts() if MnoeConfig.isProvisioningEnabled() - MnoeMarketplace.getLocalProducts() if MnoeConfig.areLocalProductsEnabled() - - # App instances needs to be run after fetching the organization (At least the first call) - MnoeAppInstances.getAppInstances().then( - (appInstances) -> - _self.appInstancesDeferred.resolve(appInstances) - ) - - response - ) - - $q.all([userPromise, orgPromise, _self.appInstancesDeferred.promise]).then( - -> - # Display the layout - $rootScope.isLoggedIn = true - ).catch( - -> - # Display the layout - $rootScope.isLoggedIn = true - - # Display no organisation message - $rootScope.hasNoOrganisations = true - ) - ) diff --git a/src/app/stylesheets/variables.less b/src/app/stylesheets/variables.less index 67efd19c..325b79cc 100644 --- a/src/app/stylesheets/variables.less +++ b/src/app/stylesheets/variables.less @@ -23,12 +23,13 @@ @public-page-header-bg-color: @bg-inverse-color; @public-page-header-padding: 10px; +@public-page-header-logo-img: "../images/main-logo.png"; @public-page-header-logo: { min-height: 61px; max-width: 160px; max-height: 130px; margin: 17px auto 5px auto; }; /*-----------------------------------------------------------------------*/ /* Login Page */ /*-----------------------------------------------------------------------*/ -@login-bg-img: "mno_enterprise/login-background.jpg"; +@login-bg-img: "../images/login-background.jpg"; @login-bg-color: @bg-main-color; @login-box-grid-position: { margin-top: 80px; .make-sm-column(4); .make-sm-column-offset(4); }; @@ -39,10 +40,11 @@ @login-box-bg-color: @bg-inverse-color; @login-box-padding: 20px; -@login-box-brand-logo: { max-width: 300px; max-height: 200px; margin: 16px auto 16px auto; }; +@login-box-brand-logo-img: "../images/main-logo.png"; +@login-box-brand-logo: { min-height: 61px; max-width: 160px; max-height: 130px; margin: 17px auto 5px auto; }; @login-box-btn-login: { width: 100%; }; -@login-box-sso-intuit-logo: "mno_enterprise/logo-intuit.png"; +@login-box-sso-intuit-logo: "../images/logo-intuit.png"; @login-box-oauth-text-color: #fff; @login-box-oauth-bg-color-facebook: #3b5998; @login-box-oauth-bg-color-google: #dd4b39; diff --git a/src/app/views/apps/authorize.controller.coffee b/src/app/views/apps/authorize.controller.coffee new file mode 100644 index 00000000..175aa8a9 --- /dev/null +++ b/src/app/views/apps/authorize.controller.coffee @@ -0,0 +1,11 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AppAuthController', + ($state, $window, $timeout, $stateParams, MnoErrorsHandler) -> + + $timeout( + -> + $window.location.href = $stateParams.redirect_path + , 3000) + + return + ) diff --git a/src/app/views/apps/authorize.html b/src/app/views/apps/authorize.html new file mode 100644 index 00000000..04709c5a --- /dev/null +++ b/src/app/views/apps/authorize.html @@ -0,0 +1,15 @@ +
+ +
+
+
+

You are being redirected to your application

+ Main logo whitebg +
+
+ Loader 32x32 bg inverse +
+
+
+
+
diff --git a/src/app/views/auth/auth.less b/src/app/views/auth/auth.less new file mode 100644 index 00000000..023b3f38 --- /dev/null +++ b/src/app/views/auth/auth.less @@ -0,0 +1,420 @@ +//--------------------------------------------------------------------------- +// Material Style +//--------------------------------------------------------------------------- +.registration-material { + height: 90%; + width: 100%; + position: absolute; + background-color: @login-bg-color; + text-align: center; + + .action-box { + height: 90%; + } + + .action-box md-input-container { + text-align: left; + } + + .action-box md-checkbox { + text-align: left; + } +} + +// Default style + +.registration when (isstring(@login-bg-img)) { + background-image: url(@login-bg-img); + background-size: cover; +} + +.registration { + height: 100%; + width: 100%; + position: absolute; + background-color: @login-bg-color; + + .login-box-wrapper { + text-align: center; + @login-box-grid-position(); + } + + .login-box-wrapper.large { + text-align: center; + margin-top: 80px; + width: 100%; + max-width: 400px; + } + + .login-box-wrapper md-input-container { + text-align: left; + } + + .login-box-wrapper .login-box-title { + color: @login-box-title-color; + margin-bottom: 15px; + @login-box-title(); + } + + .login-box-wrapper .login-box { + padding: @login-box-padding; + background-color: @login-box-bg-color; + border-radius: @login-box-corners; + + .loader { + content: url(../images/loader-32x32-bg-inverse.gif) + } + } + + .login-box-wrapper .login-box .brand-logo when (isstring(@login-box-brand-logo-img)){ + min-height: 80px; + background-repeat: no-repeat; + background-image: url(@login-box-brand-logo-img); + background-size: 100%; + @login-box-brand-logo(); + } + + .login-box-wrapper .login-box .btn-login { + @login-box-btn-login(); + } + + + .login-box-wrapper .login-box:before when (@login-box-title-display-box-arrow = true){ + .fa; + color: @login-box-bg-color; + display: block; + content: @fa-var-caret-up; + margin-top: -27px; + font-size: 24px; + line-height: 8px; + max-width: 60%; + margin-left: auto; + margin-right: auto; + } + + .login-box .sign-in-using { + color: @decorator-main-color; + margin-top: 5px; + } + .login-box .oauth-sso { + display: flex; + flex-direction: row-reverse; + justify-content: center; + align-items: center; + margin-top: 5px; + + .omniauth i { + display: inline-block; + border-radius: 3px; + color: @login-box-oauth-text-color; + width: 25px; + height: 25px; + margin: 0 5px; + line-height: 25px; + text-align: center; + + &.fa-facebook { + background: @login-box-oauth-bg-color-facebook; + } + &.fa-google { + background: @login-box-oauth-bg-color-google; + } + &.fa-linkedin { + background: @login-box-oauth-bg-color-linkedin; + } + } + .omniauth.intuit { + background: @login-box-oauth-bg-color-intuit; + height: 25px; + width: 50px; + display: inline-block; + margin: 0 5px; + border-radius: 3px; + background-position: 50%; + background-repeat: no-repeat; + background-size: 40px auto; + background-image: url(@login-box-sso-intuit-logo); + } + } + + .login-box h3, .login-box h5 { + color: @text-inverse-strong-color; + } + + .checkbox-section label { + font-weight: normal; + } + + .login-box-wrapper .login-box .phone select.form-control { + height: 34px; + } +} + +input.fluroblue[type=radio] { + width : 18px; + margin : 0; + padding : 0; + opacity : 0; +} + +input.fluroblue[type=radio] + label{ + display : inline-block; + margin-left : -12px; + padding-left : 25px; + background : image-url('icons/radio-fluroblue.png') no-repeat 0 -18px; + line-height : 18px; +} + +input.fluroblue[type=radio]:checked + label{ + background-position : 0 1px; +} + +form label { + color: @input-label-color; +} + +.input-group-addon.select select { + background-color: @input-bg; +} + + +select { + text-indent: 1px; + text-overflow: ''; +} + +// Remove dropdown arrow (Chrome, Firefox and IE) +select.unstyled { + -webkit-appearance: none; + -moz-appearance: none; +} + +select::-ms-expand { + display: none; +} + +//---------------------------------------- +// Styling for phone: select + input +//---------------------------------------- +form .phone { + select.form-control { + width:30%; + .border-right-radius(0px); + } + + input.form-control[type='text'] { + margin-left:23%; + margin-top:-34px; + width:77%; + .border-left-radius(0px); + } +} + +form select.form-control { + height:44px; + -webkit-appearance: none; +} + +form .select.fa { + margin: -28px 7% 0px 0px; + float: right; +} + +//---------------------------------------- +// Styling for password checker +//---------------------------------------- +form li.checked { + color: lighten(@decorator-inverse-color,30%); +} + +form li.unchecked { + color: lighten(@text-strong-color,70%); +} + +//---------------------------------------- +// Style for forms on inverse backgrounds +//---------------------------------------- +form.inverse { + select { + background-color: @bg-inverse-color; + .font(16px, 300, @decorator-inverse-color); + border:solid 1px #111c21; + + .border-radius(5px); + height:44px; + -webkit-appearance: none; + padding-left:15px; + width:100%; + + // Placeholder + .placeholder(@decorator-inverse-color); + } + + .select.fa { + .font(16px,300,@decorator-inverse-color); + margin: -28px 7% 0px 0px; + float: right; + } + + .form-control { + // Placeholder + .placeholder(@decorator-inverse-color); + } +} + +//------------------------------------ +// Dark forms styles +//------------------------------------ + +form.dark { + input[type='text'], input[type='email'], textarea { + .border-radius(5px); + .font(16px, 300, @bg-main-color); + background-color: @bg-inverse-color; + border: solid 1px @bg-inverse-color; + line-height: 25px; + margin-bottom: 0px; + width: 100%; + min-height: 42px; + padding: 5px 5px 5px 15px; + } + + *::-webkit-input-placeholder { + color: @bg-main-color; + } + *:-moz-placeholder { + /* FF 4-18 */ + color: @bg-main-color; + } + *::-moz-placeholder { + /* FF 19+ */ + color: @bg-main-color; + } + *:-ms-input-placeholder { + /* IE 10+ */ + color: @bg-main-color; + } + + label { + color: @bg-main-color; + } +} + +.dark { + .bootstrap-select { + select { + .font(16px, 300, @bg-main-color); + background-color: @bg-inverse-color; + } + + .dropdown-toggle { + height: 42px; + } + + .dropdown-menu { + .font(16px, 300, @bg-main-color); + background-color: @bg-inverse-color; + } + .dropdown-menu > li > a { + .font(16px, 300, @bg-main-color); + } + + .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { + .font(16px, 300, @decorator-inverse-color); + background-color: @bg-inverse-color; + } + } +} + +// Put a dropdown inside an input-group-addon +.input-group-addon.select { + padding:0px; + select { + height:100%; + border:0px; + } +} + +//------------------------------------ +// Invalid inputs (AngularJS) +//------------------------------------ +input.ng-dirty.ng-invalid { + border-color: @brand-danger; +} + +// Mixins + +html, body { + height: 100%; +} + +// .border-radius doesn't exist anymore in bootstrap3 +.border-radius(@radius) { + .border-top-radius(@radius); + .border-bottom-radius(@radius); +} + + +// Thoses mixins don't exist anymore in bootstrap 3. I just did a dummy +// implementation of them to prevent errors +.muted { +} + +.rgba(@color,@alpha) { + background-color:@color; + background-color:rgba(red(@color),green(@color),blue(@color),@alpha); +} + +.font(@size,@weight,@color) { + font-weight:@weight; + font-size:@size; + color:@color; +} + +.buttonBackground(@a,@b) { +} + +.border-top-left-radius(@a) { +} + +.border-top-right-radius(@a) { +} + +.border-bottom-right-radius(@a) { +} + +.border-bottom-left-radius(@a) { +} + +.inherit-all { + .font(inherit,inherit,inherit); + background-color:inherit; + text-align:inherit; +} + +// Fontawesome icon mixing +// E.g.: +// .my-class { +// .icon-fa(exchange); +// } +// +// E.g.: +// .my-class { +// .fa-lg(); +// .icon-fa(exchange); +// } +// +.icon-fa(@icon) { + .fa; + @fa-icon-variable: "fa-var-@{icon}"; + &:before { content: @@fa-icon-variable; } +}; + +// Replace glyphicon by fontawesome +.glyphicon.glyphicon-chevron-right { + .icon-fa(chevron-right) +} +.glyphicon.glyphicon-chevron-left { + .icon-fa(chevron-left) +} + diff --git a/src/app/views/auth/confirmation/confirm.controller.coffee b/src/app/views/auth/confirmation/confirm.controller.coffee new file mode 100644 index 00000000..f2affb2a --- /dev/null +++ b/src/app/views/auth/confirmation/confirm.controller.coffee @@ -0,0 +1,36 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AuthConfirmCtrl', + ($state, $stateParams, MnoeAuthSvc, toastr, MnoErrorsHandler) -> + vm = @ + vm.user = { phone_country_code: "US", confirmation_token: $stateParams.confirmation_token} + vm.$pwdScore = {} + vm.phoneRequired = "required" + + MnoeAuthSvc.one("/users/confirmation").get({confirmation_token: vm.user.confirmation_token}).then( + (response) -> + if response.attributes.new_email_confirmed + toastr.success('Email confirmed successfully') + $state.go('home.impac') + else if response.attributes.confirmed_at + toastr.info('User already confirmed') + $state.go('login') + else if response.attributes.no_phone_required + vm.phoneRequired = '' + (error) -> + toastr.error("Confirmation token is invalid") + $state.go('login') + ) + + vm.confirmUser = -> + delete vm.user.$pwdScore + MnoeAuthSvc.one("/users/confirmation/finalize").patch({user: vm.user}).then( + -> + toastr.success('User confirmed successfully') + $state.go('home.impac') + (error) -> + toastr.error('Confirmation unsuccessful, please try again') + vm.hasClicked = false + ) + + return + ) diff --git a/src/app/views/auth/confirmation/confirm.html b/src/app/views/auth/confirmation/confirm.html new file mode 100644 index 00000000..06b8d4c3 --- /dev/null +++ b/src/app/views/auth/confirmation/confirm.html @@ -0,0 +1,306 @@ +
+
+
+
+ +
+
+
+
diff --git a/src/app/views/auth/confirmation/lounge.controller.coffee b/src/app/views/auth/confirmation/lounge.controller.coffee new file mode 100644 index 00000000..3f0c37c7 --- /dev/null +++ b/src/app/views/auth/confirmation/lounge.controller.coffee @@ -0,0 +1,18 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AuthLoungeCtrl', + ($state, $stateParams, MnoeAuthSvc, toastr, MnoErrorsHandler) -> + vm = @ + vm.user = {} + vm.user.email = $stateParams.email + resentConfirmation = 0 + + vm.resendConfirmation = -> + MnoeAuthSvc.all('/users').all('/confirmation').patch({user: vm.user}).finally( + -> + resentConfirmation += 1 + toastr.info("If your email address exists in our database and is unconfirmed, you will receive an email with instructions in a few minutes.(#{resentConfirmation})") + vm.hasClicked = false + ) + + return + ) diff --git a/src/app/views/auth/confirmation/lounge.html b/src/app/views/auth/confirmation/lounge.html new file mode 100644 index 00000000..6641e9ab --- /dev/null +++ b/src/app/views/auth/confirmation/lounge.html @@ -0,0 +1,40 @@ +
+
+
+ +
+
+
+ diff --git a/src/app/views/auth/confirmation/new.controller.coffee b/src/app/views/auth/confirmation/new.controller.coffee new file mode 100644 index 00000000..6d48452b --- /dev/null +++ b/src/app/views/auth/confirmation/new.controller.coffee @@ -0,0 +1,16 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AuthConfirmRecoveryCtrl', + ($state, $stateParams, MnoeAuthSvc, toastr, MnoErrorsHandler) -> + vm = @ + vm.user = {} + + vm.resendConfirmation = -> + MnoeAuthSvc.all('/users/confirmation').post({user: vm.user}).then( + -> + toastr.info('If your email address exists in our database and is unconfirmed, you will receive an email with instructions in a few minutes.') + $state.go('login') + -> + toastr.error('Your request was unsuccessful, please try again or contact your platform administrator.') + ) + return + ) diff --git a/src/app/views/auth/confirmation/new.html b/src/app/views/auth/confirmation/new.html new file mode 100644 index 00000000..64102d61 --- /dev/null +++ b/src/app/views/auth/confirmation/new.html @@ -0,0 +1,39 @@ +
+
+ +
+
diff --git a/src/app/views/auth/login/login.controller.coffee b/src/app/views/auth/login/login.controller.coffee new file mode 100644 index 00000000..59565886 --- /dev/null +++ b/src/app/views/auth/login/login.controller.coffee @@ -0,0 +1,16 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AuthLoginCtrl', + (Auth, toastr, $state, DEVISE_CONFIG) -> + vm = @ + + vm.login = -> + Auth.login(vm.user).then( + -> + toastr.success('You have successfully logged in!') + $state.go('home.impac') + (error) -> + toastr.error(error.data.error) + ).finally(-> vm.hasClicked = false) + + return + ) diff --git a/src/app/views/auth/login/login.html b/src/app/views/auth/login/login.html new file mode 100644 index 00000000..32585950 --- /dev/null +++ b/src/app/views/auth/login/login.html @@ -0,0 +1,46 @@ +
+
+
+ +
+
+
diff --git a/src/app/views/auth/password/recovery.coffee b/src/app/views/auth/password/recovery.coffee new file mode 100644 index 00000000..e238aaa0 --- /dev/null +++ b/src/app/views/auth/password/recovery.coffee @@ -0,0 +1,29 @@ +angular.module('mnoEnterpriseAngular') + .controller 'PasswordRecoveryCtrl', ($state, Auth, toastr, MnoErrorsHandler) -> + 'ngInject' + + vm = this + vm.request_sent = false + + vm.password_recovery = -> + if vm.form.$invalid && !MnoErrorsHandler.onlyServerError(vm.form) + return + + MnoErrorsHandler.resetErrors(vm.form) + + Auth.sendResetPasswordInstructions(vm.user).then( + -> + toastr.info('devise.passwords.send_paranoid_instructions', { + timeOut: 0, + closeButton: true, + extendedTimeOut: 0 + }) + $state.go('login') + vm.request_sent = true + (error) -> + MnoErrorsHandler.processServerError(error, vm.form) + ).finally( -> vm.hasClicked = false) + + return true + + return diff --git a/src/app/views/auth/password/recovery.html b/src/app/views/auth/password/recovery.html new file mode 100644 index 00000000..25d143d1 --- /dev/null +++ b/src/app/views/auth/password/recovery.html @@ -0,0 +1,37 @@ +
+
+
+ +
+
+
diff --git a/src/app/views/auth/password/recovery.less b/src/app/views/auth/password/recovery.less new file mode 100644 index 00000000..e69de29b diff --git a/src/app/views/auth/password/reset.coffee b/src/app/views/auth/password/reset.coffee new file mode 100644 index 00000000..3c1f6094 --- /dev/null +++ b/src/app/views/auth/password/reset.coffee @@ -0,0 +1,64 @@ +angular.module('mnoEnterpriseAngular') + .controller 'PasswordResetCtrl', ($state, $location, Auth, toastr, MnoErrorsHandler) -> + 'ngInject' + + vm = this + vm.resetConfirmed = false + vm.user = { + $pwdScore: {} + } + + if !$location.search().reset_password_token + toastr.error('devise.passwords.no_token', { + timeOut: 0, + closeButton: true, + extendedTimeOut: 0 + }) + $state.go('password_recovery') + + vm.password_reset = -> + if vm.form.$invalid && !MnoErrorsHandler.onlyServerError(vm.form) + return + else if vm.user.password != vm.user.password_confirmation + toastr.error('Passwords do not match.') + vm.hasClicked = false + return + + MnoErrorsHandler.resetErrors(vm.form) + + vm.user.reset_password_token = $location.search().reset_password_token + Auth.resetPassword(vm.user).then( + -> + vm.resetConfirmed = true + Auth.login(vm.user).then( + -> + toastr.success('devise.passwords.updated', { + timeOut: 10000, + closeButton: true, + }) + $state.go('home.impac') + (error) -> + toastr.success('devise.passwords.updated_not_active', { + timeOut: 10000, + closeButton: true, + }) + ) + ).catch( + (error) -> + if error.status == 422 + toastr.info('devise.passwords.already_reset_error', { + timeOut: 10000, + closeButton: true, + }) + $state.go('login') + else + toastr.error('devise.passwords.unspecified_reset_error', { + timeOut: 10000, + closeButton: true, + }) + MnoErrorsHandler.processServerError(error, vm.form) + ).finally( -> vm.hasClicked = false) + + return true + + return diff --git a/src/app/views/auth/password/reset.html b/src/app/views/auth/password/reset.html new file mode 100644 index 00000000..b1ac6779 --- /dev/null +++ b/src/app/views/auth/password/reset.html @@ -0,0 +1,57 @@ +
+
+
+ +
+
+
--> diff --git a/src/app/views/auth/password/reset.less b/src/app/views/auth/password/reset.less new file mode 100644 index 00000000..53694ce1 --- /dev/null +++ b/src/app/views/auth/password/reset.less @@ -0,0 +1,16 @@ +input-group-addon.pw-strength-indicator { + color: transparent; + + &.danger { + color: @brand-danger; + } + + &.warning, + &.success { + color: @brand-success; + } +} + +.input-group-addon.pw-strength-indicator.classic { + .border-right-radius(4px); +} diff --git a/src/app/views/auth/signup/signup.controller.coffee b/src/app/views/auth/signup/signup.controller.coffee new file mode 100644 index 00000000..450c16f6 --- /dev/null +++ b/src/app/views/auth/signup/signup.controller.coffee @@ -0,0 +1,29 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AuthSignUpCtrl', + ($state, Auth, toastr, MnoErrorsHandler) -> + vm = @ + + vm.errorHandler = MnoErrorsHandler + + vm.checkServerErrors = -> + MnoErrorsHandler.resetErrors(vm.form) if vm.form['email'].$error.server + + vm.signup = -> + # Is form valid and if there is errors that are not server type + if vm.form.$invalid && !MnoErrorsHandler.onlyServerError(vm.form) + return + + # Reset last error + MnoErrorsHandler.resetErrors(vm.form) + + Auth.register(vm.user).then( + (response) -> + toastr.success('Signup successful') + Auth._currentUser = null + $state.go('confirmation_lounge', { email: vm.user.email }) + (error) -> + MnoErrorsHandler.processServerError(error, vm.form) + ).finally(-> vm.hasClicked = false) + + return + ) diff --git a/src/app/views/auth/signup/signup.html b/src/app/views/auth/signup/signup.html new file mode 100644 index 00000000..dd54cb8d --- /dev/null +++ b/src/app/views/auth/signup/signup.html @@ -0,0 +1,59 @@ +
+
+
+ +
+
+
diff --git a/src/app/views/auth/unlock/unlock.controller.coffee b/src/app/views/auth/unlock/unlock.controller.coffee new file mode 100644 index 00000000..67c6121b --- /dev/null +++ b/src/app/views/auth/unlock/unlock.controller.coffee @@ -0,0 +1,18 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AuthUnlockRecoveryCtrl', + ($state, $stateParams, MnoeAuthSvc, toastr, MnoErrorsHandler) -> + vm = @ + vm.user = {} + + vm.resendUnlockInstructions = () -> + vm.hasClicked = true + MnoeAuthSvc.all('/users/unlock').post({user: vm.user}).then( + -> + toastr.info('If your account exists, you will receive an email with instructions for how to unlock it in a few minutes.') + $state.go('login') + -> + toastr.error('Your request was unsuccessful, please try again or contact your platform administrator.') + ) + + return + ) diff --git a/src/app/views/auth/unlock/unlock.html b/src/app/views/auth/unlock/unlock.html new file mode 100644 index 00000000..f90963f5 --- /dev/null +++ b/src/app/views/auth/unlock/unlock.html @@ -0,0 +1,38 @@ +
+
+ +
+
diff --git a/src/app/views/layout.controller.coffee b/src/app/views/layout.controller.coffee index b28d4813..7d0b3e70 100644 --- a/src/app/views/layout.controller.coffee +++ b/src/app/views/layout.controller.coffee @@ -1,7 +1,60 @@ angular.module 'mnoEnterpriseAngular' - .controller 'LayoutController', ($scope, $stateParams, $state, $q, MnoeCurrentUser, MnoeOrganizations) -> + .controller 'LayoutController', ($scope, $rootScope, $stateParams, $state, $q, $timeout, AnalyticsSvc, MnoeCurrentUser, MnoeOrganizations, + MnoeMarketplace, MnoeAppInstances, MnoeConfig) -> 'ngInject' + layout = this + + # Hide the layout with a loader + $rootScope.isLoggedIn = false + + # Used so parent state loads before children states + $rootScope.resourcesLoaded = $q.defer() + + # Load the current user + userPromise = MnoeCurrentUser.get() + + # Load the current organization if defined (url param, cookie or first) + layout.appInstancesDeferred = $q.defer() + orgPromise = MnoeOrganizations.getCurrentOrganisation().then( + (response) -> + # App instances needs to be run after fetching the organization (At least the first call) + MnoeAppInstances.getAppInstances().then( + (appInstances) -> + if MnoeConfig.isOnboardingWizardEnabled() && _.isEmpty(appInstances) + $state.go('onboarding.step1') + + layout.appInstancesDeferred.resolve(appInstances) + ) + + response + ) + + $q.all([userPromise, orgPromise, layout.appInstancesDeferred.promise]).then( + -> + # Allow child state to load + $rootScope.resourcesLoaded.resolve() + + # Display the layout + $rootScope.isLoggedIn = true + + # Pre-load the market place + MnoeMarketplace.getApps() + ).catch( + -> + # Display the layout + $rootScope.isLoggedIn = true + + # Display no organisation message + $rootScope.hasNoOrganisations = true + ) + + $timeout ( -> AnalyticsSvc.init() ) + + $rootScope.$on('$stateChangeSuccess', -> + AnalyticsSvc.update() + ) + # Impac! is displayed only to admin and super admin $scope.$watch(MnoeOrganizations.getSelectedId, (newValue) -> MnoeCurrentUser.get().then( @@ -16,4 +69,6 @@ angular.module 'mnoEnterpriseAngular' ) if newValue? ) + + return diff --git a/src/app/views/layout.html b/src/app/views/layout.html index 0a2755d9..528f6ea5 100644 --- a/src/app/views/layout.html +++ b/src/app/views/layout.html @@ -1,15 +1,23 @@ -
- -
+
+
+ + +
-
- -
+
+ +
-
- - -
-
+
+ +
+ +
+ + +
+
+
+ diff --git a/src/app/views/public/landing/landing.controller.coffee b/src/app/views/public/landing/landing.controller.coffee index 8ed0a6a8..51a92652 100644 --- a/src/app/views/public/landing/landing.controller.coffee +++ b/src/app/views/public/landing/landing.controller.coffee @@ -1,6 +1,6 @@ angular.module 'mnoEnterpriseAngular' .controller('LandingCtrl', - ($scope, $rootScope, $state, $stateParams, $window, MnoeConfig, MnoeMarketplace, URI) -> + ($scope, $rootScope, $state, $stateParams, $window, $translate, MnoeConfig, MnoeMarketplace, URI) -> vm = @ vm.isLoading = true diff --git a/src/app/views/public/public.controller.coffee b/src/app/views/public/public.controller.coffee index ed3eef30..27a4dc77 100644 --- a/src/app/views/public/public.controller.coffee +++ b/src/app/views/public/public.controller.coffee @@ -1,13 +1,12 @@ angular.module 'mnoEnterpriseAngular' - .controller 'PublicController', ($scope, $rootScope, $stateParams, $state, $q, $window, URI, MnoeCurrentUser, MnoeOrganizations, MnoeConfig) -> + .controller 'PublicController', ($scope, $rootScope, $stateParams, $state, $q, URI, MnoeCurrentUser, MnoeOrganizations, MnoeConfig) -> 'ngInject' layout = @ - layout.links = URI layout.isRegistrationEnabled = MnoeConfig.isRegistrationEnabled() - $window.location = URI.login unless MnoeConfig.arePublicApplicationsEnabled() + $state.go('login') unless MnoeConfig.arePublicApplicationsEnabled() MnoeCurrentUser.get().then( (response) -> diff --git a/src/app/views/public/public.html b/src/app/views/public/public.html index 253bbd8e..e02c4171 100644 --- a/src/app/views/public/public.html +++ b/src/app/views/public/public.html @@ -1,12 +1,17 @@
diff --git a/src/images/loader-32x32-bg-inverse.gif b/src/images/loader-32x32-bg-inverse.gif new file mode 100644 index 00000000..32e63fe7 Binary files /dev/null and b/src/images/loader-32x32-bg-inverse.gif differ diff --git a/src/images/loader-32x32-bg-main.gif b/src/images/loader-32x32-bg-main.gif new file mode 100644 index 00000000..522505a7 Binary files /dev/null and b/src/images/loader-32x32-bg-main.gif differ diff --git a/src/images/login-background.jpg b/src/images/login-background.jpg new file mode 100644 index 00000000..b97dd50a Binary files /dev/null and b/src/images/login-background.jpg differ diff --git a/src/images/logo-intuit.png b/src/images/logo-intuit.png new file mode 100644 index 00000000..acfb72bc Binary files /dev/null and b/src/images/logo-intuit.png differ diff --git a/src/index.html b/src/index.html index 9ea5643f..65eaad14 100644 --- a/src/index.html +++ b/src/index.html @@ -37,12 +37,7 @@

You are using an outdated browser. Please upgrade your browser to improve your experience.

-
- - -
- -
+
diff --git a/src/locales/en.json b/src/locales/en.json index 645fd1e7..a58fd5c3 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -184,11 +184,15 @@ "devise.mailer.password_change.subject": "Password Changed", "devise.omniauth_callbacks.failure": "Could not authenticate you from %{kind} because \"%{reason}\".", "devise.omniauth_callbacks.success": "Successfully authenticated from %{kind} account.", - "devise.passwords.no_token": "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided.", + "devise.passwords.no_token": "You can't access that page without coming from a password reset email. Please make sure you used the full URL provided in the email, or enter your email here to receive a new link.", "devise.passwords.send_instructions": "You will receive an email with instructions on how to reset your password in a few minutes.", "devise.passwords.send_paranoid_instructions": "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.", "devise.passwords.updated": "Your password has been changed successfully. You are now signed in.", "devise.passwords.updated_not_active": "Your password has been changed successfully.", + "devise.passwords.do_not_match_error": "Your passwords do not match", + "devise.passwords.confirmation_password_required": "You must enter a password and password confirmation.", + "devise.passwords.already_reset_error": "Your password has already been reset. Log in!", + "devise.passwords.unspecified_reset_error": "Oops, something went wrong... Try again", "devise.registrations.destroyed": "Bye! Your account has been successfully cancelled. We hope to see you again soon.", "devise.registrations.signed_up": "Welcome! You have signed up successfully.", "devise.registrations.signed_up_but_inactive": "You have signed up successfully. However, we could not sign you in because your account is not yet activated.", @@ -349,14 +353,50 @@ "activemodel.attributes.mno_enterprise/app_instance.updated_at": "Updated at", "activemodel.errors.models.mno_enterprise/user.attributes.email.taken": "has already been taken", "activemodel.errors.mno_enterprise/user.password_weak": "Password is not strong enough. Try mixing letters, numbers and cases", - "mno_enterprise.templates.modals.new_organization.title": "Create New Company", - "mno_enterprise.templates.modals.new_organization.enter_new_company_name": "Enter a name for your new company", - "mno_enterprise.templates.modals.new_organization.cancel": "Cancel", - "mno_enterprise.templates.modals.new_organization.create": "Create", - "mno_enterprise.templates.app_feature.single_sign_on": "Single Sign-on", - "mno_enterprise.templates.app_feature.single_billing": "Single billing", - "mno_enterprise.templates.app_feature.data_sharing": "Data-sharing", - "mno_enterprise.templates.onboarding.menu.logout": "Log out", + "mno_enterprise.templates.components.loading_lounge.redirecting_to_app": "Redirecting to %{appname}", + "mno_enterprise.templates.components.loading_lounge.you_will_be_redirected_in": "You will be automatically redirected in %{counter}s (or click the link below)", + "mno_enterprise.templates.components.loading_lounge.go_to_my_app": "Go to my app now!", + "mno_enterprise.templates.components.loading_lounge.app_is_getting_configured": "%{appname} is getting configured", + "mno_enterprise.templates.components.loading_lounge.app_should_be_available_soon": "Don't worry, it should be available in a few seconds.", + "mno_enterprise.templates.components.loading_lounge.app_is_loading": "%{appname} is loading", + "mno_enterprise.templates.components.loading_lounge.app_has_been_idle_for_long": "%{appname} has been idle for a long time. We are currently loading it. It should be available soon!", + "mno_enterprise.templates.components.loading_lounge.app_is_getting_setup": "%{appname} is getting setup", + "mno_enterprise.templates.components.loading_lounge.app_is_preparing_for_first_use": "%{appname} is preparing for the first use. It should be available soon!", + "mno_enterprise.templates.components.loading_lounge.app_has_been_deleted": "%{appname} has been deleted", + "mno_enterprise.templates.components.loading_lounge.app_has_been_removed_by_admin": "%{appname} has been removed by your administrator and is no more accessible.", + "mno_enterprise.templates.components.loading_lounge.app_is_idle": "%{appname} is idle", + "mno_enterprise.templates.components.loading_lounge.cannot_start_app": "Unfortunately we cannot switch this app ON for you", + "mno_enterprise.templates.components.loading_lounge.app_not_found": "Application not found", + "mno_enterprise.templates.components.loading_lounge.app_not_managed_by_us": "The app you requested does not seem to be managed by us.", + "mno_enterprise.templates.components.loading_lounge.did_you_type_the_right_url": "Are you sure you typed the right url?", + "mno_enterprise.templates.components.layout.your_dashboard_is_loading": "Your dashboard is loading...", + "mno_enterprise.templates.components.without_organisation.hello": "Hi, {name}!", + "mno_enterprise.templates.components.without_organisation.text": "Unfortunately, your account is not attached to an organisation. Please contact your administrator.", + "mno_enterprise.templates.components.without_organisation.text_if_can_create": "Unfortunately, your account is not attached to an organisation. Please contact your administrator or create a new one using the button below.", + "mno_enterprise.templates.components.without_organisation.button": "Create an organisation", + "mno_enterprise.templates.components.without_organisation.logout": "Logout", + "mno_enterprise.templates.components.app_install_btn.conflicting_app": "You cannot install this App as it is incompatible with:", + "mno_enterprise.templates.components.app_install_btn.start_app": "Start Now!", + "mno_enterprise.templates.components.app_install_btn.launch_app": "Launch App", + "mno_enterprise.templates.components.app_install_btn.create_account": "Create an account", + "mno_enterprise.templates.components.app_install_btn.create_account_tooltip": "A new tab will open for you to create an account. Afterwards, return here to connect it.", + "mno_enterprise.templates.components.app_install_btn.connect_app": "Connect an existing account", + "mno_enterprise.templates.components.app_install_btn.access_addon": "Access Add-on", + "mno_enterprise.templates.components.app_install_btn.access_addon_tooltip": "This application requires you to set-up the connection in a separate screen.", + "mno_enterprise.templates.components.app_install_btn.insufficient_privilege": "You do not have the sufficient privileges. Contact your administrator.", + "mno_enterprise.templates.components.app_install_btn.success_notification_title": "{name} has been added to your account", + "mno_enterprise.templates.components.app_install_btn.success_launch_notification_body": "To start using {name}, click on the {name} icon then click on \"Launch\".", + "mno_enterprise.templates.components.app_install_btn.success_connect_notification_body": "To start using {name}, click on the {name} icon then click on \"Connect\".", + "mno_enterprise.templates.components.app_install_btn.app_requires_no_set_up": "No set-up required", + "mno_enterprise.templates.components.app_install_btn.app_connects_on_launch": "This app connects when launched.", + "mno_enterprise.templates.components.app_install_btn.app_connected": "(connected)", + "mno_enterprise.templates.components.audit_events_list.table.date": "Date", + "mno_enterprise.templates.components.audit_events_list.table.user": "User", + "mno_enterprise.templates.components.audit_events_list.table.action": "Action", + "mno_enterprise.templates.components.audit_events_list.table.description": "Description", + "mno_enterprise.templates.components.audit_events_list.loading_events": "Loading Events", + "mno_enterprise.templates.components.mno_pagination.items_per_page": "Items per page: ", + "mno_enterprise.templates.onboarding.menu.logout": "Logout", "mno_enterprise.templates.onboarding.breadcrumb.step1": "Welcome", "mno_enterprise.templates.onboarding.breadcrumb.step2": "Select your application(s)", "mno_enterprise.templates.onboarding.breadcrumb.step3": "Connect your application(s)", @@ -384,7 +424,7 @@ "mno_enterprise.templates.onboarding.app_infos_modal.free_trial": "{duration} days free trial", "mno_enterprise.templates.onboarding.connect_apps.connect_applications": "Connect your applications", "mno_enterprise.templates.onboarding.connect_apps.apps_to_connect_now": "Connect your application now to start sharing data immediately.", - "mno_enterprise.templates.onboarding.connect_apps.apps_to_connect_later": "You can connect at any time from your dashboard.", + "mno_enterprise.templates.onboarding.connect_apps.apps_to_connect_later": "You can connect at any time from your dashboard", "mno_enterprise.templates.onboarding.connect_apps.go_to_dashboard": "Go to my dashboard!", "mno_enterprise.templates.onboarding.connect_apps.skip": "Go to my account!", "mno_enterprise.templates.impac.widgets.settings.account.title": "Account to monitor", @@ -832,7 +872,7 @@ "mno_enterprise.templates.dashboard.account.personal_information": "Personal Information", "mno_enterprise.templates.dashboard.account.user_form_name": "Name", "mno_enterprise.templates.dashboard.account.user_form_surname": "Surname", - "mno_enterprise.templates.dashboard.account.us`er_form_email": "Email", + "mno_enterprise.templates.dashboard.account.user_form_email": "Email", "mno_enterprise.templates.dashboard.account.user_form_phone": "Phone", "mno_enterprise.templates.dashboard.account.user_form_save": "Save", "mno_enterprise.templates.dashboard.account.user_form_cancel": "Cancel", @@ -964,6 +1004,184 @@ "mno_enterprise.templates.components.language_selectbox.change_language": "Change language", "mno_enterprise.templates.components.language_selectbox.confirm_header": "Change language to {name}?", "mno_enterprise.templates.components.language_selectbox.confirm": "Are you sure you want to change language to {name}?", + "mno_enterprise.templates.dashboard.teams.index.no_teams_warning": "You do not have any team! Teams are a great way of controlling who has access to applications within your company.", + "mno_enterprise.templates.dashboard.teams.index.add_team": "Add a Team Now!", + "mno_enterprise.templates.dashboard.teams.index.add_team_light": "add team", + "mno_enterprise.templates.dashboard.teams.index.admin_permission_warning": "Admins and Super Admins always have access to all apps. Members unallocated to any team will have access to all apps by default.", + "mno_enterprise.templates.dashboard.teams.index.all": "All", + "mno_enterprise.templates.dashboard.teams.index.no_apps_warning": "It looks like you do not have any application at the moment.", + "mno_enterprise.templates.dashboard.teams.index.add_app": "Add an App Now!", + "mno_enterprise.templates.dashboard.teams.member_add_modal.title": "Add Team Members", + "mno_enterprise.templates.dashboard.teams.member_add_modal.all_belong_to_team_warning": "All your company members already belong to this team", + "mno_enterprise.templates.dashboard.teams.member_add_modal.select_company_members": "Select the company members your wish to add to this team", + "mno_enterprise.templates.dashboard.teams.member_add_modal.close": "Close", + "mno_enterprise.templates.dashboard.teams.member_add_modal.cancel": "Cancel", + "mno_enterprise.templates.dashboard.teams.member_add_modal.add": "Add", + "mno_enterprise.templates.dashboard.teams.member_removal_modal.title": "Confirm action", + "mno_enterprise.templates.dashboard.teams.member_removal_modal.confirmation": "Are you sure you want to remove {fullname} from {teamname}?", + "mno_enterprise.templates.dashboard.teams.member_removal_modal.cancel": "Cancel", + "mno_enterprise.templates.dashboard.teams.member_removal_modal.remove": "Remove", + "mno_enterprise.templates.dashboard.teams.team_add_modal.title": "Create New Team", + "mno_enterprise.templates.dashboard.teams.team_add_modal.enter_team_name": "Enter a name for your new team", + "mno_enterprise.templates.dashboard.teams.team_add_modal.cancel": "Cancel", + "mno_enterprise.templates.dashboard.teams.team_add_modal.create": "Create", + "mno_enterprise.templates.dashboard.teams.team_delete_modal.title": "Confirm action", + "mno_enterprise.templates.dashboard.teams.team_delete_modal.confirmation": "Are you really sure you want to remove {teamname}?", + "mno_enterprise.templates.dashboard.teams.team_delete_modal.cancel": "Cancel", + "mno_enterprise.templates.dashboard.teams.team_delete_modal.remove": "Remove", + "mno_enterprise.templates.dashboard.teams.team_list.no_team_member_warning": "{teamname} does not have any team member yet!", + "mno_enterprise.templates.dashboard.teams.team_list.add_team_members": "Add Team Members", + "mno_enterprise.templates.dashboard.teams.team_list.tbl_name": "Name", + "mno_enterprise.templates.dashboard.teams.team_list.tbl_surname": "Surname", + "mno_enterprise.templates.dashboard.teams.team_list.tbl_email": "Email", + "mno_enterprise.templates.dashboard.teams.team_list.tbl_role": "Role", + "mno_enterprise.templates.dashboard.teams.team_list.remove": "Remove", + "mno_enterprise.templates.impac.dock.launch": "Launch", + "mno_enterprise.templates.impac.dock.connect": "Connect", + "mno_enterprise.templates.impac.dock.settings.settings": "Settings", + "mno_enterprise.templates.impac.dock.settings.status": "Status", + "mno_enterprise.templates.impac.dock.settings.app_name": "Name", + "mno_enterprise.templates.impac.dock.settings.access_add-on_settings": "Access add-on Settings", + "mno_enterprise.templates.impac.dock.settings.manual_data_sharing": "Data sharing", + "mno_enterprise.templates.impac.dock.settings.manual_data_sync": "Manual Data Sync", + "mno_enterprise.templates.impac.dock.settings.disconnect_link": "Disconnect Link", + "mno_enterprise.templates.impac.dock.settings.delete": "Delete", + "mno_enterprise.templates.impac.dock.settings.confirm_app_deletion": "Confirm app deletion", + "mno_enterprise.templates.impac.dock.settings.please_note_this_app": "Please note this app is on a monthly plan. Your billing for this app will be calculated pro rata.", + "mno_enterprise.templates.impac.dock.settings.delete_instructions": "In order to delete your app, please enter the following statement in the box below.", + "mno_enterprise.templates.impac.dock.settings.cancel": "Cancel", + "mno_enterprise.templates.impac.dock.settings.developer_details": "developer details", + "mno_enterprise.templates.impac.dock.settings.linked_to": "Linked to", + "mno_enterprise.templates.impac.dock.settings.organization_id": "Organization id", + "mno_enterprise.templates.impac.dock.settings.group_id": "Group id (uid)", + "mno_enterprise.templates.impac.dock.add_more_apps": "Add more apps", + "mno_enterprise.templates.impac.dock.cancel": "Cancel", + "mno_enterprise.templates.impac.index.dashboard": "Dashboard", + "mno_enterprise.templates.impac.index.new_dashboard": "New Dashboard", + "mno_enterprise.templates.impac.index.source": "Source", + "mno_enterprise.templates.impac.index.add_widget": "Add Widget", + "mno_enterprise.templates.impac.index.delete_dashboard": "Delete", + "mno_enterprise.templates.impac.index.widget_added": "Widget added!", + "mno_enterprise.templates.impac.index.select_widgets_to_add": "Select the widgets you want to add to your dashboard.", + "mno_enterprise.templates.impac.index.all_categories": "All categories", + "mno_enterprise.templates.impac.index.cat_accounting": "Accounting", + "mno_enterprise.templates.impac.index.cat_invoicing": "Invoicing", + "mno_enterprise.templates.impac.index.cat_hr_payroll": "HR / Payroll", + "mno_enterprise.templates.impac.index.cat_sales": "Sales", + "mno_enterprise.templates.impac.index.no_dashboard_warning": "Looks like you do not have any dashboard yet!", + "mno_enterprise.templates.impac.index.benefits_of_dashboards_html": "Dashboards are a great way of visualizing your data and monitoring your business. Our smart widgets will give you deep insight on how your business performs every day in real time, all thanks to our patend data sharing platform Connec!™", + "mno_enterprise.templates.impac.index.create_dashboard": "Create a Dashboard!", + "mno_enterprise.templates.impac.index.note_on_permissions": "Note: dashboards you create will only be accessible by you. Dashboard sharing across users will be added soon.", + "mno_enterprise.templates.impac.index.no_widgets_warning": "Right. Let's get this dashboard warmed up!", + "mno_enterprise.templates.impac.index.click_below_to_add_widgets": "Click on the button below to add a new widget to your dashboard. Our widgets currently focus on financial data but more will come soon!", + "mno_enterprise.templates.impac.index.add_new_widget": "Add a new Widget", + "mno_enterprise.templates.impac.modals.create.title": "Create New Dashboard", + "mno_enterprise.templates.impac.modals.create.label_name": "Name", + "mno_enterprise.templates.impac.modals.create.label_current_company": "Current Company", + "mno_enterprise.templates.impac.modals.create.label_multi_company": "Multi Company", + "mno_enterprise.templates.impac.modals.create.unauthorized_to_access_company_reporting": "Oops! Only Admins and Super Admins can create dashboards for company %{companyname}.", + "mno_enterprise.templates.impac.modals.create.please_select_multi_company": "Please select a \"Multi Company\" dashboard to select data from other companies.", + "mno_enterprise.templates.impac.modals.create.label_companies": "Companies", + "mno_enterprise.templates.impac.modals.create.cancel": "Cancel", + "mno_enterprise.templates.impac.modals.create.add": "Add", + "mno_enterprise.templates.impac.modals.delete.title": "Delete Dashboard", + "mno_enterprise.templates.impac.modals.delete.are_you_sure": "Are you sure you want to delete this analytics dashboard?", + "mno_enterprise.templates.impac.modals.delete.cancel": "Cancel", + "mno_enterprise.templates.impac.modals.delete.add": "Delete", + "mno_enterprise.templates.impac.modals.formula_modal.title": "Custom Calculation", + "mno_enterprise.templates.impac.modals.formula_modal.calculation_explanation": "Make a custom equation with your accounts, and save it as a widget. To create an equation, simply select your accounts in the list, and use the classical operators (example: ({1} + {2}) / {3})", + "mno_enterprise.templates.impac.modals.formula_modal.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.modals.formula_modal.type_formula_below": "Type your formula just below", + "mno_enterprise.templates.impac.modals.formula_modal.formula_result": "Result", + "mno_enterprise.templates.impac.modals.formula_modal.formula_legend": "Legend", + "mno_enterprise.templates.impac.modals.formula_modal.cancel": "Cancel", + "mno_enterprise.templates.impac.modals.formula_modal.save": "Save", + "mno_enterprise.templates.impac.widgets.common.data_not_found.data_not_found": "Data not found", + "mno_enterprise.templates.impac.widgets.common.data_not_found.are_you_missing_an_app": "Are you missing an app ?", + "mno_enterprise.templates.impac.widgets.accounts_accounting_values.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.accounts_accounting_values.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.accounts_accounting_values.save": "Save", + "mno_enterprise.templates.impac.widgets.accounts_accounting_values.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.accounts_assets_summary.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.accounts_assets_summary.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.accounts_assets_summary.save": "Save", + "mno_enterprise.templates.impac.widgets.accounts_assets_summary.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.accounts_balance.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.accounts_balance.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.accounts_balance.save": "Save", + "mno_enterprise.templates.impac.widgets.accounts_balance.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.accounts_comparison.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.accounts_comparison.select_accounts_to_compare": "Select which accounts you wish to compare.", + "mno_enterprise.templates.impac.widgets.accounts_comparison.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.accounts_comparison.save": "Save", + "mno_enterprise.templates.impac.widgets.accounts_comparison.add_account": "+ ADD ACCOUNT", + "mno_enterprise.templates.impac.widgets.accounts_comparison.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.accounts_custom_calculation.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.accounts_expenses_revenue.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.accounts_expenses_revenue.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.accounts_expenses_revenue.save": "Save", + "mno_enterprise.templates.impac.widgets.accounts_expenses_revenue.label_expenses": "Expenses", + "mno_enterprise.templates.impac.widgets.accounts_expenses_revenue.label_revenue": "Revenue", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.save": "Save", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.label_payable": "Payable", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.label_receivable": "Receivable", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.label_accounts_receivable": "Accounts Receivable", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.label_accounts_payable": "Accounts Payable", + "mno_enterprise.templates.impac.widgets.accounts_payable_receivable.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.invoices_list.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.invoices_list.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.invoices_list.save": "Save", + "mno_enterprise.templates.impac.widgets.invoices_list.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.invoices_list.label_paid": "Paid", + "mno_enterprise.templates.impac.widgets.invoices_list.label_due": "Due", + "mno_enterprise.templates.impac.widgets.invoices_list.label_invoiced": "Invoiced", + "mno_enterprise.templates.impac.widgets.invoices_list.no_invoices_found_for": "No %{orderby}invoice found for your %{entitytype}", + "mno_enterprise.templates.impac.widgets.invoices_summary.widget_settings": "Widget settings", + "mno_enterprise.templates.impac.widgets.invoices_summary.cancel": "Cancel", + "mno_enterprise.templates.impac.widgets.invoices_summary.save": "Save", + "mno_enterprise.templates.impac.widgets.invoices_summary.data_being_retrieved": "Your data is being retrieved...", + "mno_enterprise.templates.impac.widgets.settings.account.title": "Account to monitor", + "mno_enterprise.templates.impac.widgets.settings.chart_filters.title": "Chart filters", + "mno_enterprise.templates.impac.widgets.settings.chart_filters.label_top": "Top", + "mno_enterprise.templates.impac.widgets.settings.hist_mode.label_current": "Current", + "mno_enterprise.templates.impac.widgets.settings.hist_mode.label_history": "History", + "mno_enterprise.templates.impac.widgets.settings.organizations.select_companies": "Select Companies", + "mno_enterprise.templates.impac.widgets.settings.time_range.title": "Time range", + "mno_enterprise.templates.impac.widgets.settings.time_range.show_last": "Show last", + "mno_enterprise.templates.modals.new_organization.title": "Create New Company", + "mno_enterprise.templates.modals.new_organization.enter_new_company_name": "Enter a name for your new company", + "mno_enterprise.templates.modals.new_organization.cancel": "Cancel", + "mno_enterprise.templates.modals.new_organization.create": "Create", + "mno_enterprise.auth.confirmations.new.title": "Confirmation", + "mno_enterprise.auth.confirmations.new.resend": "Resend confirmation instructions", + "mno_enterprise.auth.confirmations.show.title": "Almost there", + "mno_enterprise.auth.confirmations.show.confirm": "Confirm my account", + "mno_enterprise.auth.confirmations.lounge.title": "Congratulations! We've sent you a confirmation link, please check your email inbox.", + "mno_enterprise.auth.confirmations.lounge.subtitle": "If you did not receive the email please click resend.", + "mno_enterprise.auth.confirmations.lounge.resend": "Resend!", + "mno_enterprise.auth.confirmations.lounge.support_tip_html": "Or if you prefer you can send an email to our %{email} directly and we'll activate it for you.", + "mno_enterprise.auth.confirmations.lounge.support_team": "customer care service", + "mno_enterprise.auth.confirmations.lounge.support_email_subject": "Signup Verification", + "mno_enterprise.auth.passwords.new.title": "Password Recovery", + "mno_enterprise.auth.passwords.new.recover": "Recover my password", + "mno_enterprise.auth.passwords.edit.title": "Change your password", + "mno_enterprise.auth.passwords.edit.change": "Change my password", + "mno_enterprise.auth.registrations.new.title": "Sign Up", + "mno_enterprise.auth.registrations.new.create": "Create my account!", + "mno_enterprise.auth.registrations.new.user_accept": "I accept", + "mno_enterprise.auth.registrations.new.tos": "the Terms of Use", + "mno_enterprise.auth.sessions.new.title": "Sign In", + "mno_enterprise.auth.sessions.new.sign_in": "Sign in", + "mno_enterprise.auth.shared.links.login": "Already have an account? Log in", + "mno_enterprise.auth.shared.links.signup": "Don't have an account? Sign up", + "mno_enterprise.auth.shared.links.recover_password": "Forgot your password?", + "mno_enterprise.auth.shared.links.resend_confirmation": "Didn't receive confirmation instructions?", + "mno_enterprise.auth.shared.links.unlock": "Didn't receive unlock instructions?", + "mno_enterprise.auth.shared.links.omni_login": "Sign in with %{provider}", + "mno_enterprise.auth.unlocks.new.title": "Resend unlock instructions", + "mno_enterprise.auth.unlocks.new.unlock": "Resend unlock instructions", "mno_enterprise.pages.app_access_unauthorized.title": "App Access Not Granted", "mno_enterprise.pages.app_access_unauthorized.explanation": "It looks like the administrator of this application has not granted you access.", "mno_enterprise.pages.app_access_unauthorized.recommendation": "You should ask your administrator to share this application with you.", @@ -1024,5 +1242,152 @@ "mno_enterprise.provision.select_organization.title": "Choose an organization", "mno_enterprise.provision.select_organization.explanation": "You belong to multiple organizations", "mno_enterprise.provision.select_organization.select": "Please select the organization you are making this new order for", - "mno_enterprise.provision.select_organization.submit": "Go" + "mno_enterprise.provision.select_organization.submit": "Go", + "activemodel.models.mno_enterprise/app": "App", + "activemodel.models.mno_enterprise/app_instance": "App instance", + "activemodel.models.mno_enterprise/credit_card": "Credit card", + "activemodel.models.mno_enterprise/deletion_request": "Deletion request", + "activemodel.models.mno_enterprise/impac/dashboard": "Dashboard", + "activemodel.models.mno_enterprise/impac/widget": "Widget", + "activemodel.models.mno_enterprise/invoice": "Invoice", + "activemodel.models.mno_enterprise/org_invite": "Org invite", + "activemodel.models.mno_enterprise/org_team": "Org team", + "activemodel.models.mno_enterprise/organization": "Organization", + "activemodel.models.mno_enterprise/user": "User", + "activemodel.attributes.mno_enterprise/app.categories": "Categories", + "activemodel.attributes.mno_enterprise/app.created_at": "Created at", + "activemodel.attributes.mno_enterprise/app.description": "Description", + "activemodel.attributes.mno_enterprise/app.id": "Id", + "activemodel.attributes.mno_enterprise/app.key_benefits": "Key benefits", + "activemodel.attributes.mno_enterprise/app.key_features": "Key features", + "activemodel.attributes.mno_enterprise/app.logo": "Logo", + "activemodel.attributes.mno_enterprise/app.name": "Name", + "activemodel.attributes.mno_enterprise/app.nid": "Nid", + "activemodel.attributes.mno_enterprise/app.pictures": "Pictures", + "activemodel.attributes.mno_enterprise/app.popup_description": "Popup description", + "activemodel.attributes.mno_enterprise/app.slug": "Slug", + "activemodel.attributes.mno_enterprise/app.stack": "Stack", + "activemodel.attributes.mno_enterprise/app.terms_url": "Link to terms of use", + "activemodel.attributes.mno_enterprise/app.testimonials": "Testimonials", + "activemodel.attributes.mno_enterprise/app.tiny_description": "Short description", + "activemodel.attributes.mno_enterprise/app.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/app.website": "Website", + "activemodel.attributes.mno_enterprise/app.worldwide_usage": "Worldwide usage", + "activemodel.attributes.mno_enterprise/app_instance.app_id": "App", + "activemodel.attributes.mno_enterprise/app_instance.autostop_at": "Autostop at", + "activemodel.attributes.mno_enterprise/app_instance.autostop_interval": "Autostop interval", + "activemodel.attributes.mno_enterprise/app_instance.billing_type": "Billing type", + "activemodel.attributes.mno_enterprise/app_instance.created_at": "Created at", + "activemodel.attributes.mno_enterprise/app_instance.id": "Id", + "activemodel.attributes.mno_enterprise/app_instance.name": "Name", + "activemodel.attributes.mno_enterprise/app_instance.next_status": "Next status", + "activemodel.attributes.mno_enterprise/app_instance.owner_id": "Owner", + "activemodel.attributes.mno_enterprise/app_instance.owner_type": "Owner type", + "activemodel.attributes.mno_enterprise/app_instance.soa_enabled": "Soa enabled", + "activemodel.attributes.mno_enterprise/app_instance.stack": "Stack", + "activemodel.attributes.mno_enterprise/app_instance.started_at": "Started at", + "activemodel.attributes.mno_enterprise/app_instance.status": "Status", + "activemodel.attributes.mno_enterprise/app_instance.stopped_at": "Stopped at", + "activemodel.attributes.mno_enterprise/app_instance.terminated_at": "Terminated at", + "activemodel.attributes.mno_enterprise/app_instance.uid": "Uid", + "activemodel.attributes.mno_enterprise/app_instance.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/credit_card.billing_address": "Billing address", + "activemodel.attributes.mno_enterprise/credit_card.billing_city": "City", + "activemodel.attributes.mno_enterprise/credit_card.billing_country": "Country", + "activemodel.attributes.mno_enterprise/credit_card.billing_postcode": "Postcode", + "activemodel.attributes.mno_enterprise/credit_card.country": "Country", + "activemodel.attributes.mno_enterprise/credit_card.created_at": "Created at", + "activemodel.attributes.mno_enterprise/credit_card.first_name": "First name", + "activemodel.attributes.mno_enterprise/credit_card.id": "Id", + "activemodel.attributes.mno_enterprise/credit_card.last_name": "Last name", + "activemodel.attributes.mno_enterprise/credit_card.masked_number": "Masked number", + "activemodel.attributes.mno_enterprise/credit_card.month": "Month", + "activemodel.attributes.mno_enterprise/credit_card.number": "Number", + "activemodel.attributes.mno_enterprise/credit_card.organization_id": "Organization", + "activemodel.attributes.mno_enterprise/credit_card.title": "Title", + "activemodel.attributes.mno_enterprise/credit_card.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/credit_card.verification_value": "CVV", + "activemodel.attributes.mno_enterprise/credit_card.year": "Year", + "activemodel.attributes.mno_enterprise/deletion_request.created_at": "Created at", + "activemodel.attributes.mno_enterprise/deletion_request.id": "Id", + "activemodel.attributes.mno_enterprise/deletion_request.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/impac/dashboard.created_at": "Created at", + "activemodel.attributes.mno_enterprise/impac/dashboard.id": "Id", + "activemodel.attributes.mno_enterprise/impac/dashboard.name": "Name", + "activemodel.attributes.mno_enterprise/impac/dashboard.organization_ids": "Organizations", + "activemodel.attributes.mno_enterprise/impac/dashboard.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/impac/dashboard.widgets_order": "Widgets order", + "activemodel.attributes.mno_enterprise/impac/dashboard.widgets_templates": "Widgets templates", + "activemodel.attributes.mno_enterprise/impac/widget.created_at": "Created at", + "activemodel.attributes.mno_enterprise/impac/widget.id": "Id", + "activemodel.attributes.mno_enterprise/impac/widget.name": "Name", + "activemodel.attributes.mno_enterprise/impac/widget.settings": "Settings", + "activemodel.attributes.mno_enterprise/impac/widget.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/impac/widget.widget_category": "Widget category", + "activemodel.attributes.mno_enterprise/impac/widget.width": "Width", + "activemodel.attributes.mno_enterprise/invoice.created_at": "Created at", + "activemodel.attributes.mno_enterprise/invoice.id": "Id", + "activemodel.attributes.mno_enterprise/invoice.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/org_invite.created_at": "Created at", + "activemodel.attributes.mno_enterprise/org_invite.id": "Id", + "activemodel.attributes.mno_enterprise/org_invite.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/org_team.created_at": "Created at", + "activemodel.attributes.mno_enterprise/org_team.id": "Id", + "activemodel.attributes.mno_enterprise/org_team.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/organization.account_frozen": "Account frozen?", + "activemodel.attributes.mno_enterprise/organization.created_at": "Created at", + "activemodel.attributes.mno_enterprise/organization.free_trial_end_at": "Free trial end at", + "activemodel.attributes.mno_enterprise/organization.geo_city": "City", + "activemodel.attributes.mno_enterprise/organization.geo_country_code": "Country code", + "activemodel.attributes.mno_enterprise/organization.geo_currency": "Currency", + "activemodel.attributes.mno_enterprise/organization.geo_state_code": "State code", + "activemodel.attributes.mno_enterprise/organization.geo_tz": "Timezone", + "activemodel.attributes.mno_enterprise/organization.id": "Id", + "activemodel.attributes.mno_enterprise/organization.latitude": "Latitude", + "activemodel.attributes.mno_enterprise/organization.logo": "Logo", + "activemodel.attributes.mno_enterprise/organization.longitude": "Longitude", + "activemodel.attributes.mno_enterprise/organization.mails": "Contact emails", + "activemodel.attributes.mno_enterprise/organization.meta_data": "Metadata", + "activemodel.attributes.mno_enterprise/organization.name": "Name", + "activemodel.attributes.mno_enterprise/organization.soa_enabled": "Connec!™ enabled", + "activemodel.attributes.mno_enterprise/organization.uid": "Uid", + "activemodel.attributes.mno_enterprise/organization.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/user.authenticatable_salt": "Authenticatable salt", + "activemodel.attributes.mno_enterprise/user.company": "Company", + "activemodel.attributes.mno_enterprise/user.confirmation_sent_at": "Confirmation sent at", + "activemodel.attributes.mno_enterprise/user.confirmation_token": "Confirmation token", + "activemodel.attributes.mno_enterprise/user.confirmed_at": "Confirmed at", + "activemodel.attributes.mno_enterprise/user.created_at": "Created at", + "activemodel.attributes.mno_enterprise/user.current_password": "Current password", + "activemodel.attributes.mno_enterprise/user.current_sign_in_at": "Current sign in at", + "activemodel.attributes.mno_enterprise/user.current_sign_in_ip": "Current sign in ip", + "activemodel.attributes.mno_enterprise/user.email": "Email", + "activemodel.attributes.mno_enterprise/user.encrypted_password": "Encrypted password", + "activemodel.attributes.mno_enterprise/user.failed_attempts": "Failed attempts", + "activemodel.attributes.mno_enterprise/user.geo_city": "City", + "activemodel.attributes.mno_enterprise/user.geo_country_code": "Country", + "activemodel.attributes.mno_enterprise/user.geo_state_code": "State code", + "activemodel.attributes.mno_enterprise/user.id": "Id", + "activemodel.attributes.mno_enterprise/user.last_sign_in_at": "Last sign in at", + "activemodel.attributes.mno_enterprise/user.last_sign_in_ip": "Last sign in ip", + "activemodel.attributes.mno_enterprise/user.locked_at": "Locked at", + "activemodel.attributes.mno_enterprise/user.name": "First name", + "activemodel.attributes.mno_enterprise/user.password": "Password", + "activemodel.attributes.mno_enterprise/user.password_confirmation": "Password confirmation", + "activemodel.attributes.mno_enterprise/user.phone": "Phone", + "activemodel.attributes.mno_enterprise/user.phone_country_code": "Ext.", + "activemodel.attributes.mno_enterprise/user.remember_me": "Remember me", + "activemodel.attributes.mno_enterprise/user.remember_created_at": "Remember created at", + "activemodel.attributes.mno_enterprise/user.reset_password_sent_at": "Reset password sent at", + "activemodel.attributes.mno_enterprise/user.reset_password_token": "Reset password token", + "activemodel.attributes.mno_enterprise/user.sign_in_count": "Login count", + "activemodel.attributes.mno_enterprise/user.sso_session": "Sso session", + "activemodel.attributes.mno_enterprise/user.surname": "Surname", + "activemodel.attributes.mno_enterprise/user.uid": "Uid", + "activemodel.attributes.mno_enterprise/user.unconfirmed_email": "Unconfirmed email", + "activemodel.attributes.mno_enterprise/user.unlock_token": "Unlock token", + "activemodel.attributes.mno_enterprise/user.updated_at": "Updated at", + "activemodel.attributes.mno_enterprise/user.website": "Website", + "activemodel.errors.models.mno_enterprise/user.attributes.email.taken": "has already been taken", + "activemodel.errors.mno_enterprise/user.password_weak": "Password is not strong enough. Try mixing letters, numbers and cases" }