diff --git a/changelog/14763.txt b/changelog/14763.txt new file mode 100644 index 000000000000..8eb8ff05c715 --- /dev/null +++ b/changelog/14763.txt @@ -0,0 +1,3 @@ +```release-note:change +ui: Upgrade Ember to version 3.28 +``` \ No newline at end of file diff --git a/ui/.eslintignore b/ui/.eslintignore index 7fc1479f0cc2..b23faf8366fc 100644 --- a/ui/.eslintignore +++ b/ui/.eslintignore @@ -17,6 +17,7 @@ # misc /coverage/ !.* +.*/ .eslintcache # ember-try diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index 5beeccf83a13..16b2b22d38bc 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -32,16 +32,15 @@ module.exports = { // node files { files: [ - '.eslintrc.js', - '.prettierrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'lib/*/index.js', - 'scripts/start-vault.js', - 'server/**/*.js', + './.eslintrc.js', + './.prettierrc.js', + './.template-lintrc.js', + './ember-cli-build.js', + './testem.js', + './blueprints/*/index.js', + './config/**/*.js', + './lib/*/index.js', + './server/**/*.js', ], parserOptions: { sourceType: 'script', @@ -58,5 +57,10 @@ module.exports = { 'node/no-unpublished-require': 'off', }, }, + { + // Test files: + files: ['tests/**/*-test.{js,ts}'], + extends: ['plugin:qunit/recommended'], + }, ], }; diff --git a/ui/.template-lintrc.js b/ui/.template-lintrc.js index 3b0e2a354953..f61b165724d1 100644 --- a/ui/.template-lintrc.js +++ b/ui/.template-lintrc.js @@ -1,14 +1,40 @@ 'use strict'; -const recommended = require('ember-template-lint/lib/config/recommended').rules; // octane extends recommended - no additions as of 3.14 -const stylistic = require('ember-template-lint/lib/config/stylistic').rules; -const testOverrides = { ...recommended, ...stylistic }; -for (const key in testOverrides) { - testOverrides[key] = false; +const fs = require('fs'); +let testOverrides = {}; +try { + // ember-template-lint no longer exports anything so we cannot access the rule definitions conventionally + // read file, convert to json string and parse + const toJSON = (str) => { + return JSON.parse( + str + .slice(str.indexOf(':') + 2) // get rid of export statement + .slice(0, -(str.length - str.lastIndexOf(','))) // remove trailing brackets from export + .replace(/:.*,/g, `: ${false},`) // convert values to false + .replace(/,([^,]*)$/, '$1') // remove last comma + .replace(/'/g, '"') // convert to double quotes + .replace(/(\w[^"].*[^"]):/g, '"$1":') // wrap quotes around single word keys + .trim() + ); + }; + const recommended = toJSON( + fs.readFileSync('node_modules/ember-template-lint/lib/config/recommended.js').toString() + ); + const stylistic = toJSON( + fs.readFileSync('node_modules/ember-template-lint/lib/config/stylistic.js').toString() + ); + testOverrides = { + ...recommended, + ...stylistic, + prettier: false, + }; +} catch (error) { + console.log(error); } module.exports = { - extends: ['octane', 'stylistic'], + plugins: ['ember-template-lint-plugin-prettier'], + extends: ['recommended', 'ember-template-lint-plugin-prettier:recommended'], rules: { 'no-bare-strings': 'off', 'no-action': 'off', @@ -22,7 +48,7 @@ module.exports = { }, ignore: ['lib/story-md', 'tests/**'], // ember language server vscode extension does not currently respect the ignore field - // override all rules manually as workround to align with cli + // override all rules manually as workaround to align with cli overrides: [ { files: ['**/*-test.js'], diff --git a/ui/app/components/b64-toggle.js b/ui/app/components/b64-toggle.js index 1db074e3e285..0f631ba67131 100644 --- a/ui/app/components/b64-toggle.js +++ b/ui/app/components/b64-toggle.js @@ -8,7 +8,7 @@ const B64 = 'base64'; const UTF8 = 'utf-8'; export default Component.extend({ tagName: 'button', - attributeBindings: ['type'], + attributeBindings: ['type', 'data-test-transit-b64-toggle'], type: 'button', classNames: ['button', 'b64-toggle'], classNameBindings: ['isInput:is-input:is-textarea'], diff --git a/ui/app/components/console/log-json.js b/ui/app/components/console/log-json.js index 2eef304e8dea..a176c260ede1 100644 --- a/ui/app/components/console/log-json.js +++ b/ui/app/components/console/log-json.js @@ -2,4 +2,5 @@ import Component from '@ember/component'; export default Component.extend({ 'data-test-component': 'console/log-json', + attributeBindings: ['data-test-component'], }); diff --git a/ui/app/components/console/log-text.js b/ui/app/components/console/log-text.js index 227e2c872d4e..ebd80a51ec19 100644 --- a/ui/app/components/console/log-text.js +++ b/ui/app/components/console/log-text.js @@ -2,4 +2,5 @@ import Component from '@ember/component'; export default Component.extend({ 'data-test-component': 'console/log-text', + attributeBindings: ['data-test-component'], }); diff --git a/ui/app/components/console/output-log.js b/ui/app/components/console/output-log.js index b5f6864487a9..c2993f582d5d 100644 --- a/ui/app/components/console/output-log.js +++ b/ui/app/components/console/output-log.js @@ -2,5 +2,6 @@ import Component from '@ember/component'; export default Component.extend({ 'data-test-component': 'console/output-log', + attributeBindings: ['data-test-component'], log: null, }); diff --git a/ui/app/components/console/ui-panel.js b/ui/app/components/console/ui-panel.js index ea52fee8ba4e..572dec8065ea 100644 --- a/ui/app/components/console/ui-panel.js +++ b/ui/app/components/console/ui-panel.js @@ -20,6 +20,7 @@ export default Component.extend({ controlGroup: service(), store: service(), 'data-test-component': 'console/ui-panel', + attributeBindings: ['data-test-component'], classNames: 'console-ui-panel', classNameBindings: ['isFullscreen:fullscreen'], diff --git a/ui/app/components/generated-item.js b/ui/app/components/generated-item.js index 9b82a7e8ad99..06f17ce8b234 100644 --- a/ui/app/components/generated-item.js +++ b/ui/app/components/generated-item.js @@ -67,16 +67,16 @@ export default Component.extend({ actions: { onKeyUp(name, value) { this.model.set(name, value); - if (this.model.validations) { + if (this.model.validate) { // Set validation error message for updated attribute - this.model.validations.attrs[name] && this.model.validations.attrs[name].isValid - ? set(this.validationMessages, name, '') - : set(this.validationMessages, name, this.model.validations.attrs[name].message); - + const { isValid, state } = this.model.validate(); + if (state[name]) { + state[name].isValid + ? set(this.validationMessages, name, '') + : set(this.validationMessages, name, state[name].errors.join('. ')); + } // Set form button state - this.model.validate().then(({ validations }) => { - this.set('isFormInvalid', !validations.isValid); - }); + this.set('isFormInvalid', !isValid); } else { this.set('isFormInvalid', false); } diff --git a/ui/app/components/hover-copy-button.js b/ui/app/components/hover-copy-button.js index 8c4f08ee3799..63a4564ab846 100644 --- a/ui/app/components/hover-copy-button.js +++ b/ui/app/components/hover-copy-button.js @@ -2,6 +2,7 @@ import Component from '@ember/component'; export default Component.extend({ 'data-test-hover-copy': true, + attributeBindings: ['data-test-hover-copy'], classNameBindings: 'alwaysShow:hover-copy-button-static:hover-copy-button', copyValue: null, alwaysShow: false, diff --git a/ui/app/components/identity/edit-form.js b/ui/app/components/identity/edit-form.js index b4e4bfde2f1d..ae8b6c148e9d 100644 --- a/ui/app/components/identity/edit-form.js +++ b/ui/app/components/identity/edit-form.js @@ -8,6 +8,7 @@ import { waitFor } from '@ember/test-waiters'; export default Component.extend({ flashMessages: service(), 'data-test-component': 'identity-edit-form', + attributeBindings: ['data-test-component'], model: null, // 'create', 'edit', 'merge' diff --git a/ui/app/components/mount-backend-form.js b/ui/app/components/mount-backend-form.js index 3f874362af2a..481fac75b856 100644 --- a/ui/app/components/mount-backend-form.js +++ b/ui/app/components/mount-backend-form.js @@ -45,7 +45,7 @@ export default Component.extend({ showEnable: false, - // cp-validation related properties + // validation related properties validationMessages: null, isFormInvalid: false, @@ -166,27 +166,24 @@ export default Component.extend({ actions: { onKeyUp(name, value) { + this.mountModel.set(name, value); + const { + isValid, + state: { path, maxVersions }, + } = this.mountModel.validate(); // validate path if (name === 'path') { - this.mountModel.set('path', value); - this.mountModel.validations.attrs.path.isValid + path.isValid ? set(this.validationMessages, 'path', '') - : set(this.validationMessages, 'path', this.mountModel.validations.attrs.path.message); + : set(this.validationMessages, 'path', path.errors.join('. ')); } // check maxVersions is a number if (name === 'maxVersions') { - this.mountModel.set('maxVersions', value); - this.mountModel.validations.attrs.maxVersions.isValid + maxVersions.isValid ? set(this.validationMessages, 'maxVersions', '') - : set( - this.validationMessages, - 'maxVersions', - this.mountModel.validations.attrs.maxVersions.message - ); + : set(this.validationMessages, 'maxVersions', maxVersions.errors.join('. ')); } - this.mountModel.validate().then(({ validations }) => { - this.set('isFormInvalid', !validations.isValid); - }); + this.set('isFormInvalid', !isValid); }, onTypeChange(path, value) { if (path === 'type') { diff --git a/ui/app/components/nav-header.js b/ui/app/components/nav-header.js index 3726af4a3f62..7697c6fc1a0b 100644 --- a/ui/app/components/nav-header.js +++ b/ui/app/components/nav-header.js @@ -5,6 +5,7 @@ import { computed } from '@ember/object'; export default Component.extend({ router: service(), 'data-test-navheader': true, + attributeBindings: ['data-test-navheader'], classNameBindings: 'consoleFullscreen:panel-fullscreen', tagName: 'header', navDrawerOpen: false, diff --git a/ui/app/components/pgp-file.js b/ui/app/components/pgp-file.js index e3ff786754ec..fd1a7d1fbbe2 100644 --- a/ui/app/components/pgp-file.js +++ b/ui/app/components/pgp-file.js @@ -7,6 +7,7 @@ const BASE_64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/] export default Component.extend({ 'data-test-pgp-file': true, + attributeBindings: ['data-test-pgp-file'], classNames: ['box', 'is-fullwidth', 'is-marginless', 'is-shadowless'], key: null, index: null, diff --git a/ui/app/components/radial-progress.js b/ui/app/components/radial-progress.js index e3bec6380837..ba2693d3cd89 100644 --- a/ui/app/components/radial-progress.js +++ b/ui/app/components/radial-progress.js @@ -5,7 +5,7 @@ export default Component.extend({ 'data-test-radial-progress': true, tagName: 'svg', classNames: 'radial-progress', - attributeBindings: ['size:width', 'size:height', 'viewBox'], + attributeBindings: ['size:width', 'size:height', 'viewBox', 'data-test-radial-progress'], progressDecimal: null, size: 20, strokeWidth: 1, diff --git a/ui/app/components/regex-validator.hbs b/ui/app/components/regex-validator.hbs index 8a6eb18c951e..10b3a36af614 100644 --- a/ui/app/components/regex-validator.hbs +++ b/ui/app/components/regex-validator.hbs @@ -62,7 +62,7 @@ spellcheck="false" value={{this.testValue}} {{on "change" this.updateTestValue}} - class="input {{if this.regexError "has-error"}}" + class="input {{if this.regexError 'has-error'}}" /> {{#if this.testValue}} diff --git a/ui/app/components/secret-edit-metadata.js b/ui/app/components/secret-edit-metadata.js index 88b09dfb20f4..09fe2750a1d6 100644 --- a/ui/app/components/secret-edit-metadata.js +++ b/ui/app/components/secret-edit-metadata.js @@ -54,6 +54,7 @@ export default class SecretEditMetadata extends Component { if (value) { if (name === 'customMetadata') { // cp validations won't work on an object so performing validations here + // JLR TODO: review this and incorporate into model-validations system /* eslint-disable no-useless-escape */ let regex = /^[^\\]+$/g; // looking for a backward slash value.match(regex) @@ -62,9 +63,12 @@ export default class SecretEditMetadata extends Component { } if (name === 'maxVersions') { this.args.model.maxVersions = value; - this.args.model.validations.attrs.maxVersions.isValid + const { + state: { maxVersions }, + } = this.args.model.validate(); + maxVersions.isValid ? set(this.validationMessages, name, '') - : set(this.validationMessages, name, this.args.model.validations.attrs.maxVersions.message); + : set(this.validationMessages, name, maxVersions.errors.join('. ')); } } diff --git a/ui/app/components/secret-link.js b/ui/app/components/secret-link.js index 30c516a1919c..15a1ec41a2d2 100644 --- a/ui/app/components/secret-link.js +++ b/ui/app/components/secret-link.js @@ -1,28 +1,26 @@ -import { computed } from '@ember/object'; -import Component from '@ember/component'; +import Component from '@glimmer/component'; +import { action } from '@ember/object'; import { encodePath } from 'vault/utils/path-encoding-helpers'; -export default Component.extend({ - onLinkClick() {}, - tagName: '', - // so that ember-test-selectors doesn't log a warning - supportsDataTestProperties: true, - mode: 'list', - - secret: null, - queryParams: null, - ariaLabel: null, - - link: computed('mode', 'secret', function () { - const route = `vault.cluster.secrets.backend.${this.mode}`; - if ((this.mode !== 'versions' && !this.secret) || this.secret === ' ') { +export default class SecretLink extends Component { + get link() { + const { mode, secret } = this.args; + const route = `vault.cluster.secrets.backend.${mode}`; + if ((mode !== 'versions' && !secret) || secret === ' ') { return { route: `${route}-root`, models: [] }; } else { - return { route, models: [encodePath(this.secret)] }; + return { route, models: [encodePath(secret)] }; } - }), - query: computed('queryParams', function () { - const qp = this.queryParams || {}; + } + get query() { + const qp = this.args.queryParams || {}; return qp.isQueryParams ? qp.values : qp; - }), -}); + } + + @action + onLinkClick() { + if (this.args.onLinkClick) { + this.args.onLinkClick(...arguments); + } + } +} diff --git a/ui/app/components/toolbar-secret-link.js b/ui/app/components/toolbar-secret-link.js index e1c6347dc02b..44a1348ca673 100644 --- a/ui/app/components/toolbar-secret-link.js +++ b/ui/app/components/toolbar-secret-link.js @@ -1,3 +1,4 @@ +import Component from '@glimmer/component'; /** * @module ToolbarSecretLink * `ToolbarSecretLink` styles SecretLink for the Toolbar. @@ -16,18 +17,8 @@ * * @param type="" {String} - Use "add" to change icon */ - -import OuterHTML from './outer-html'; -import { computed } from '@ember/object'; - -export default OuterHTML.extend({ - glyph: computed('type', function () { - if (this.type == 'add') { - return 'plus'; - } else { - return 'chevron-right'; - } - }), - tagName: '', - supportsDataTestProperties: true, -}); +export default class ToolbarSecretLink extends Component { + get glyph() { + return this.args.type === 'add' ? 'plus' : 'chevron-right'; + } +} diff --git a/ui/app/decorators/model-validations.js b/ui/app/decorators/model-validations.js new file mode 100644 index 000000000000..4cf91310eeaf --- /dev/null +++ b/ui/app/decorators/model-validations.js @@ -0,0 +1,94 @@ +/* eslint-disable no-console */ +import validators from 'vault/utils/validators'; + +/** + * used to validate properties on a class + * + * decorator expects validations object with the following shape: + * { [propertyKeyName]: [{ type, options, message }] } + * each key in the validations object should refer to the property on the class to apply the validation to + * type refers to the type of validation to apply -- must be exported from validators util for lookup + * options is an optional object for given validator -- min, max, nullable etc. -- see validators in util + * message is added to the errors array and returned from the validate method if validation fails + * each property supports multiple validations provided as an array -- for example, presence and length for string + * + * validations must be invoked using the validate method which is added directly to the decorated class + * const { isValid, state } = this.model.validate(); + * isValid represents the validity of the full class -- if no properties provided in the validations object are invalid this will be true + * state represents the error state of the properties defined in the validations object + * const { isValid, errors } = state[propertyKeyName]; + * isValid represents the validity of the property + * errors will be populated with messages defined in the validations object when validations fail + * since a property can have multiple validations, errors is always returned as an array + * + * full example + * + * import Model from '@ember-data/model'; + * import withModelValidations from 'vault/decorators/model-validations'; + * + * const validations = { foo: [{ type: 'presence', message: 'foo is a required field' }] }; + * @withModelValidations(validations) + * class SomeModel extends Model { foo = null; } + * + * const model = new SomeModel(); + * const { isValid, state } = model.validate(); + * -> isValid = false; + * -> state.foo.isValid = false; + * -> state.foo.errors = ['foo is a required field']; + */ + +export function withModelValidations(validations) { + return function decorator(SuperClass) { + return class ModelValidations extends SuperClass { + static _validations; + + constructor() { + super(...arguments); + if (!validations || typeof validations !== 'object') { + throw new Error('Validations object must be provided to constructor for setup'); + } + this._validations = validations; + } + + validate() { + let isValid = true; + const state = {}; + + for (const key in this._validations) { + const rules = this._validations[key]; + + if (!Array.isArray(rules)) { + console.error( + `Must provide validations as an array for property "${key}" on ${this.modelName} model` + ); + continue; + } + + state[key] = { errors: [] }; + + for (const rule of rules) { + const { type, options, message } = rule; + if (!validators[type]) { + console.error( + `Validator type: "${type}" not found. Available validators: ${Object.keys(validators).join( + ', ' + )}` + ); + continue; + } + if (!validators[type](this[key], options)) { + // consider setting a prop like validationErrors directly on the model + // for now return an errors object + state[key].errors.push(message); + if (isValid) { + isValid = false; + } + } + } + state[key].isValid = !state[key].errors.length; + } + return { isValid, state }; + } + }; + }; +} diff --git a/ui/app/helpers/await.js b/ui/app/helpers/await.js new file mode 100644 index 000000000000..711276c28a4c --- /dev/null +++ b/ui/app/helpers/await.js @@ -0,0 +1,31 @@ +import Helper from '@ember/component/helper'; +import { Promise } from 'rsvp'; + +export default class AwaitHelper extends Helper { + compute([promise]) { + if (!promise || typeof promise.then !== 'function') { + return promise; + } + if (promise !== this.lastPromise) { + this.lastPromise = promise; + this.value = null; + this.resolve(promise); + } + return this.value; + } + async resolve(promise) { + let value; + try { + value = await Promise.resolve(promise); + } catch (error) { + value = error; + } finally { + // ensure this promise is still the newest promise + // otherwise avoid firing recompute since a newer promise is in flight + if (promise === this.lastPromise) { + this.value = value; + this.recompute(); + } + } + } +} diff --git a/ui/app/mixins/policy-edit-controller.js b/ui/app/mixins/policy-edit-controller.js index 4361bfe438b7..8f7798849101 100644 --- a/ui/app/mixins/policy-edit-controller.js +++ b/ui/app/mixins/policy-edit-controller.js @@ -36,8 +36,9 @@ export default Mixin.create({ } return this.transitionToRoute('vault.cluster.policy.show', m.get('policyType'), m.get('name')); }) - .catch((e) => { - model.set('errors', e.errors); + .catch(() => { + // swallow error -- model.errors set by Adapter + return; }); }, diff --git a/ui/app/models/auth-method.js b/ui/app/models/auth-method.js index ea73dd9031be..a54cc80d4f02 100644 --- a/ui/app/models/auth-method.js +++ b/ui/app/models/auth-method.js @@ -1,22 +1,22 @@ import Model, { hasMany, attr } from '@ember-data/model'; -import { alias } from '@ember/object/computed'; -import { computed } from '@ember/object'; +import { alias } from '@ember/object/computed'; // eslint-disable-line +import { computed } from '@ember/object'; // eslint-disable-line import { fragment } from 'ember-data-model-fragments/attributes'; import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs'; import { memberAction } from 'ember-api-actions'; -import { validator, buildValidations } from 'ember-cp-validations'; - import apiPath from 'vault/utils/api-path'; import attachCapabilities from 'vault/lib/attach-capabilities'; +import { withModelValidations } from 'vault/decorators/model-validations'; -const Validations = buildValidations({ - path: validator('presence', { - presence: true, - message: "Path can't be blank.", - }), -}); +const validations = { + path: [{ type: 'presence', message: "Path can't be blank." }], +}; -let ModelExport = Model.extend(Validations, { +// unsure if ember-api-actions will work on native JS class model +// for now create class to use validations and then use classic extend pattern +@withModelValidations(validations) +class AuthMethodModel extends Model {} +const ModelExport = AuthMethodModel.extend({ authConfigs: hasMany('auth-config', { polymorphic: true, inverse: 'backend', async: false }), path: attr('string'), accessor: attr('string'), diff --git a/ui/app/models/policy.js b/ui/app/models/policy.js index af8c3d8a4497..647e367eca8b 100644 --- a/ui/app/models/policy.js +++ b/ui/app/models/policy.js @@ -4,7 +4,6 @@ import { computed } from '@ember/object'; import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; export default Model.extend({ - errors: attr('array'), name: attr('string'), policy: attr('string'), policyType: computed('constructor.modelName', function () { diff --git a/ui/app/models/secret-engine.js b/ui/app/models/secret-engine.js index 45bb60df192a..b30b01b3d2af 100644 --- a/ui/app/models/secret-engine.js +++ b/ui/app/models/secret-engine.js @@ -1,34 +1,25 @@ import Model, { attr } from '@ember-data/model'; -import { computed } from '@ember/object'; -import { equal } from '@ember/object/computed'; +import { computed } from '@ember/object'; // eslint-disable-line +import { equal } from '@ember/object/computed'; // eslint-disable-line import { fragment } from 'ember-data-model-fragments/attributes'; import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs'; -import { validator, buildValidations } from 'ember-cp-validations'; +import { withModelValidations } from 'vault/decorators/model-validations'; // identity will be managed separately and the inclusion // of the system backend is an implementation detail const LIST_EXCLUDED_BACKENDS = ['system', 'identity']; -const Validations = buildValidations({ - path: validator('presence', { - presence: true, - message: "Path can't be blank.", - }), +const validations = { + path: [{ type: 'presence', message: "Path can't be blank." }], maxVersions: [ - validator('number', { - allowString: true, - integer: true, - message: 'Maximum versions must be a number.', - }), - validator('length', { - min: 1, - max: 16, - message: 'You cannot go over 16 characters.', - }), + { type: 'number', options: { asString: true }, message: 'Maximum versions must be a number.' }, + { type: 'length', options: { min: 1, max: 16 }, message: 'You cannot go over 16 characters.' }, ], -}); +}; -export default Model.extend(Validations, { +@withModelValidations(validations) +class SecretEngineModel extends Model {} +export default SecretEngineModel.extend({ path: attr('string'), accessor: attr('string'), name: attr('string'), diff --git a/ui/app/models/secret-v2.js b/ui/app/models/secret-v2.js index f4db1443c186..1f95d6e3d224 100644 --- a/ui/app/models/secret-v2.js +++ b/ui/app/models/secret-v2.js @@ -1,27 +1,21 @@ import Model, { belongsTo, hasMany, attr } from '@ember-data/model'; -import { computed } from '@ember/object'; -import { alias } from '@ember/object/computed'; +import { computed } from '@ember/object'; // eslint-disable-line +import { alias } from '@ember/object/computed'; // eslint-disable-line import { expandAttributeMeta } from 'vault/utils/field-to-attrs'; import KeyMixin from 'vault/mixins/key-mixin'; import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; -import { validator, buildValidations } from 'ember-cp-validations'; +import { withModelValidations } from 'vault/decorators/model-validations'; -const Validations = buildValidations({ +const validations = { maxVersions: [ - validator('number', { - allowString: true, - integer: true, - message: 'Maximum versions must be a number.', - }), - validator('length', { - min: 1, - max: 16, - message: 'You cannot go over 16 characters.', - }), + { type: 'number', options: { asString: true }, message: 'Maximum versions must be a number.' }, + { type: 'length', options: { min: 1, max: 16 }, message: 'You cannot go over 16 characters.' }, ], -}); +}; -export default Model.extend(KeyMixin, Validations, { +@withModelValidations(validations) +class SecretV2Model extends Model {} +export default SecretV2Model.extend(KeyMixin, { failedServerRead: attr('boolean'), engine: belongsTo('secret-engine', { async: false }), engineId: attr('string'), diff --git a/ui/app/routes/vault/cluster/secrets/backend/list.js b/ui/app/routes/vault/cluster/secrets/backend/list.js index cf8d2930f55a..6245fce39a67 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/list.js +++ b/ui/app/routes/vault/cluster/secrets/backend/list.js @@ -36,8 +36,8 @@ export default Route.extend({ case 'alphabet': modelType = 'transform/alphabet'; break; - default: - modelType = 'transform'; // CBS TODO: transform/transformation + default: // CBS TODO: transform/transformation + modelType = 'transform'; break; } return modelType; diff --git a/ui/app/services/path-help.js b/ui/app/services/path-help.js index cd2f346731c0..8e51da5a5b71 100644 --- a/ui/app/services/path-help.js +++ b/ui/app/services/path-help.js @@ -14,7 +14,7 @@ import { resolve, reject } from 'rsvp'; import { debug } from '@ember/debug'; import { dasherize, capitalize } from '@ember/string'; import { singularize } from 'ember-inflector'; -import buildValidations from 'vault/utils/build-api-validators'; +import { withModelValidations } from 'vault/decorators/model-validations'; import generatedItemAdapter from 'vault/adapters/generated-item-list'; export function sanitizePath(path) { @@ -36,6 +36,7 @@ export default Service.extend({ getNewModel(modelType, backend, apiPath, itemType) { let owner = getOwner(this); const modelName = `model:${modelType}`; + const modelFactory = owner.factoryFor(modelName); let newModel, helpUrl; // if we have a factory, we need to take the existing model into account @@ -298,8 +299,17 @@ export default Service.extend({ // Build and add validations on model // NOTE: For initial phase, initialize validations only for user pass auth if (backend === 'userpass') { - let validations = buildValidations(fieldGroups); - newModel = newModel.extend(validations); + const validations = fieldGroups.reduce((obj, element) => { + if (element.default) { + element.default.forEach((v) => { + obj[v.name] = [{ type: 'presence', message: `${v.name} can't be black` }]; + }); + } + return obj; + }, {}); + @withModelValidations(validations) + class GeneratedItemModel extends newModel {} + newModel = GeneratedItemModel; } } } catch (err) { diff --git a/ui/app/templates/components/alert-popup.hbs b/ui/app/templates/components/alert-popup.hbs index e74ed6c5a94e..2a08deced4b0 100644 --- a/ui/app/templates/components/alert-popup.hbs +++ b/ui/app/templates/components/alert-popup.hbs @@ -11,7 +11,7 @@ {{this.type.text}} {{#if this.message}} -

{{this.message}}

+

{{this.message}}

{{/if}} diff --git a/ui/app/templates/components/alphabet-edit.hbs b/ui/app/templates/components/alphabet-edit.hbs index 67d4937f77d5..8d58eff1b005 100644 --- a/ui/app/templates/components/alphabet-edit.hbs +++ b/ui/app/templates/components/alphabet-edit.hbs @@ -35,7 +35,7 @@ Edit alphabet @@ -89,7 +89,7 @@ Cancel diff --git a/ui/app/templates/components/auth-config-form/config.hbs b/ui/app/templates/components/auth-config-form/config.hbs index cc91e466438d..d60d73724d97 100644 --- a/ui/app/templates/components/auth-config-form/config.hbs +++ b/ui/app/templates/components/auth-config-form/config.hbs @@ -14,7 +14,7 @@ diff --git a/ui/app/templates/components/auth-jwt.hbs b/ui/app/templates/components/auth-jwt.hbs index 7bad92761ef2..e178623edc85 100644 --- a/ui/app/templates/components/auth-jwt.hbs +++ b/ui/app/templates/components/auth-jwt.hbs @@ -45,7 +45,7 @@ data-test-auth-submit={{true}} type="submit" disabled={{@disabled}} - class="button is-primary {{if @disabled "is-loading"}}" + class="button is-primary {{if @disabled 'is-loading'}}" id="auth-submit" > {{#if this.isOIDC}} diff --git a/ui/app/templates/components/clients/history.hbs b/ui/app/templates/components/clients/history.hbs index 6bad4a549530..f7f93304de69 100644 --- a/ui/app/templates/components/clients/history.hbs +++ b/ui/app/templates/components/clients/history.hbs @@ -138,7 +138,7 @@ @runningTotals={{this.totalUsageCounts}} @timestamp={{this.responseTimestamp}} /> - {{!-- TODO CMB: remove UsageStats component from history tab (and update associated tests) --}} + {{! TODO CMB: remove UsageStats component from history tab (and update associated tests) }} {{#if this.hasAttributionData}}

Vault client counts

- A client is any user or service that interacts with Vault. They are made up of entity clients and non-entity clients. The total client count number is an important consideration for Vault billing. + A client is any user or service that interacts with Vault. They are made up of entity clients and non-entity clients. + The total client count number is an important consideration for Vault billing.

diff --git a/ui/app/templates/components/config-pki-ca.hbs b/ui/app/templates/components/config-pki-ca.hbs index 3bf28bfb2b78..2ea45a304b66 100644 --- a/ui/app/templates/components/config-pki-ca.hbs +++ b/ui/app/templates/components/config-pki-ca.hbs @@ -73,7 +73,7 @@
- + Cancel
@@ -246,21 +248,23 @@ {{/each}} {{else}} - - {{#if (get this (camelize (concat "show" group)))}} -
- {{#each fields as |attr|}} - - {{/each}} -
- {{/if}} + {{#let (camelize (concat "show" group)) as |prop|}} + + {{#if (get this prop)}} +
+ {{#each fields as |attr|}} + + {{/each}} +
+ {{/if}} + {{/let}} {{/if}} {{/each-in}} {{/each}} @@ -287,7 +291,7 @@
- + Cancel
diff --git a/ui/app/templates/components/database-role-edit.hbs b/ui/app/templates/components/database-role-edit.hbs index 9293e23e2ec2..416608324547 100644 --- a/ui/app/templates/components/database-role-edit.hbs +++ b/ui/app/templates/components/database-role-edit.hbs @@ -52,7 +52,7 @@ @mode="edit" @replace={{true}} @queryParams={{query-params itemType="role"}} - @data-test-edit-link={{true}} + data-test-edit-link={{true}} > Edit role
@@ -119,7 +119,7 @@ data-test-secret-save type="submit" disabled={{this.loading}} - class="button is-primary {{if this.loading "is-loading"}}" + class="button is-primary {{if this.loading 'is-loading'}}" > {{#if (eq @mode "create")}} Create role @@ -145,7 +145,7 @@ {{/if}}
- + Cancel
diff --git a/ui/app/templates/components/diff-version-selector.hbs b/ui/app/templates/components/diff-version-selector.hbs index aa66f777e6c2..ccc3127e7b70 100644 --- a/ui/app/templates/components/diff-version-selector.hbs +++ b/ui/app/templates/components/diff-version-selector.hbs @@ -25,7 +25,7 @@ {{leftSideSecretVersion.version}} {{#if (and - (eq leftSideSecretVersion.version (or this.leftSideVersionSelected @args.model.currentVersion)) + (eq leftSideSecretVersion.version (or this.leftSideVersionSelected @model.currentVersion)) (not leftSideSecretVersion.destroyed) (not leftSideSecretVersion.deleted) ) diff --git a/ui/app/templates/components/form-field-groups-loop.hbs b/ui/app/templates/components/form-field-groups-loop.hbs index 37afcf163d8a..fdb5c9b9f5e2 100644 --- a/ui/app/templates/components/form-field-groups-loop.hbs +++ b/ui/app/templates/components/form-field-groups-loop.hbs @@ -8,21 +8,23 @@ {{/unless}} {{/each}} {{else}} - - {{#if (get @model (camelize (concat "show" group)))}} -
- {{#each fields as |attr|}} - - {{/each}} -
- {{/if}} + {{#let (camelize (concat "show" group)) as |prop|}} + + {{#if (get @model prop)}} +
+ {{#each fields as |attr|}} + + {{/each}} +
+ {{/if}} + {{/let}} {{/if}} {{/each-in}} {{/each}} \ No newline at end of file diff --git a/ui/app/templates/components/generate-credentials.hbs b/ui/app/templates/components/generate-credentials.hbs index f79df2bba7d3..2cf94f28864b 100644 --- a/ui/app/templates/components/generate-credentials.hbs +++ b/ui/app/templates/components/generate-credentials.hbs @@ -145,7 +145,7 @@