diff --git a/changelog/20431.txt b/changelog/20431.txt new file mode 100644 index 0000000000..a0083d879e --- /dev/null +++ b/changelog/20431.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Add download button for each secret value in KV v2 +``` diff --git a/ui/app/components/secret-create-or-update.js b/ui/app/components/secret-create-or-update.js index 47aedac955..0427127a08 100644 --- a/ui/app/components/secret-create-or-update.js +++ b/ui/app/components/secret-create-or-update.js @@ -253,22 +253,16 @@ export default class SecretCreateOrUpdate extends Component { this.codemirrorString = this.args.secretData.toJSONString(true); } @action + handleMaskedInputChange(secret, index, value) { + const row = { ...secret, value }; + set(this.args.secretData, index, row); + this.handleChange(); + } + @action handleChange() { this.codemirrorString = this.args.secretData.toJSONString(true); set(this.args.modelForData, 'secretData', this.args.secretData.toJSON()); } - //submit on shift + enter - @action - handleKeyDown(e) { - e.stopPropagation(); - if (!(e.keyCode === keys.ENTER && e.metaKey)) { - return; - } - const $form = this.element.querySelector('form'); - if ($form.length) { - $form.submit(); - } - } @action updateValidationErrorCount(errorCount) { this.validationErrorCount = errorCount; diff --git a/ui/app/styles/components/masked-input.scss b/ui/app/styles/components/masked-input.scss index 51bc8392fe..03d7868934 100644 --- a/ui/app/styles/components/masked-input.scss +++ b/ui/app/styles/components/masked-input.scss @@ -49,14 +49,16 @@ } .button.masked-input-toggle, -.button.copy-button { +.button.copy-button, +.button.download-button { min-width: $spacing-xl; border-left: 0; color: $grey; box-shadow: 0 3px 1px 0px rgba(10, 10, 10, 0.12); } -.button.copy-button { +.button.copy-button, +.button.download-button { border-radius: 0; } @@ -66,7 +68,8 @@ .display-only { .button.masked-input-toggle, - .button.copy-button { + .button.copy-button, + .button.download-button { background: transparent; height: auto; line-height: 1rem; diff --git a/ui/app/templates/components/configure-ssh-secret.hbs b/ui/app/templates/components/configure-ssh-secret.hbs index 94136fe9bc..8e61dc38e9 100644 --- a/ui/app/templates/components/configure-ssh-secret.hbs +++ b/ui/app/templates/components/configure-ssh-secret.hbs @@ -46,7 +46,13 @@ Private key
- - {{! template-lint-enable no-down-event-binding }} - {{/if}} - {{#if this.allowCopy}} - - - - {{/if}} - -
\ No newline at end of file diff --git a/ui/tests/integration/components/masked-input-test.js b/ui/tests/integration/components/masked-input-test.js index 933f36c66e..a4ee8e075e 100644 --- a/ui/tests/integration/components/masked-input-test.js +++ b/ui/tests/integration/components/masked-input-test.js @@ -16,68 +16,40 @@ module('Integration | Component | masked input', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { - await render(hbs`{{masked-input}}`); - assert.dom('[data-test-masked-input]').exists('shows expiration beacon'); - }); - - test('it renders a textarea', async function (assert) { - await render(hbs`{{masked-input}}`); - + await render(hbs``); + assert.dom('[data-test-masked-input]').exists('shows masked input'); assert.ok(component.textareaIsPresent); + assert.dom('[data-test-textarea]').hasClass('masked-font', 'it renders an input with obscure font'); + assert.notOk(component.copyButtonIsPresent, 'does not render copy button by default'); + assert.notOk(component.downloadButtonIsPresent, 'does not render download button by default'); + + await component.toggleMasked(); + assert.dom('.masked-value').doesNotHaveClass('masked-font', 'it unmasks when show button is clicked'); + await component.toggleMasked(); + assert.dom('.masked-value').hasClass('masked-font', 'it remasks text when button is clicked'); }); - test('it renders an input with obscure font', async function (assert) { - await render(hbs`{{masked-input}}`); - - assert.dom('[data-test-textarea]').hasClass('masked-font', 'loading class with correct font'); - }); - - test('it renders obscure font when displayOnly', async function (assert) { + test('it renders correctly when displayOnly', async function (assert) { this.set('value', 'value'); - await render(hbs`{{masked-input displayOnly=true value=this.value}}`); + await render(hbs``); - assert.dom('.masked-value').hasClass('masked-font', 'loading class with correct font'); - }); - - test('it does not render a textarea when displayOnly is true', async function (assert) { - await render(hbs`{{masked-input displayOnly=true}}`); - - assert.notOk(component.textareaIsPresent); + assert.dom('.masked-value').hasClass('masked-font', 'value has obscured font'); + assert.notOk(component.textareaIsPresent, 'it does not render a textarea when displayOnly is true'); }); test('it renders a copy button when allowCopy is true', async function (assert) { - await render(hbs`{{masked-input allowCopy=true}}`); - + await render(hbs``); assert.ok(component.copyButtonIsPresent); }); - test('it does not render a copy button when allowCopy is false', async function (assert) { - await render(hbs`{{masked-input allowCopy=false}}`); - - assert.notOk(component.copyButtonIsPresent); - }); - - test('it unmasks text when button is clicked', async function (assert) { - this.set('value', 'value'); - await render(hbs`{{masked-input value=this.value}}`); - await component.toggleMasked(); - - assert.dom('.masked-value').doesNotHaveClass('masked-font'); - }); - - test('it remasks text when button is clicked', async function (assert) { - this.set('value', 'value'); - await render(hbs`{{masked-input value=this.value}}`); - - await component.toggleMasked(); - await component.toggleMasked(); - - assert.dom('.masked-value').hasClass('masked-font'); + test('it renders a download button when allowDownload is true', async function (assert) { + await render(hbs``); + assert.ok(component.downloadButtonIsPresent); }); test('it shortens all outputs when displayOnly and masked', async function (assert) { this.set('value', '123456789-123456789-123456789'); - await render(hbs`{{masked-input value=this.value displayOnly=true}}`); + await render(hbs``); const maskedValue = document.querySelector('.masked-value').innerText; assert.strictEqual(maskedValue.length, 11); @@ -88,7 +60,7 @@ module('Integration | Component | masked input', function (hooks) { test('it does not unmask text on focus', async function (assert) { this.set('value', '123456789-123456789-123456789'); - await render(hbs`{{masked-input value=this.value}}`); + await render(hbs``); assert.dom('.masked-value').hasClass('masked-font'); await focus('.masked-value'); assert.dom('.masked-value').hasClass('masked-font'); @@ -96,7 +68,7 @@ module('Integration | Component | masked input', function (hooks) { test('it does not remove value on tab', async function (assert) { this.set('value', 'hello'); - await render(hbs`{{masked-input value=this.value}}`); + await render(hbs``); await triggerKeyEvent('[data-test-textarea]', 'keydown', 9); await component.toggleMasked(); const unMaskedValue = document.querySelector('.masked-value').value; diff --git a/ui/tests/pages/components/masked-input.js b/ui/tests/pages/components/masked-input.js index c4894b04f6..61ec76b25b 100644 --- a/ui/tests/pages/components/masked-input.js +++ b/ui/tests/pages/components/masked-input.js @@ -8,5 +8,6 @@ import { clickable, isPresent } from 'ember-cli-page-object'; export default { textareaIsPresent: isPresent('[data-test-textarea]'), copyButtonIsPresent: isPresent('[data-test-copy-button]'), + downloadButtonIsPresent: isPresent('[data-test-download-button]'), toggleMasked: clickable('[data-test-button="toggle-masked"]'), };