diff --git a/ui/.gitignore b/ui/.gitignore index 6680946d45..944ab0c512 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -17,6 +17,7 @@ /npm-debug.log* /testem.log /yarn-error.log +/.storybook/preview-head.html # ember-try /.node_modules.ember-try/ diff --git a/ui/.storybook/preview-head.html b/ui/.storybook/preview-head.html deleted file mode 100644 index d35e63834d..0000000000 --- a/ui/.storybook/preview-head.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - diff --git a/ui/app/helpers/secret-query-params.js b/ui/app/helpers/secret-query-params.js new file mode 100644 index 0000000000..483700274b --- /dev/null +++ b/ui/app/helpers/secret-query-params.js @@ -0,0 +1,10 @@ +import { helper } from '@ember/component/helper'; + +export function secretQueryParams([backendType]) { + if (backendType === 'transit') { + return { tab: 'actions' }; + } + return; +} + +export default helper(secretQueryParams); diff --git a/ui/app/macros/lazy-capabilities.js b/ui/app/macros/lazy-capabilities.js index 2169d61ef2..a20fb88ea1 100644 --- a/ui/app/macros/lazy-capabilities.js +++ b/ui/app/macros/lazy-capabilities.js @@ -4,7 +4,7 @@ // // export default DS.Model.extend({ // //pass the template string as the first arg, and be sure to use '' around the -// //paramerters that get interpolated in the string - that's how the template function +// //parameters that get interpolated in the string - that's how the template function // //knows where to put each value // zeroAddressPath: lazyCapabilities(apiPath`${'id'}/config/zeroaddress`, 'id'), // diff --git a/ui/app/models/transit-key.js b/ui/app/models/transit-key.js index 3f2e029a3d..c3797b5932 100644 --- a/ui/app/models/transit-key.js +++ b/ui/app/models/transit-key.js @@ -7,14 +7,38 @@ import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; const { attr } = DS; const ACTION_VALUES = { - encrypt: 'supportsEncryption', - decrypt: 'supportsDecryption', - datakey: 'supportsEncryption', - rewrap: 'supportsEncryption', - sign: 'supportsSigning', - hmac: true, - verify: true, - export: 'exportable', + encrypt: { + isSupported: 'supportsEncryption', + description: 'Looks up wrapping properties for the given token', + glyph: 'lock-closed', + }, + decrypt: { + isSupported: 'supportsDecryption', + description: 'Decrypts the provided ciphertext using this key', + glyph: 'envelope-unsealed--outline', + }, + datakey: { + isSupported: 'supportsEncryption', + description: 'Generates a new key and value encrypted with this key', + glyph: 'key', + }, + rewrap: { + isSupported: 'supportsEncryption', + description: 'Rewraps the ciphertext using the latest version of the named key', + glyph: 'refresh-default', + }, + sign: { + isSupported: 'supportsSigning', + description: 'Get the cryptographic signature of the given data', + glyph: 'edit', + }, + hmac: { isSupported: true, description: 'Generate a data digest using a hash algorithm', glyph: 'remix' }, + verify: { + isSupported: true, + description: 'Validate the provided signature for the given data', + glyph: 'check-circle-outline', + }, + export: { isSupported: 'exportable', description: 'Get the named key', glyph: 'exit' }, }; export default DS.Model.extend({ @@ -56,13 +80,15 @@ export default DS.Model.extend({ }, supportedActions: computed('type', function() { - return Object.keys(ACTION_VALUES).filter(name => { - const isSupported = ACTION_VALUES[name]; - if (typeof isSupported === 'boolean') { - return isSupported; - } - return get(this, isSupported); - }); + return Object.keys(ACTION_VALUES) + .filter(name => { + const { isSupported } = ACTION_VALUES[name]; + return typeof isSupported === 'boolean' || get(this, isSupported); + }) + .map(name => { + const { description, glyph } = ACTION_VALUES[name]; + return { name, description, glyph }; + }); }), canDelete: computed('deletionAllowed', 'lastLoadTS', function() { @@ -116,9 +142,7 @@ export default DS.Model.extend({ return types; }), - backend: attr('string', { - readOnly: true, - }), + backend: attr('string'), rotatePath: lazyCapabilities(apiPath`${'backend'}/keys/${'id'}/rotate`, 'backend', 'id'), canRotate: alias('rotatePath.canUpdate'), diff --git a/ui/app/styles/components/transit-card.scss b/ui/app/styles/components/transit-card.scss new file mode 100644 index 0000000000..1fb0cec046 --- /dev/null +++ b/ui/app/styles/components/transit-card.scss @@ -0,0 +1,41 @@ +.transit-card-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 0.2fr)); + grid-template-rows: 1fr; + align-content: start; + grid-gap: 2rem; + margin-top: $spacing-l; +} + +.transit-card { + border-radius: $radius; + box-shadow: 0 0 0 1px rgba($grey-dark, 0.3), $box-shadow-middle; + display: grid; + grid-template-columns: 0.45fr 2fr; + padding: $spacing-m; + + .transit-icon { + justify-self: center; + } + + .transit-action-description { + font-family: $family-sans; + font-size: $size-8; + color: $grey; + } + + .title { + color: $grey; + font-size: $size-7; + margin-bottom: $spacing-xxs; + } + + &:hover { + box-shadow: 0 0 0 1px $blue-500, $box-shadow-middle; + background: $blue-010; + + .title { + color: initial; + } + } +} diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss index 15bafbc0c5..9ae37b8119 100644 --- a/ui/app/styles/core.scss +++ b/ui/app/styles/core.scss @@ -88,6 +88,7 @@ @import './components/token-expire-warning'; @import './components/toolbar'; @import './components/tool-tip'; +@import './components/transit-card'; @import './components/unseal-warning'; @import './components/ui-wizard'; @import './components/vault-loading'; diff --git a/ui/app/templates/components/selectable-card.hbs b/ui/app/templates/components/selectable-card.hbs index bb69888640..6c55fe0285 100644 --- a/ui/app/templates/components/selectable-card.hbs +++ b/ui/app/templates/components/selectable-card.hbs @@ -1,7 +1,7 @@ {{!-- conditional to check if SelectableCard is apart of a CSS Grid, if yes return grid item class --}} {{#if gridContainer}}
-
+

{{format-number total}}

{{formattedCardTitle}}

{{subText}}

@@ -10,7 +10,7 @@
{{else}}
-
+

{{format-number total}}

{{formattedCardTitle}}

{{subText}}

diff --git a/ui/app/templates/components/transit-edit.hbs b/ui/app/templates/components/transit-edit.hbs index f4457a3cab..43d46153b4 100644 --- a/ui/app/templates/components/transit-edit.hbs +++ b/ui/app/templates/components/transit-edit.hbs @@ -20,5 +20,4 @@ - {{partial (concat 'partials/transit-form-' mode)}} diff --git a/ui/app/templates/components/transit-key-actions.hbs b/ui/app/templates/components/transit-key-actions.hbs index 3ab25a7221..c9833edcc9 100644 --- a/ui/app/templates/components/transit-key-actions.hbs +++ b/ui/app/templates/components/transit-key-actions.hbs @@ -9,7 +9,6 @@ data-test-transit-key-rotate="true" > Rotate encryption key - {{/if}} {{else}} diff --git a/ui/app/templates/partials/secret-list/item.hbs b/ui/app/templates/partials/secret-list/item.hbs index d34033c0b4..dd423a242a 100644 --- a/ui/app/templates/partials/secret-list/item.hbs +++ b/ui/app/templates/partials/secret-list/item.hbs @@ -1,3 +1,5 @@ + + {{#linked-block (concat "vault.cluster.secrets.backend." @@ -8,12 +10,14 @@ class="list-item-row" data-test-secret-link=item.id encode=true + queryParams=(secret-query-params backendModel.type) }}
{{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}} diff --git a/ui/app/templates/partials/transit-form-show.hbs b/ui/app/templates/partials/transit-form-show.hbs index 1cfda34819..70f5e535bd 100644 --- a/ui/app/templates/partials/transit-form-show.hbs +++ b/ui/app/templates/partials/transit-form-show.hbs @@ -1,12 +1,24 @@
- - - {{#if (eq tab 'versions')}} - - {{/if}} - {{#if (eq mode "show") }} - {{#if (or capabilities.canUpdate capabilities.canDelete)}} - - Edit encryption key - +{{#unless (eq tab 'actions')}} + + + {{#if (eq tab 'versions')}} + {{/if}} - - Key actions - - {{/if}} - - + {{#if (eq mode "show") }} + {{#if (or capabilities.canUpdate capabilities.canDelete)}} + + Edit encryption key + + {{/if}} + {{/if}} + + +{{/unless}} -{{#if (eq tab 'versions')}} + +{{#if (eq tab 'actions')}} +
+ {{#each model.supportedActions as |supportedAction|}} + {{#linked-block + "vault.cluster.secrets.backend.actions" + model.id + queryParams=(hash action=supportedAction.name) + class="transit-card" + data-test-transit-card=supportedAction.name + }} +
+ +
+
+

+ {{if (eq supportedAction.name 'export') 'Export Key' (humanize supportedAction.name)}} +

+

{{supportedAction.description}}

+
+ {{/linked-block}} + {{/each}} +
+{{else if (eq tab 'versions')}} {{#if (or (eq key.type "aes256-gcm96") (eq key.type "chacha20-poly1305") diff --git a/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs b/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs index c33bd2fd30..8cb8afa0a1 100644 --- a/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs +++ b/ui/app/templates/vault/cluster/secrets/backend/transit-actions-layout.hbs @@ -1,20 +1,5 @@
- {{#menu-sidebar title="Transit Actions" class="is-2"}} - {{#each model.supportedActions as |supportedAction|}} -
  • - {{#secret-link - mode="actions" - secret=model.id - class=(if (eq supportedAction selectedAction) "is-active") - queryParams=(query-params action=supportedAction) - data-test-transit-action-link=supportedAction - }} - {{capitalize supportedAction}} - {{/secret-link}} -
  • - {{/each}} - {{/menu-sidebar}} -
    +
    {{key-value-header @@ -27,21 +12,39 @@

    - {{model.id}} + {{#secret-link + class="is-inline has-text-info" + secret=model.id + mode="show" + replace=true + queryParams=(query-params tab='actions') + data-test-transit-link="actions" + }} + + {{/secret-link}} + Key Actions

    - - - - Details - - - +
    + +
    export default Component.extend({<%= contents %> }); diff --git a/ui/tests/acceptance/transit-test.js b/ui/tests/acceptance/transit-test.js index 774d51318e..5e53d6aa26 100644 --- a/ui/tests/acceptance/transit-test.js +++ b/ui/tests/acceptance/transit-test.js @@ -264,9 +264,21 @@ module('Acceptance | transit', function(hooks) { await click('[data-test-transit-key-actions-link]'); await settled(); assert.ok( - currentURL().startsWith(`/vault/secrets/${path}/actions/${name}`), - `${name}: navigates to tranist actions` + currentURL().startsWith(`/vault/secrets/${path}/show/${name}?tab=actions`), + `${name}: navigates to transit actions` ); + + const keyAction = key.supportsEncryption ? 'encrypt' : 'sign'; + const actionTitle = find(`[data-test-transit-action-title=${keyAction}]`).innerText.toLowerCase(); + + assert.equal( + actionTitle.includes(keyAction), + true, + `shows a card with title that links to the ${name} transit action` + ); + + await click(`[data-test-transit-card=${keyAction}]`); + await settled(); assert.ok( find('[data-test-transit-key-version-select]'), `${name}: the rotated key allows you to select versions` diff --git a/ui/tests/unit/models/transit-key-test.js b/ui/tests/unit/models/transit-key-test.js index 3917bb8ee4..a7559ca7fe 100644 --- a/ui/tests/unit/models/transit-key-test.js +++ b/ui/tests/unit/models/transit-key-test.js @@ -19,7 +19,7 @@ module('Unit | Model | transit key', function(hooks) { }) ); - let supportedActions = model.get('supportedActions'); + let supportedActions = model.get('supportedActions').map(k => k.name); assert.deepEqual(['encrypt', 'decrypt', 'datakey', 'rewrap', 'hmac', 'verify'], supportedActions); }); diff --git a/ui/yarn.lock b/ui/yarn.lock index 270f889081..674caed8d1 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1082,9 +1082,9 @@ "@glimmer/util" "^0.41.4" "@hashicorp/structure-icons@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@hashicorp/structure-icons/-/structure-icons-1.3.0.tgz#1c7c1cb43a1c1aa92b073a7aa7956495ae14c3e0" - integrity sha512-wTKpdaAPphEY2kg5QbQTSUlhqLTpBBR1+1dXp4LYTN0PtMSpetyDDDhcSyvKE8i4h2nwPJBRRfeFlE1snaHd7w== + version "1.8.1" + resolved "https://registry.yarnpkg.com/@hashicorp/structure-icons/-/structure-icons-1.8.1.tgz#d29945df2b41dcb317b141e51a26bd4be796a164" + integrity sha512-XFYdCIshmaR3Igc8eWpOZ2Gr3IR/0TogXZ4PQ9bz1E9cLzF3njBcs3tCpJUOwRwe/wMI5YTlL/sOGvcZ77AB/Q== "@icons/material@^0.2.4": version "0.2.4"