diff --git a/addons/api/package.json b/addons/api/package.json index 0debd68d7..1bf61697c 100644 --- a/addons/api/package.json +++ b/addons/api/package.json @@ -42,7 +42,7 @@ "ember-auto-import": "^1.10.1", "ember-cli": "~3.27.0", "ember-cli-dependency-checker": "^3.2.0", - "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "^4.0.1", "ember-cli-update": "^0.54.6", diff --git a/addons/auth/package.json b/addons/auth/package.json index b0fa7a0a6..9d27bee7c 100644 --- a/addons/auth/package.json +++ b/addons/auth/package.json @@ -37,7 +37,7 @@ "ember-auto-import": "^1.10.1", "ember-cli": "~3.27.0", "ember-cli-dependency-checker": "^3.2.0", - "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "^4.0.1", "ember-cli-update": "^0.54.6", diff --git a/addons/core/package.json b/addons/core/package.json index 88cbffcda..1e3f3ef9a 100644 --- a/addons/core/package.json +++ b/addons/core/package.json @@ -52,7 +52,7 @@ "ember-auto-import": "^1.10.1", "ember-cli": "~3.27.0", "ember-cli-dependency-checker": "^3.2.0", - "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "^4.0.1", "ember-disable-prototype-extensions": "^1.1.3", diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml index dd00b107b..49bde4799 100644 --- a/addons/core/translations/actions/en-us.yaml +++ b/addons/core/translations/actions/en-us.yaml @@ -33,3 +33,4 @@ edit-form: Edit Form change-origin: Change Origin URL change-state: Change State close: Close +toggle-secret: Toggle secret visibility diff --git a/addons/rose/addon/components/rose/form/checkbox/index.hbs b/addons/rose/addon/components/rose/form/checkbox/index.hbs index a660c270d..d9b655458 100644 --- a/addons/rose/addon/components/rose/form/checkbox/index.hbs +++ b/addons/rose/addon/components/rose/form/checkbox/index.hbs @@ -1,4 +1,4 @@ -
+
+ + +```htmlbars + +``` + + + {{ + template: hbs` + + + + `, + context: { + description: text('description', 'Short description in label'), + helperText: text('helperText', 'This is an optional help text.'), + checked: boolean('checked', false), + error: boolean('error', false), + disabled: boolean('disabled', false), + inline: boolean('inline', false) + } + }} + + diff --git a/addons/rose/addon/components/rose/form/radio/group/index.hbs b/addons/rose/addon/components/rose/form/radio/group/index.hbs index fc34bd1cc..de2b010b6 100644 --- a/addons/rose/addon/components/rose/form/radio/group/index.hbs +++ b/addons/rose/addon/components/rose/form/radio/group/index.hbs @@ -1,3 +1,3 @@ {{yield (hash - radio=(component 'rose/form/radio/radio' name=@name selectedValue=@selectedValue disabled=@disabled changed=@changed) -)}} + radio=(component 'rose/form/radio/radio' name=@name selectedValue=@selectedValue disabled=@disabled changed=@changed inline=@inline) +)}} \ No newline at end of file diff --git a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx index bafffaaa4..515c7ca35 100644 --- a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx +++ b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx @@ -15,6 +15,7 @@ Renders a form radiofield group. @name={{name}} @selectedValue="disagree" @changed={{fn}} + @inline={{false}} as |radioGroup| > +
+ + +```htmlbars + +``` + + + {{ + template: hbs` + + + + `, + context: { + error: boolean('error', false), + value: text('value', 'value and selectedValue must match to select radiofield'), + selectedValue: text( + 'selectedValue', + 'value and selectedValue must match to select radiofield' + ), + disabled: boolean('disabled', false), + icon: select( + 'icon', + { + 'none': null, + 'user-organization': 'user-organization', + 'copy-action': 'copy-action', + 'unfold-more': 'unfold-more', + 'unfold-less': 'unfold-less', + }, + null + ), + inline: boolean('inline', false) + } + }} + \ No newline at end of file diff --git a/addons/rose/addon/styles/rose/components/form/_checkbox.scss b/addons/rose/addon/styles/rose/components/form/_checkbox.scss index ec86a0367..6dd2c6802 100644 --- a/addons/rose/addon/styles/rose/components/form/_checkbox.scss +++ b/addons/rose/addon/styles/rose/components/form/_checkbox.scss @@ -2,6 +2,10 @@ @use '../../utilities/type'; .rose-form-checkbox { + &.inline { + display: inline-block; + margin-right: sizing.rems(s); + } @include type.type(m); position: relative; margin-bottom: sizing.rems(m); diff --git a/addons/rose/addon/styles/rose/components/form/_fieldset.scss b/addons/rose/addon/styles/rose/components/form/_fieldset.scss index 33f5ad5d9..f35d3755c 100644 --- a/addons/rose/addon/styles/rose/components/form/_fieldset.scss +++ b/addons/rose/addon/styles/rose/components/form/_fieldset.scss @@ -5,7 +5,7 @@ margin-bottom: sizing.rems(m); .rose-form-fieldset-title { - @include type.type(l, bold); + @include type.type(s, bold); } .rose-form-fieldset-description { diff --git a/addons/rose/addon/styles/rose/components/form/_radio.scss b/addons/rose/addon/styles/rose/components/form/_radio.scss index 860c39ca5..3c94ac816 100644 --- a/addons/rose/addon/styles/rose/components/form/_radio.scss +++ b/addons/rose/addon/styles/rose/components/form/_radio.scss @@ -21,8 +21,13 @@ $radio-svg: ( ); $data-svg-prefix: 'data:image/svg+xml;utf-8,'; - .rose-form-radio { + &.inline { + display: inline-block; + .rose-form-radio-label { + margin-right: sizing.rems(s); + } + } @include type.type(m); position: relative; margin-bottom: sizing.rems(xs); diff --git a/addons/rose/package.json b/addons/rose/package.json index 822c9c6fa..dad7f51cb 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -55,7 +55,7 @@ "ember-auto-import": "^1.10.1", "ember-cli": "~3.27.0", "ember-cli-dependency-checker": "^3.2.0", - "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-sass": "^10.0.1", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "^4.0.1", diff --git a/ui/admin/app/components/form/credential-store/index.hbs b/ui/admin/app/components/form/credential-store/index.hbs index f972854c1..b70183ed3 100644 --- a/ui/admin/app/components/form/credential-store/index.hbs +++ b/ui/admin/app/components/form/credential-store/index.hbs @@ -2,6 +2,7 @@ @onSubmit={{@submit}} @cancel={{@cancel}} @disabled={{@model.isSaving}} + @error={{@model.errors.base}} @showEditToggle={{if @model.isNew false true}} as |form| > @@ -18,16 +19,38 @@ @value={{@model.name}} @label={{t 'form.name.label'}} @helperText={{t 'form.name.help'}} + @error={{@model.errors.name}} readonly={{false}} - /> + as |field| + > + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + disabled={{@model.isSaving}} + readonly={{false}} + as |field| + > + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + { + return this.server.schema.credentialStores.all().models.length; + }; + authenticateSession({}); }); test('visiting /credential-stores', async function (assert) { - assert.expect(1); + assert.expect(2); await visit(urls.credentialStores); await a11yAudit(); assert.equal(currentURL(), urls.credentialStores); + await visit(urls.credentialStore); + await a11yAudit(); + assert.equal(currentURL(), urls.credentialStore); + }); + + test('visiting an unknown credential store display 404 message', async function (assert) { + assert.expect(1); + await visit(urls.unknownCredentialStore); + await a11yAudit(); + assert.equal( + find('.rose-message-subtitle').textContent.trim(), + 'Error 404' + ); + }); + + test('can create a new credential stores', async function (assert) { + assert.expect(1); + const count = getCredentialStoresCount(); + await visit(urls.newCredentialStore); + await fillIn('[name="name"]', 'random string'); + await click('[type="submit"]'); + assert.equal(getCredentialStoresCount(), count + 1); + }); + + test('can cancel create new credential stores', async function (assert) { + assert.expect(2); + const count = getCredentialStoresCount(); + await visit(urls.newCredentialStore); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.equal(currentURL(), urls.credentialStores); + assert.equal(getCredentialStoresCount(), count); + }); + + test('saving a new credential store with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/credential-stores', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.newCredentialStore); + await click('[type="submit"]'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'The request was invalid.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.' + ); + }); + + test('can save changes to existing credential store', async function (assert) { + assert.expect(3); + assert.notEqual(instances.credentialStore.name, 'random string'); + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="submit"]'); + assert.equal(currentURL(), urls.credentialStore); + assert.equal( + this.server.schema.credentialStores.all().models[0].name, + 'random string' + ); + }); + + test('can cancel changes to existing credential store', async function (assert) { + assert.expect(2); + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.notEqual(instances.credentialStore.name, 'random string'); + assert.equal(find('[name="name"]').value, instances.credentialStore.name); + }); + + test('saving an existing credential store with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.patch('/credential-stores/:id', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + await click('[type="submit"]'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'The request was invalid.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.' + ); + }); + + test('can discard unsaved credential store changes via dialog', async function (assert) { + assert.expect(5); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.credentialStore.name, 'random string'); + + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + assert.equal(currentURL(), urls.credentialStore); + + try { + await visit(urls.credentialStores); + } catch (e) { + assert.ok(find('.rose-dialog')); + await click('.rose-dialog-footer button:first-child', 'Click Discard'); + assert.equal(currentURL(), urls.credentialStores); + assert.notEqual( + this.server.schema.credentialStores.all().models[0].name, + 'random string' + ); + } + }); + + test('can cancel discard unsaved credential store via dialog', async function (assert) { + assert.expect(5); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.credentialStore.name, 'random string'); + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + assert.equal(currentURL(), urls.credentialStore); + + try { + await visit(urls.credentialStores); + } catch (e) { + assert.ok(find('.rose-dialog')); + await click('.rose-dialog-footer button:last-child'); + assert.equal(currentURL(), urls.credentialStore); + assert.notEqual( + this.server.schema.credentialStores.all().models[0].name, + 'random string' + ); + } }); }); diff --git a/ui/desktop/app/components/hidden-secret/index.hbs b/ui/desktop/app/components/hidden-secret/index.hbs index 8b5cc1cac..6660a77e5 100644 --- a/ui/desktop/app/components/hidden-secret/index.hbs +++ b/ui/desktop/app/components/hidden-secret/index.hbs @@ -1,9 +1,11 @@
- + > + {{t 'actions.toggle-secret'}} + {{this.textMask}} {{/if}}
\ No newline at end of file diff --git a/ui/desktop/app/styles/app.scss b/ui/desktop/app/styles/app.scss index f65623c56..617f09ea5 100644 --- a/ui/desktop/app/styles/app.scss +++ b/ui/desktop/app/styles/app.scss @@ -478,20 +478,22 @@ $chevron-down: url("data:image/svg+xml;utf-8,