UI: HDS adoption replace <ConfirmAction> component (#21520)

* replace confirm-action dropdown with button+modal

* add modal frame to sidebar

* fix weird paragraph indent

* pass button text as arg

* add warning color to rotate modals

* update seal action and config ssh

* cleanup confirm action

* edit form

* add dropdown arg

* put back seal text

* put back confirm button text

* fix toolbar stylinggp

* popup member group

* move up title

* finish popup- components

* keymgmt

* fix modal button logic

* remaining app template components

* add period for angel

* vault cluster items

* add button text assertion

* remaining instances

* remove arg for passing confirm text

* contextual confirm action components

* delete old components

* update docs

* ammend dropdown loading states, add getter for confirm button color

* address feedback

* remove @disabled arg and add @disabledMessage

* add changelog;

* mfa tests

* update test selectors

* lol cleanup selectors

* start confirm action tests WIP

* move dropdown class directly to component

* add default color of isInDropdown

* final cleanup

* add tests

* remove @buttonColor as arg for dropdown

* update confirm action tests

* updae modals with disabled message

* refactor provider edit test
This commit is contained in:
claire bontempo 2023-11-17 17:44:21 -06:00 committed by GitHub
parent 24f5807da4
commit 4ac07e1d97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 1118 additions and 1543 deletions

3
changelog/21520.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui: Replace inline confirm alert inside a popup-menu dropdown with confirm alert modal
```

View File

@ -21,14 +21,10 @@
<div class="field is-grouped box is-fullwidth is-bottomless">
<ConfirmAction
@buttonClasses="button is-primary"
@buttonText="Seal"
@confirmTitle="Seal this cluster?"
@confirmMessage="You will not be able to read or write any data until the cluster is unsealed again."
@confirmButtonText="Seal"
@horizontalPosition="auto-left"
@onConfirmAction={{this.handleSeal}}
data-test-seal
>
Seal
</ConfirmAction>
/>
</div>

View File

@ -15,83 +15,78 @@
<Hds::SideNav::Header::IconButton @icon="user" @ariaLabel="User menu" />
</Dropdown.Trigger>
<Dropdown.Content>
<Confirm as |c|>
<div class="popup-menu-content" data-test-user-menu-content>
<div class="box">
<div class="menu-label">
{{capitalize this.auth.authData.displayName}}
</div>
<nav class="menu">
<ul class="menu-list">
{{#if this.auth.allowExpiration}}
<li class="token-alert is-flex" data-test-user-menu-item="token alert">
<span><Icon @name="alert-triangle-fill" class="has-text-highlight" /></span>
<span class="is-size-8 has-text-semibold">
We've stopped auto-renewing your token due to inactivity. It will expire on
{{date-format this.auth.tokenExpirationDate "MMMM do yyyy, h:mm:ss a"}}.
</span>
</li>
{{/if}}
{{#if this.hasEntityId}}
<li class="action">
<LinkTo @route="vault.cluster.mfa-setup" data-test-user-menu-item="mfa">
Multi-factor authentication
</LinkTo>
</li>
{{/if}}
{{#if this.isUserpass}}
<li class="action">
<LinkTo @route="vault.cluster.access.reset-password" data-test-user-menu-item="reset-password">
Reset password
</LinkTo>
</li>
{{/if}}
<li class="action" id="container">
{{! container is required in navbar collapsed state }}
<Hds::Copy::Button
@text="Copy token"
@textToCopy={{this.auth.currentToken}}
@isFullWidth={{true}}
@container="#container"
class="in-dropdown link is-flex-start"
{{on "click" (fn (set-flash-message "Token copied!"))}}
/>
<div class="popup-menu-content" data-test-user-menu-content>
<div class="box">
<div class="menu-label">
{{capitalize this.auth.authData.displayName}}
</div>
<nav class="menu">
<ul class="menu-list">
{{#if this.auth.allowExpiration}}
<li class="token-alert is-flex" data-test-user-menu-item="token alert">
<span><Icon @name="alert-triangle-fill" class="has-text-highlight" /></span>
<span class="is-size-8 has-text-semibold">
We've stopped auto-renewing your token due to inactivity. It will expire on
{{date-format this.auth.tokenExpirationDate "MMMM do yyyy, h:mm:ss a"}}.
</span>
</li>
{{#if (is-before (now interval=1000) this.auth.tokenExpirationDate)}}
{{#if this.auth.authData.renewable}}
<li class="action">
{{! TODO Hds::Button replacement skipped in favor of updating it when there is a Hds::Dropdown swapout }}
<button
type="button"
{{on "click" this.renewToken}}
class="link button {{if this.isRenewing 'is-loading'}}"
data-test-user-menu-item="renew token"
>
Renew token
</button>
</li>
{{/if}}
<li class="action">
<c.Message
@id={{get this.auth "authData.displayName"}}
@title={{concat "Revoke " (get this.auth "authData.displayName") "?"}}
@onConfirm={{action "revokeToken"}}
@message="You will not be able to log in again with this token."
@triggerText="Revoke token"
@confirmButtonText="Revoke"
data-test-user-menu-item="revoke token"
/>
</li>
{{/if}}
{{/if}}
{{#if this.hasEntityId}}
<li class="action">
<LinkTo @route="vault.cluster.logout" @model={{this.currentCluster.cluster.name}} id="logout">
Log out
<LinkTo @route="vault.cluster.mfa-setup" data-test-user-menu-item="mfa">
Multi-factor authentication
</LinkTo>
</li>
</ul>
</nav>
</div>
{{/if}}
{{#if this.isUserpass}}
<li class="action">
<LinkTo @route="vault.cluster.access.reset-password" data-test-user-menu-item="reset-password">
Reset password
</LinkTo>
</li>
{{/if}}
<li class="action" id="container">
{{! container is required in navbar collapsed state }}
<Hds::Copy::Button
@text="Copy token"
@textToCopy={{this.auth.currentToken}}
@isFullWidth={{true}}
@container="#container"
class="in-dropdown link is-flex-start"
{{on "click" (fn (set-flash-message "Token copied!"))}}
/>
</li>
{{#if (is-before (now interval=1000) this.auth.tokenExpirationDate)}}
{{#if this.auth.authData.renewable}}
<li class="action">
{{! TODO Hds::Button replacement skipped in favor of updating it when there is a Hds::Dropdown swapout }}
<button
type="button"
{{on "click" this.renewToken}}
class="link button {{if this.isRenewing 'is-loading'}}"
data-test-user-menu-item="renew token"
>
Renew token
</button>
</li>
{{/if}}
<ConfirmAction
@isInDropdown={{true}}
@confirmTitle="Revoke {{get this.auth 'authData.displayName'}}?"
@onConfirmAction={{action "revokeToken"}}
@buttonText="Revoke token"
@confirmMessage="You will not be able to log in again with this token."
data-test-user-menu-item="revoke token"
/>
{{/if}}
<li class="action">
<LinkTo @route="vault.cluster.logout" @model={{this.currentCluster.cluster.name}} id="logout">
Log out
</LinkTo>
</li>
</ul>
</nav>
</div>
</Confirm>
</div>
</Dropdown.Content>
</BasicDropdown>

View File

@ -1,139 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
.confirm-wrapper {
position: relative;
overflow: hidden;
border-radius: $radius;
box-shadow: $box-shadow, $box-shadow-middle;
}
.confirm {
transition: transform $speed;
padding-top: 2px;
}
.show-confirm {
transform: translateX(-100%);
transition: transform $speed;
}
.confirm.show-confirm {
visibility: hidden;
}
.confirm-overlay {
position: absolute;
background-color: white;
top: 0;
left: 100%;
width: 100%;
}
.confirm,
.confirm-overlay {
button.link,
a {
background-color: $white;
color: $grey-darkest;
&:hover {
background-color: $ui-gray-050;
color: $ui-gray-900;
}
&.is-active {
background-color: $blue-500;
color: $blue;
}
&.is-destroy {
color: $red;
&:hover {
background-color: $red;
color: $white;
}
}
&.disabled {
opacity: 0.5;
&:hover {
background: transparent;
cursor: default;
}
}
}
}
.confirm-action span .button {
display: block;
margin: 0.25rem auto;
width: 95%;
}
.confirm-action > span {
@include from($mobile) {
align-items: center;
display: flex;
}
* {
margin-left: $spacing-12;
}
.confirm-action-text:not(.is-block) {
text-align: right;
@include until($mobile) {
display: block;
margin-bottom: $spacing-12;
text-align: left;
}
}
.confirm-action-text.is-block {
text-align: left;
}
}
.confirm-action-message {
margin: 0;
.message {
border: 0;
font-size: $size-8;
line-height: 1.33;
margin: 0;
}
.message-title {
font-size: 1rem;
}
.hs-icon {
color: $yellow;
}
p {
font-weight: $font-weight-semibold;
margin-left: $spacing-24;
padding-left: $spacing-4;
padding-top: 0;
}
.confirm-action-options {
border-top: $light-border;
display: flex;
padding: $spacing-4;
.link {
flex: 1;
text-align: center;
width: auto;
padding: $spacing-8;
}
}
}

View File

@ -48,6 +48,19 @@
width: 100%;
}
// TODO HDS polish - temp styling fix for ConfirmAction dropdown buttons
// so they match other dropdown elements until we replace popup-menu with Hds::Dropdown
.hds-confirm-action-critical {
&:hover {
background-color: $ui-gray-050;
}
div {
margin-left: -$spacing-4;
font-size: $size-7;
font-weight: $font-weight-semibold;
}
}
button.link,
.ember-power-select-option,
.ember-power-select-option[aria-current='true'],

View File

@ -61,7 +61,6 @@
@import './components/calendar-widget';
@import './components/cluster-banners';
@import './components/codemirror';
@import './components/confirm';
@import './components/console-ui-panel';
@import './components/control-group';
@import './components/doc-link';

View File

@ -22,18 +22,15 @@
</div>
</div>
<div class="field is-grouped-split box is-fullwidth is-bottomless">
<div class="control">
<Hds::ButtonSet>
<Hds::Copy::Button @text="Copy" @textToCopy={{@model.publicKey}} class="primary" />
</div>
<div class="control">
<ConfirmAction
@buttonClasses="button"
@buttonText="Delete"
@buttonColor="secondary"
@confirmMessage="This will remove the CA certificate information."
@onConfirmAction={{this.delete}}
>
Delete
</ConfirmAction>
</div>
/>
</Hds::ButtonSet>
</div>
{{else}}
<form {{on "submit" this.saveConfig}} data-test-ssh-configure-form="true">

View File

@ -35,15 +35,14 @@
{{/if}}
{{#if @model.canReset}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Reset connection"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{action "reset"}}
@confirmTitle="Reset connection?"
@confirmMessage="This will close the connection and its underlying plugin and restart it with the configuration stored in the barrier."
@confirmButtonText="Reset"
data-test-database-connection-reset
>
Reset connection
</ConfirmAction>
/>
{{/if}}
{{#if (or @model.canReset @model.canDelete)}}
<div class="toolbar-separator"></div>
@ -51,15 +50,15 @@
{{#if @model.canRotateRoot}}
{{! template-lint-disable quotes }}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Rotate root credentials"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.rotate}}
@confirmTitle="Rotate credentials?"
@confirmMessage='This will rotate the "root" user credentials stored for the database connection. The password will not be accessible once rotated.'
@confirmButtonText="Rotate"
@modalColor="warning"
data-test-database-connection-rotate
>
Rotate root credentials
</ConfirmAction>
/>
{{! template-lint-enable }}
{{/if}}
{{#if @model.canAddRole}}

View File

@ -25,15 +25,14 @@
<ToolbarActions>
{{#if @model.canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Delete role"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{action "delete"}}
@confirmTitle="Delete role?"
@confirmMessage="This role will be permanently deleted. You will need to recreate it to use it again."
@confirmButtonText="Delete"
data-test-database-role-delete
>
Delete role
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if (and @model.canRotateRoleCredentials (eq @model.type "static"))}}

View File

@ -75,7 +75,7 @@
<Item.content>
<Icon @name="folder" class="has-text-grey-light" />{{list.item.id}}
</Item.content>
<Item.menu as |Menu|>
<Item.menu>
<li class="action">
<LinkTo @route="vault.cluster.access.method.item.show" @model={{list.item.id}} class="is-block">
View
@ -88,25 +88,22 @@
{{singularize this.itemType}}
</LinkTo>
</li>
<li>
<Menu.Message
@id={{list.item.id}}
@buttonClasses="link is-destroy"
@onConfirm={{action
(perform
Item.callMethod
"destroyRecord"
list.item
(concat "Successfully deleted " (singularize this.itemType) " " list.item.id ".")
(concat "There was an error deleting this " (singularize this.itemType))
(action "refreshItemList")
)
}}
@message={{concat "Are you sure you want to delete " (singularize this.itemType) " " list.item.id "?"}}
data-test-secret-delete="true"
@triggerText={{concat "Delete " (singularize this.itemType)}}
/>
</li>
<ConfirmAction
@isInDropdown={{true}}
@onConfirmAction={{action
(perform
Item.callMethod
"destroyRecord"
list.item
(concat "Successfully deleted " (singularize this.itemType) " " list.item.id ".")
(concat "There was an error deleting this " (singularize this.itemType))
(action "refreshItemList")
)
}}
@confirmTitle="Delete {{singularize this.itemType}}?"
@confirmMessage="Are you sure you want to delete {{singularize this.itemType}} {{list.item.id}}?"
@buttonText="Delete {{singularize this.itemType}}"
/>
</Item.menu>
</ListItem>
{{/if}}

View File

@ -38,15 +38,12 @@
<Toolbar>
<ToolbarActions>
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Delete {{this.itemType}}"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{action "deleteItem"}}
@confirmMessage={{concat "Are you sure you want to delete " this.itemType " " this.model.id "?"}}
@cancelButtonText="Cancel"
data-test-secret-delete="true"
>
Delete
{{this.itemType}}
</ConfirmAction>
@confirmMessage="Are you sure you want to delete {{this.itemType}} {{this.model.id}}?"
/>
<div class="toolbar-separator"></div>
<ToolbarLink
@route="vault.cluster.access.method.item.edit"

View File

@ -7,13 +7,13 @@
<Toolbar>
<ToolbarActions>
<ConfirmAction
@buttonClasses="toolbar-link"
class="toolbar-button"
@buttonColor="secondary"
@buttonText="Delete {{this.model.identityType}}"
@confirmTitle="Delete this {{this.model.identityType}}?"
@onConfirmAction={{action "deleteItem" this.model}}
data-test-entity-item-delete="true"
>
Delete
{{this.model.identityType}}
</ConfirmAction>
/>
</ToolbarActions>
</Toolbar>
{{/if}}

View File

@ -4,41 +4,42 @@
~}}
<PopupMenu @name="alias-menu">
<Confirm as |c|>
{{#let (get this.params "0") as |item|}}
<nav class="menu">
<ul class="menu-list">
{{#let (get this.params "0") as |item|}}
<nav class="menu">
<ul class="menu-list">
<li class="action">
<LinkTo
@route="vault.cluster.access.identity.aliases.show"
@models={{array (pluralize item.parentType) item.id "details"}}
>
Details
</LinkTo>
</li>
{{#if item.updatePath.isPending}}
<li class="action">
<LinkTo
@route="vault.cluster.access.identity.aliases.show"
@models={{array (pluralize item.parentType) item.id "details"}}
>
Details
</LinkTo>
<LoadingDropdownOption />
</li>
{{#if item.updatePath.isPending}}
{{else}}
{{#if item.canEdit}}
<li class="action">
<LoadingDropdownOption />
<LinkTo
@route="vault.cluster.access.identity.aliases.edit"
@models={{array (pluralize item.parentType) item.id}}
>
Edit
</LinkTo>
</li>
{{else}}
{{#if item.canEdit}}
<li class="action">
<LinkTo
@route="vault.cluster.access.identity.aliases.edit"
@models={{array (pluralize item.parentType) item.id}}
>
Edit
</LinkTo>
</li>
{{/if}}
{{#if item.canDelete}}
<li class="action">
<c.Message @id={{item.id}} @onConfirm={{action "performTransaction" item}} data-test-item-delete />
</li>
{{/if}}
{{/if}}
</ul>
</nav>
{{/let}}
</Confirm>
{{#if item.canDelete}}
<ConfirmAction
@buttonText="Delete"
@isInDropdown={{true}}
@onConfirmAction={{action "performTransaction" item}}
data-test-item-delete
/>
{{/if}}
{{/if}}
</ul>
</nav>
{{/let}}
</PopupMenu>

View File

@ -8,13 +8,12 @@
<ul class="menu-list">
<li class="action">
<ConfirmAction
@buttonClasses="link is-destroy"
@confirmButtonText="Remove this group?"
@buttonText="Remove"
@confirmTitle="Remove this group?"
@isInDropdown={{true}}
@confirmMessage="This may affect permissions for this group."
@onConfirmAction={{action "performTransaction" this.model this.groupArray this.memberId}}
>
Remove
</ConfirmAction>
/>
</li>
</ul>
</nav>

View File

@ -8,14 +8,12 @@
<ul class="menu-list">
<li class="action">
<ConfirmAction
@buttonClasses="link is-destroy"
@buttonText="Remove"
@confirmTitle="Remove metadata?"
@isInDropdown={{true}}
@confirmMessage="This data may be used outside of Vault."
@confirmButtonText="Remove"
@onConfirmAction={{action "performTransaction" this.model this.key}}
>
Remove
</ConfirmAction>
/>
</li>
</ul>
</nav>

View File

@ -18,14 +18,12 @@
</li>
<li class="action">
<ConfirmAction
@buttonClasses="link is-destroy"
@confirmButtonText="Remove"
@buttonText="Remove from {{this.model.identityType}}"
@confirmTitle="Remove this policy?"
@isInDropdown={{true}}
@confirmMessage="This policy may affect permissions to access Vault data."
@onConfirmAction={{action "performTransaction" this.model this.policyName}}
>
Remove from
{{this.model.identityType}}
</ConfirmAction>
/>
</li>
</ul>
</nav>

View File

@ -75,31 +75,31 @@
{{/if}}
{{#if @model.provider}}
<ConfirmAction
@buttonClasses="toolbar-link"
class="toolbar-button"
@buttonText="Remove key"
@buttonColor="secondary"
@onConfirmAction={{perform this.removeKey}}
@confirmTitle="Remove this key?"
@confirmMessage="This will remove all versions of the key from the KMS provider. The key will stay in Vault."
@confirmButtonText="Remove"
@isRunning={{this.removeKey.isRunning}}
data-test-keymgmt-key-remove
>
Remove key
</ConfirmAction>
/>
{{/if}}
{{#if (or @model.canDelete @model.provider)}}
<div class="toolbar-separator"></div>
{{/if}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Rotate key"
class="toolbar-button"
@buttonColor="secondary"
@modalColor="warning"
@onConfirmAction={{perform this.rotateKey}}
@confirmTitle="Rotate this key?"
@confirmMessage="After rotation, all key actions will default to using the newest version of the key."
@confirmButtonText="Rotate"
@color="warning"
@isRunning={{this.rotateKey.isRunning}}
data-test-keymgmt-key-rotate
>
Rotate key
</ConfirmAction>
/>
{{#if @model.canEdit}}
<ToolbarSecretLink
@secret={{@model.id}}

View File

@ -47,27 +47,22 @@
<Toolbar data-test-kms-provider-details-actions>
<ToolbarActions>
{{#if @model.canDelete}}
<ToolTip @verticalPosition="above" @horizontalPosition="center" as |T|>
<T.Trigger data-test-tooltip-trigger>
<ConfirmAction
@buttonClasses="toolbar-link"
@onConfirmAction={{this.onDelete}}
@disabled={{@model.keys.length}}
data-test-kms-provider-delete={{true}}
>
Delete provider
</ConfirmAction>
</T.Trigger>
{{#if @model.keys.length}}
<T.Content class="tool-tip">
<div class="box" data-test-kms-provider-delete-tooltip>
This provider cannot be deleted until all
{{@model.keys.length}}
key(s) distributed to it are revoked. This can be done from the Keys tab.
</div>
</T.Content>
{{/if}}
</ToolTip>
<ConfirmAction
@buttonText="Delete provider"
class="toolbar-button"
@buttonColor="secondary"
@confirmTitle="Delete this provider?"
@onConfirmAction={{this.onDelete}}
@disabledMessage={{if
@model.keys.length
(concat
"This provider cannot be deleted until all "
@model.keys.length
" key(s) distributed to it are revoked. This can be done from the Keys tab."
)
}}
data-test-kms-provider-delete
/>
{{/if}}
{{#if (and @model.canDelete (or @model.canListKeys @model.canEdit))}}
<div class="toolbar-separator"></div>

View File

@ -78,6 +78,7 @@
{{/each}}
<div class="is-flex-row {{if this.targets 'has-top-padding-s has-border-top-light'}}">
<Select
class="is-marginless"
@options={{this.targetTypes}}
@labelAttribute="label"
@valueAttribute="type"
@ -128,7 +129,7 @@
/>
</div>
{{#if this.errors.targets.errors}}
<AlertInline @type="danger" @message={{join ", " this.errors.targets.errors}} />
<AlertInline class="has-top-padding-s" @type="danger" @message={{join ", " this.errors.targets.errors}} />
{{/if}}
</div>
{{#unless @isInline}}

View File

@ -81,22 +81,17 @@
</td>
<td class="middle no-padding has-text-right">
<PopupMenu>
<Confirm as |c|>
<nav aria-label="remove peer">
<ul>
<li class="action">
<c.Message
@id={{server.nodeId}}
@onConfirm={{action "removePeer" server}}
@triggerText="Remove Peer"
@confirmButtonText="Remove"
@title={{concat "Remove " server.nodeId "?"}}
@message="This will remove the server from the raft cluster."
/>
</li>
</ul>
</nav>
</Confirm>
<nav aria-label="remove peer">
<ul>
<ConfirmAction
@isInDropdown={{true}}
@onConfirmAction={{action "removePeer" server}}
@buttonText="Remove Peer"
@confirmTitle="Remove {{server.nodeId}}?"
@confirmMessage="This will remove the server from the raft cluster."
/>
</ul>
</nav>
</PopupMenu>
</td>
</tr>

View File

@ -32,9 +32,12 @@
<Toolbar>
<ToolbarActions>
{{#if this.model.canDelete}}
<ConfirmAction @buttonClasses="toolbar-link" @onConfirmAction={{action "delete"}}>
Delete role
</ConfirmAction>
<ConfirmAction
@buttonText="Delete role"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{action "delete"}}
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.model.canGenerate}}

View File

@ -31,9 +31,12 @@
<Toolbar>
<ToolbarActions>
{{#if this.model.canDelete}}
<ConfirmAction @buttonClasses="toolbar-link" @onConfirmAction={{action "delete"}}>
Delete role
</ConfirmAction>
<ConfirmAction
@buttonText="Delete role"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{action "delete"}}
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if (eq this.model.keyType "otp")}}

View File

@ -20,14 +20,14 @@
<ToolbarActions>
{{#if (and (eq @mode "show") @model.canDelete)}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Delete"
class="toolbar-button"
@buttonColor="secondary"
@confirmTitle="Delete secret?"
@confirmMessage="You will not be able to recover this secret data later."
@onConfirmAction={{this.handleDelete}}
data-test-secret-v1-delete="true"
>
Delete
</ConfirmAction>
data-test-secret-v1-delete
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if (and (eq @mode "show") @canUpdateSecret)}}

View File

@ -24,55 +24,56 @@
</div>
<div class="column has-text-right">
<PopupMenu @name="role-aws-nav" @contentClass="is-wide">
<Confirm as |c|>
<nav class="menu">
<ul class="menu-list">
{{#if @item.generatePath.isPending}}
<nav class="menu">
<ul class="menu-list">
{{#if @item.generatePath.isPending}}
<li class="action">
<LoadingDropdownOption />
</li>
{{else if @item.canGenerate}}
<li class="action">
<LinkTo
@route="vault.cluster.secrets.backend.credentials"
@model={{@item.id}}
data-test-role-aws-link="generate"
>
Generate credentials
</LinkTo>
</li>
{{/if}}
{{#if @item.updatePath.isPending}}
<li class="action">
<LoadingDropdownOption />
</li>
<li class="action">
<LoadingDropdownOption />
</li>
{{else}}
{{#if @item.canRead}}
<li class="action">
<LoadingDropdownOption />
</li>
{{else if @item.canGenerate}}
<li class="action">
<LinkTo
@route="vault.cluster.secrets.backend.credentials"
@model={{@item.id}}
data-test-role-aws-link="generate"
>
Generate credentials
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.id}} data-test-role-ssh-link="show">
Details
</LinkTo>
</li>
{{/if}}
{{#if @item.updatePath.isPending}}
{{#if @item.canEdit}}
<li class="action">
<LoadingDropdownOption />
<LinkTo @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} data-test-role-ssh-link="edit">
Edit
</LinkTo>
</li>
<li class="action">
<LoadingDropdownOption />
</li>
{{else}}
{{#if @item.canRead}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.id}} data-test-role-ssh-link="show">
Details
</LinkTo>
</li>
{{/if}}
{{#if @item.canEdit}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} data-test-role-ssh-link="edit">
Edit
</LinkTo>
</li>
{{/if}}
{{#if @item.canDelete}}
<li class="action">
<c.Message @id={{@item.id}} @onConfirm={{@delete}} data-test-aws-role-delete={{@item.id}} />
</li>
{{/if}}
{{/if}}
</ul>
</nav>
</Confirm>
{{#if @item.canDelete}}
<ConfirmAction
@buttonText="Delete"
@isInDropdown={{true}}
@onConfirmAction={{@delete}}
data-test-aws-role-delete={{@item.id}}
/>
{{/if}}
{{/if}}
</ul>
</nav>
</PopupMenu>
</div>
</div>

View File

@ -31,58 +31,54 @@
</div>
<div class="column has-text-right">
<PopupMenu name="secret-menu">
<Confirm as |c|>
<nav class="menu">
<ul class="menu-list">
{{#if @item.isFolder}}
<SecretLink @mode="list" @secret={{@item.id}} class="has-text-black has-text-weight-semibold">
Contents
</SecretLink>
<nav class="menu">
<ul class="menu-list">
{{#if @item.isFolder}}
<SecretLink @mode="list" @secret={{@item.id}} class="has-text-black has-text-weight-semibold">
Contents
</SecretLink>
{{else}}
{{#if (or @item.versionPath.isLoading @item.secretPath.isLoading)}}
<li class="action">
<LoadingDropdownOption />
</li>
{{else}}
{{#if (or @item.versionPath.isLoading @item.secretPath.isLoading)}}
{{#if @item.canRead}}
<li class="action">
<LoadingDropdownOption />
<SecretLink
@mode="show"
@secret={{@item.id}}
@queryParams={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
class="has-text-black has-text-weight-semibold"
>
Details
</SecretLink>
</li>
{{else}}
{{#if @item.canRead}}
<li class="action">
<SecretLink
@mode="show"
@secret={{@item.id}}
@queryParams={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
class="has-text-black has-text-weight-semibold"
>
Details
</SecretLink>
</li>
{{/if}}
{{#if @item.canEdit}}
<li class="action">
<SecretLink
@mode="edit"
@secret={{@item.id}}
@queryParams={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
class="has-text-black has-text-weight-semibold"
>
Edit
</SecretLink>
</li>
{{/if}}
{{#if @item.canDelete}}
<li class="action">
<c.Message
@id={{@item.id}}
@triggerText="Delete"
@message="This will permanently delete this secret."
@onConfirm={{@delete}}
/>
</li>
{{/if}}
{{/if}}
{{#if @item.canEdit}}
<li class="action">
<SecretLink
@mode="edit"
@secret={{@item.id}}
@queryParams={{secret-query-params @backendModel.type @item.type asQueryParams=true}}
class="has-text-black has-text-weight-semibold"
>
Edit
</SecretLink>
</li>
{{/if}}
{{#if @item.canDelete}}
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Delete"
@confirmMessage="This will permanently delete this secret."
@onConfirmAction={{@delete}}
/>
{{/if}}
{{/if}}
</ul>
</nav>
</Confirm>
{{/if}}
</ul>
</nav>
</PopupMenu>
</div>
</div>

View File

@ -37,85 +37,86 @@
<div class="column has-text-right">
{{#if (eq @backendType "ssh")}}
<PopupMenu @name="role-ssh-nav">
<Confirm as |c|>
<nav class="menu">
<ul class="menu-list">
{{#if (eq @item.keyType "otp")}}
{{#if @item.generatePath.isPending}}
<nav class="menu">
<ul class="menu-list">
{{#if (eq @item.keyType "otp")}}
{{#if @item.generatePath.isPending}}
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
</li>
{{else if @item.canGenerate}}
<li class="action">
<LinkTo
@route="vault.cluster.secrets.backend.credentials"
@model={{@item.id}}
data-test-role-ssh-link="generate"
>
Generate Credentials
</LinkTo>
</li>
{{/if}}
{{else if (eq @item.keyType "ca")}}
{{#if @item.signPath.isPending}}
<li class="action">
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
</li>
{{else if @item.canGenerate}}
<li class="action">
<LinkTo
@route="vault.cluster.secrets.backend.credentials"
@model={{@item.id}}
data-test-role-ssh-link="generate"
>
Generate Credentials
</LinkTo>
</li>
{{/if}}
{{else if (eq @item.keyType "ca")}}
{{#if @item.signPath.isPending}}
<li class="action">
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
</li>
</li>
{{else if @item.canGenerate}}
<li class="action">
<LinkTo
@route="vault.cluster.secrets.backend.sign"
@model={{@item.id}}
data-test-role-ssh-link="generate"
>
Sign Keys
</LinkTo>
</li>
{{/if}}
{{/if}}
{{#if @item.canEditZeroAddress}}
</li>
{{else if @item.canGenerate}}
<li class="action">
<Hds::Button
disabled={{@loadingToggleZeroAddress}}
class="link"
@icon={{if @loadingToggleZeroAddress "loading"}}
@isIconOnly={{@loadingToggleZeroAddress}}
{{on "click" @toggleZeroAddress}}
@text={{if @item.zeroAddress "Disable Zero Address" "Enable Zero Address"}}
/>
<LinkTo
@route="vault.cluster.secrets.backend.sign"
@model={{@item.id}}
data-test-role-ssh-link="generate"
>
Sign Keys
</LinkTo>
</li>
{{/if}}
{{#if @item.updatePath.isPending}}
{{/if}}
{{#if @item.canEditZeroAddress}}
<li class="action">
<Hds::Button
disabled={{@loadingToggleZeroAddress}}
class="link"
@icon={{if @loadingToggleZeroAddress "loading"}}
@isIconOnly={{@loadingToggleZeroAddress}}
{{on "click" @toggleZeroAddress}}
@text={{if @item.zeroAddress "Disable Zero Address" "Enable Zero Address"}}
/>
</li>
{{/if}}
{{#if @item.updatePath.isPending}}
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
</li>
{{else}}
{{#if @item.canRead}}
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.id}} data-test-role-ssh-link="show">
Details
</LinkTo>
</li>
{{else}}
{{#if @item.canRead}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.id}} data-test-role-ssh-link="show">
Details
</LinkTo>
</li>
{{/if}}
{{#if @item.canEdit}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} data-test-role-ssh-link="edit">
Edit
</LinkTo>
</li>
{{/if}}
{{#if @item.canDelete}}
<li class="action">
<c.Message @id={{@item.id}} @onConfirm={{@delete}} data-test-ssh-role-delete />
</li>
{{/if}}
{{/if}}
</ul>
</nav>
</Confirm>
{{#if @item.canEdit}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} data-test-role-ssh-link="edit">
Edit
</LinkTo>
</li>
{{/if}}
{{#if @item.canDelete}}
<ConfirmAction
@buttonText="Delete"
@isInDropdown={{true}}
@onConfirmAction={{@delete}}
data-test-ssh-role-delete
/>
{{/if}}
{{/if}}
</ul>
</nav>
</PopupMenu>
{{/if}}
</div>

View File

@ -32,15 +32,13 @@
<ToolbarActions>
{{#if this.capabilities.canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Delete role"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{action "delete"}}
@confirmTitle="Are you sure?"
@confirmMessage="Deleting this role means that youll need to recreate it and reassign any existing transformations to use it again."
@confirmButtonText="Delete"
data-test-transformation-role-delete
>
Delete role
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.capabilities.canUpdate}}

View File

@ -104,9 +104,7 @@
</div>
</div>
{{#if (and @key.canDelete @capabilities.canDelete)}}
<ConfirmAction @buttonClasses="button" @onConfirmAction={{@deleteKey}}>
Delete transit key
</ConfirmAction>
<ConfirmAction @buttonText="Delete transit key" @onConfirmAction={{@deleteKey}} />
{{/if}}
</div>
</form>

View File

@ -6,15 +6,15 @@
{{#if (eq this.selectedAction "rotate")}}
{{#if this.key.canRotate}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Rotate encryption key"
class="toolbar-button"
@buttonColor="secondary"
@confirmTitle="Rotate this key?"
@confirmMessage="After rotation, all key actions will default to using the newest version of the key."
@confirmButtonText="Rotate"
@modalColor="warning"
@onConfirmAction={{action "doSubmit"}}
data-test-transit-key-rotate
>
Rotate encryption key
</ConfirmAction>
/>
{{/if}}
{{else}}
<MessageError @errors={{this.errors}} />

View File

@ -33,63 +33,66 @@
</div>
<div class="column has-text-right">
<PopupMenu @name="identity-item" @onOpen={{action "reloadRecord" item}}>
<Confirm as |c|>
<nav class="menu">
<ul class="menu-list">
<nav class="menu">
<ul class="menu-list">
<li class="action">
<LinkTo @route="vault.cluster.access.identity.show" @models={{array item.id "details"}}>
Details
</LinkTo>
</li>
{{#if (or item.isReloading item.updatePath.isPending item.aliasPath.isPending)}}
<li class="action">
<LinkTo @route="vault.cluster.access.identity.show" @models={{array item.id "details"}}>
Details
</LinkTo>
<LoadingDropdownOption />
</li>
{{#if (or item.isReloading item.updatePath.isPending item.aliasPath.isPending)}}
{{else}}
{{#if item.canAddAlias}}
<li class="action">
<LoadingDropdownOption />
<LinkTo
@route="vault.cluster.access.identity.aliases.add"
@models={{array (pluralize this.identityType) item.id}}
>
Create alias
</LinkTo>
</li>
{{else}}
{{#if item.canAddAlias}}
<li class="action">
<LinkTo
@route="vault.cluster.access.identity.aliases.add"
@models={{array (pluralize this.identityType) item.id}}
>
Create alias
</LinkTo>
</li>
{{/if}}
{{#if item.canEdit}}
<li class="action">
<LinkTo @route="vault.cluster.access.identity.edit" @model={{item.id}}>
Edit
</LinkTo>
</li>
<li class="action">
{{#if item.disabled}}
{{! TODO Hds::Button replacement skipped in favor of updating it when there is a Hds::Dropdown swapout }}
<button type="button" {{action "toggleDisabled" item}} class="link">
Enable
</button>
{{else if (eq this.identityType "entity")}}
<c.Message
@id="{{item.id}}-disable"
@triggerText="Disable"
@message="Associated tokens will not be revoked, but cannot be used"
@title="Disable this?"
@confirmButtonText="Disable"
@onConfirm={{action "toggleDisabled" item}}
data-test-engine-disable="true"
/>
{{/if}}
</li>
{{/if}}
{{#if item.canDelete}}
<li class="action">
<c.Message @id={{item.id}} @onConfirm={{action "delete" item}} data-test-item-delete="true" />
</li>
{{/if}}
{{/if}}
</ul>
</nav>
</Confirm>
{{#if item.canEdit}}
<li class="action">
<LinkTo @route="vault.cluster.access.identity.edit" @model={{item.id}}>
Edit
</LinkTo>
</li>
<li class="action">
{{#if item.disabled}}
<Hds::Button
@text="Enable"
type="button"
{{on "click" (action "toggleDisabled" item)}}
class="link"
/>
{{else if (eq this.identityType "entity")}}
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Disable"
@confirmMessage="Associated tokens will not be revoked, but cannot be used"
@confirmTitle="Disable this entity?"
@onConfirmAction={{action "toggleDisabled" item}}
@modalColor="warning"
/>
{{/if}}
</li>
{{/if}}
{{#if item.canDelete}}
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Delete"
@onConfirmAction={{action "delete" item}}
@confirmTitle="Delete this {{this.identityType}}?"
data-test-item-delete
/>
{{/if}}
{{/if}}
</ul>
</nav>
</PopupMenu>
</div>
</div>

View File

@ -57,28 +57,26 @@
<div class="control">
{{#if (and this.capabilities.forceRevokePrefix.canUpdate (not this.confirmingRevoke))}}
<ConfirmAction
@buttonClasses="toolbar-link"
@confirmTitle="Disable this?"
@confirmMessage={{concat "All leases under this one will also be removed and disregard any errors encountered."}}
@confirmButtonText="Revoke"
@buttonText="Force revoke prefix"
class="toolbar-button"
@buttonColor="secondary"
@confirmTitle="Revoke this?"
@confirmMessage="All leases under this one will also be removed and any errors encountered will be disregarded."
@onConfirmAction={{action "revokePrefix" this.baseKey.id true}}
>
Force revoke prefix
</ConfirmAction>
/>
{{/if}}
</div>
<div class="control">
{{#if (and this.capabilities.revokePrefix.canUpdate (not this.confirmingForceRevoke))}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Revoke prefix"
class="toolbar-button"
@buttonColor="secondary"
@confirmTitle="Revoke this?"
@confirmMessage={{concat "All leases under this one will also be removed"}}
@confirmButtonText="Revoke"
@confirmMessage="All leases under this one will also be removed."
@onConfirmAction={{action "revokePrefix" this.baseKey.id}}
data-test-lease-revoke-prefix="true"
>
Revoke prefix
</ConfirmAction>
data-test-lease-revoke-prefix
/>
{{/if}}
</div>
{{/if}}

View File

@ -26,15 +26,14 @@
<Toolbar>
<ToolbarActions>
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Revoke lease"
class="toolbar-button"
@buttonColor="secondary"
@confirmTitle="Revoke this?"
@confirmMessage={{concat "All leases under this one will also be removed"}}
@confirmButtonText="Confirm"
@confirmMessage="All leases under this one will also be removed."
@onConfirmAction={{action "revokeLease" this.model}}
data-test-lease-revoke="true"
>
Revoke lease
</ConfirmAction>
data-test-lease-revoke
/>
</ToolbarActions>
</Toolbar>
{{/if}}

View File

@ -84,36 +84,32 @@
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu @name="auth-backend-nav">
<Confirm as |c|>
<nav class="menu">
<ul class="menu-list">
<nav class="menu">
<ul class="menu-list">
<li>
<LinkTo @route="vault.cluster.access.method.section" @models={{array method.id "configuration"}}>
View configuration
</LinkTo>
</li>
{{#if method.canEdit}}
<li>
<LinkTo @route="vault.cluster.access.method.section" @models={{array method.id "configuration"}}>
View configuration
<LinkTo @route="vault.cluster.settings.auth.configure" @model={{method.id}}>
Edit configuration
</LinkTo>
</li>
{{#if method.canEdit}}
<li>
<LinkTo @route="vault.cluster.settings.auth.configure" @model={{method.id}}>
Edit configuration
</LinkTo>
</li>
{{/if}}
{{/if}}
{{#if (and (not-eq method.methodType "token") method.canDisable)}}
<li class="action">
<c.Message
@id={{method.id}}
@title="Disable method?"
@message="This may affect access to Vault data."
@triggerText="Disable"
@onConfirm={{perform this.disableMethod method}}
/>
</li>
{{/if}}
</ul>
</nav>
</Confirm>
{{#if (and (not-eq method.methodType "token") method.canDisable)}}
<ConfirmAction
@isInDropdown={{true}}
@confirmTitle="Disable method?"
@confirmMessage="This may affect access to Vault data."
@buttonText="Disable"
@onConfirmAction={{perform this.disableMethod method}}
/>
{{/if}}
</ul>
</nav>
</PopupMenu>
</div>
</div>

View File

@ -45,15 +45,17 @@
<Toolbar>
<ToolbarActions>
<ConfirmAction
@buttonClasses="toolbar-link"
@disabled={{not (is-empty this.model.enforcements)}}
data-test-delete-mfa-config
class="toolbar-button"
@disabledMessage={{unless
(is-empty this.model.enforcements)
"This method cannot be deleted until its enforcements are deleted. This can be done from the 'Enforcements' tab."
}}
@buttonColor="secondary"
@onConfirmAction={{this.deleteMethod}}
@confirmTitle="Are you sure?"
@confirmMessage="Deleting this MFA configuration is permanent, and it will no longer be available."
@confirmButtonText="Delete"
>
Delete
</ConfirmAction>
@buttonText="Delete"
/>
<ToolbarLink
@route="vault.cluster.access.mfa.methods.method.edit"
@model={{this.model.method.id}}

View File

@ -36,7 +36,7 @@
<Item.content>
{{list.item.id}}
</Item.content>
<Item.menu as |m|>
<Item.menu>
{{#let (concat this.currentNamespace (if this.currentNamespace "/") list.item.id) as |targetNamespace|}}
{{#if (includes targetNamespace this.accessibleNamespaces)}}
<li class="action">
@ -46,23 +46,22 @@
</li>
{{/if}}
{{/let}}
<li class="action">
<m.Message
@id={{list.item.id}}
@confirmButtonText="Remove"
@message="Any engines or mounts in this namespace will also be removed."
@onConfirm={{action
(perform
Item.callMethod
"destroyRecord"
list.item
(concat "Successfully deleted namespace: " list.item.id)
"There was an error deleting this namespace: "
(action "refreshNamespaceList")
)
}}
/>
</li>
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Delete"
@confirmTitle="Delete this namespace?"
@confirmMessage="Any engines or mounts in this namespace will also be removed."
@onConfirmAction={{action
(perform
Item.callMethod
"destroyRecord"
list.item
(concat "Successfully deleted namespace: " list.item.id)
"There was an error deleting this namespace: "
(action "refreshNamespaceList")
)
}}
/>
</Item.menu>
</ListItem>
{{/if}}

View File

@ -41,15 +41,14 @@
<ToolbarActions>
{{#if @model.canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Delete assignment"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
@confirmTitle="Delete assignment?"
@confirmMessage="This assignment will be permanently deleted. You will not be able to recover it."
@confirmButtonText="Delete"
data-test-oidc-assignment-delete
>
Delete assignment
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if @model.canEdit}}

View File

@ -8,14 +8,13 @@
{{#if this.model.canDelete}}
<ConfirmAction
data-test-oidc-client-delete
@buttonClasses="toolbar-link"
@buttonText="Delete application"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
@confirmTitle="Delete application?"
@confirmMessage="This application will be permanently deleted. You will need to re-create it to use it again."
@confirmButtonText="Delete"
>
Delete application
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.model.canEdit}}

View File

@ -5,43 +5,30 @@
<Toolbar>
<ToolbarActions>
{{#if this.model.canDelete}}
<ToolTip @verticalPosition="above" as |T|>
<T.Trigger tabindex="-1">
<ConfirmAction
data-test-oidc-key-delete
@disabled={{eq this.model.name "default"}}
@buttonClasses="toolbar-link"
@onConfirmAction={{this.delete}}
@confirmTitle="Delete key?"
@confirmMessage="This key will be permanently deleted. You will not be able to recover it."
@confirmButtonText="Delete"
>
Delete key
</ConfirmAction>
</T.Trigger>
{{#if (eq this.model.name "default")}}
<T.Content @defaultClass="tool-tip smaller-font">
<div class="box">
This is a built-in key that cannot be deleted.
</div>
</T.Content>
{{/if}}
</ToolTip>
{{#if (and (not-eq this.model.name "default") this.model.canDelete)}}
<ConfirmAction
@buttonText="Delete key"
data-test-oidc-key-delete
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
@confirmTitle="Delete key?"
@confirmMessage="This key will be permanently deleted. You will not be able to recover it."
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.model.canRotate}}
<ConfirmAction
@buttonText="Rotate key"
data-test-oidc-key-rotate
@buttonClasses="toolbar-link"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{perform this.rotateKey}}
@confirmTitle="Rotate this key?"
@confirmMessage="After rotation, a new public/private key pair will be generated."
@confirmButtonText="Rotate"
@modalColor="warning"
@isRunning={{this.rotateKey.isRunning}}
>
Rotate key
</ConfirmAction>
/>
{{/if}}
{{#if this.model.canEdit}}
<ToolbarLink @route="vault.cluster.access.oidc.keys.key.edit" @model={{this.model.name}} data-test-oidc-key-edit>

View File

@ -5,29 +5,16 @@
<Toolbar>
<ToolbarActions>
{{#if this.model.canDelete}}
<ToolTip @verticalPosition="above" as |T|>
<T.Trigger tabindex="-1">
<ConfirmAction
data-test-oidc-provider-delete
@disabled={{eq this.model.name "default"}}
@buttonClasses="toolbar-link"
@onConfirmAction={{this.delete}}
@confirmTitle="Delete provider?"
@confirmMessage="This provider will be permanently deleted. You will need to re-create it to use it again."
@confirmButtonText="Delete"
>
Delete provider
</ConfirmAction>
</T.Trigger>
{{#if (eq this.model.name "default")}}
<T.Content @defaultClass="tool-tip smaller-font">
<div class="box">
This is a built-in provider that cannot be deleted.
</div>
</T.Content>
{{/if}}
</ToolTip>
{{#if (and (not-eq this.model.name "default") this.model.canDelete)}}
<ConfirmAction
data-test-oidc-provider-delete
@buttonText="Delete provider"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
@confirmTitle="Delete provider?"
@confirmMessage="This provider will be permanently deleted. You will need to re-create it to use it again."
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.model.canEdit}}

View File

@ -38,14 +38,13 @@
{{#if this.model.canDelete}}
<ConfirmAction
data-test-oidc-scope-delete
@buttonClasses="toolbar-link"
@buttonText="Delete scope"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
@confirmTitle="Delete scope?"
@confirmMessage="This scope will be permanently deleted. You will not be able to recover it."
@confirmButtonText="Delete"
>
Delete scope
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.model.canEdit}}

View File

@ -92,53 +92,50 @@
</div>
<div class="column has-text-right">
<PopupMenu name="policy-nav">
<Confirm as |c|>
<nav class="menu">
<ul class="menu-list">
{{#if item.updatePath.isPending}}
<nav class="menu">
<ul class="menu-list">
{{#if item.updatePath.isPending}}
<li class="action">
<LoadingDropdownOption />
</li>
<li class="action">
<LoadingDropdownOption />
</li>
{{else}}
{{#if item.canRead}}
<li class="action">
<LoadingDropdownOption />
<LinkTo
@route="vault.cluster.policy.show"
@models={{array this.policyType item.id}}
data-test-policy-link="show"
>
Details
</LinkTo>
</li>
<li class="action">
<LoadingDropdownOption />
</li>
{{else}}
{{#if item.canRead}}
<li class="action">
<LinkTo
@route="vault.cluster.policy.show"
@models={{array this.policyType item.id}}
data-test-policy-link="show"
>
Details
</LinkTo>
</li>
{{/if}}
{{#if item.canEdit}}
<li class="action">
<LinkTo
@route="vault.cluster.policy.edit"
@models={{array this.policyType item.id}}
data-test-policy-link="edit"
>
Edit
</LinkTo>
</li>
{{/if}}
{{#if item.canDelete}}
<li class="action">
<c.Message
@id={{item.id}}
@confirmMessage="This will permanently delete this policy and may affect access to some data"
@onConfirm={{action "deletePolicy" item}}
data-test-policy-delete={{item.id}}
/>
</li>
{{/if}}
{{/if}}
</ul>
</nav>
</Confirm>
{{#if item.canEdit}}
<li class="action">
<LinkTo
@route="vault.cluster.policy.edit"
@models={{array this.policyType item.id}}
data-test-policy-link="edit"
>
Edit
</LinkTo>
</li>
{{/if}}
{{#if item.canDelete}}
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Delete"
@confirmTitle="Delete this policy?"
@confirmMessage="This will permanently delete this policy and may affect access to some data"
@onConfirmAction={{action "deletePolicy" item}}
/>
{{/if}}
{{/if}}
</ul>
</nav>
</PopupMenu>
</div>
</div>

View File

@ -31,13 +31,12 @@
<ToolbarActions>
{{#if (and (not-eq this.model.id "default") this.capabilities.canDelete)}}
<ConfirmAction
@buttonClasses="toolbar-link"
@confirmMessage="This may affect access to Vault data."
@buttonText="Delete policy"
class="toolbar-button"
@buttonColor="secondary"
@confirmMessage="Deleting this policy may affect access to Vault data."
@onConfirmAction={{this.deletePolicy}}
data-test-policy-delete="true"
>
Delete
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
<ToolbarLink @route="vault.cluster.policy.show" @model={{this.model.id}} data-test-policy-edit-toggle>

View File

@ -96,30 +96,24 @@
{{! meatball sandwich menu }}
<div class="linked-block-popup-menu">
<PopupMenu @name="engine-menu">
<Confirm as |c|>
<nav class="menu" aria-label="{{if backend.isSupportedBackend 'supported' 'unsupported'}} secrets engine menu">
<ul class="menu-list">
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.configuration" @model={{backend.id}} data-test-engine-config>
View configuration
</LinkTo>
</li>
{{#if (not-eq backend.type "cubbyhole")}}
<li class="action">
<c.Message
@id={{backend.id}}
@triggerText="Disable"
@message="Any data in this engine will be permanently deleted."
@title="Disable engine?"
@confirmButtonText="Disable"
@onConfirm={{perform this.disableEngine backend}}
data-test-engine-disable="true"
/>
</li>
{{/if}}
</ul>
</nav>
</Confirm>
<nav class="menu" aria-label="{{if backend.isSupportedBackend 'supported' 'unsupported'}} secrets engine menu">
<ul class="menu-list">
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.configuration" @model={{backend.id}} data-test-engine-config>
View configuration
</LinkTo>
</li>
{{#if (not-eq backend.type "cubbyhole")}}
<ConfirmAction
@isInDropdown={{true}}
@confirmMessage="Any data in this engine will be permanently deleted."
@confirmTitle="Disable engine?"
@buttonText="Disable"
@onConfirmAction={{perform this.disableEngine backend}}
/>
{{/if}}
</ul>
</nav>
</PopupMenu>
</div>
</LinkedBlock>

View File

@ -3,57 +3,65 @@
SPDX-License-Identifier: BUSL-1.1
~}}
<div class="confirm-action" ...attributes>
<BasicDropdown
@horizontalPosition={{this.horizontalPosition}}
@verticalPosition={{this.verticalPosition}}
@onOpen={{this.toggleConfirm}}
@onClose={{this.toggleConfirm}}
as |d|
{{#if @isInDropdown}}
{{! Hds component renders <li> and <button> elements }}
<Hds::Dropdown::ListItem::Interactive
data-test-confirm-action-trigger
@text={{@buttonText}}
@color="critical"
{{on "click" (fn (mut this.showConfirmModal) true)}}
...attributes
{{! remove class when dropdown/popup menus are replaced with Hds::Dropdown }}
class="hds-confirm-action-critical"
/>
{{else}}
<Hds::Button
data-test-confirm-action-trigger
@text={{@buttonText}}
@color={{@buttonColor}}
{{on "click" (fn (mut this.showConfirmModal) true)}}
...attributes
/>
{{/if}}
{{#if this.showConfirmModal}}
<Hds::Modal
id="confirm-action-modal"
@color={{this.modalColor}}
@size="small"
@onClose={{fn (mut this.showConfirmModal) false}}
as |M|
>
<d.Trigger
@htmlTag="button"
class={{concat @buttonClasses " popup-menu-trigger" (if d.isOpen " is-active")}}
type="button"
disabled={{this.disabled}}
data-test-confirm-action-trigger="true"
>
{{yield}}
{{#if (eq @buttonClasses "toolbar-link")}}
<Chevron @direction={{if this.showConfirm "up" "down"}} data-test-confirm-action-chevron />
{{/if}}
</d.Trigger>
<d.Content @defaultClass="popup-menu-content">
<div class="box confirm-action-message">
<div class="message is-highlight">
<div class="message-title" data-test-confirm-action-title>
<Icon @name="alert-triangle-fill" />
{{this.confirmTitle}}
</div>
<p>
{{this.confirmMessage}}
</p>
</div>
<div class="confirm-action-options">
{{! TODO Hds::Button replacement - skipping because modal will replace this confirm inline-popup menu }}
<button
type="button"
disabled={{or this.disabled this.isRunning}}
class="link is-destroy"
data-test-confirm-button="true"
{{on "click" (fn this.onConfirm d.actions)}}
>
{{#if this.isRunning}}
<span class="loader is-inline-block"></span>
{{else}}
{{this.confirmButtonText}}
{{/if}}
</button>
<button type="button" class="link" data-test-confirm-cancel-button="true" {{on "click" d.actions.close}}>
{{this.cancelButtonText}}
</button>
</div>
</div>
</d.Content>
</BasicDropdown>
</div>
{{#if @disabledMessage}}
<M.Header data-test-confirm-action-title @icon="x-circle">
Not allowed
</M.Header>
<M.Body data-test-confirm-action-message>
{{@disabledMessage}}
</M.Body>
<M.Footer as |F|>
<Hds::Button data-test-confirm-cancel-button @text="Close" {{on "click" F.close}} />
</M.Footer>
{{else}}
<M.Header data-test-confirm-action-title @icon="alert-circle">
{{or @confirmTitle "Are you sure?"}}
</M.Header>
<M.Body data-test-confirm-action-message>
{{this.confirmMessage}}
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button
data-test-confirm-button
disabled={{@isRunning}}
@icon={{if @isRunning "loading"}}
@color={{if (eq this.modalColor "critical") "critical" "primary"}}
@text="Confirm"
{{on "click" this.onConfirm}}
/>
<Hds::Button data-test-confirm-cancel-button @color="secondary" @text="Cancel" {{on "click" F.close}} />
</Hds::ButtonSet>
</M.Footer>
{{/if}}
</Hds::Modal>
{{/if}}

View File

@ -10,81 +10,69 @@ import { tracked } from '@glimmer/tracking';
/**
* @module ConfirmAction
* `ConfirmAction` is a button followed by a pop up confirmation message and button used to prevent users from performing actions they do not intend to.
* ConfirmAction is a button that opens a modal containing a confirmation message with confirm or cancel action.
* Splattributes are spread to the button element to apply styling directly without adding extra args.
*
*
* @example
* ```js
* <ConfirmAction
* @onConfirmAction={{ () => { console.log('Action!') } }}
* @confirmMessage="Are you sure you want to delete this config?">
* Delete
* </ConfirmAction>
// in dropdown
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Delete"
@confirmMessage="This action cannot be undone."
@onConfirmAction={{log "my action!"}}
/>
// in toolbar
<ConfirmAction
class="toolbar-button"
@buttonColor="secondary"
@buttonText="Delete item"
@confirmTitle="Delete item?"
@onConfirmAction={{log "my action!"}}
@confirmMessage="Are you sure you want to delete this config?"
@isRunning={{this.rotateKey.isRunning}}
@disabledMessage={{if true "A secondary ID is required perform revocation."}}
/>
* ```
*
* @param {Func} [onConfirmAction=null] - The action to take upon confirming.
* @param {String} [confirmTitle=Delete this?] - The title to display when confirming.
* @param {Function} onConfirmAction - The action to take upon confirming.
* @param {String} [confirmTitle=Are you sure?] - The title to display in the confirmation modal.
* @param {String} [confirmMessage=You will not be able to recover it later.] - The message to display when confirming.
* @param {String} [confirmButtonText=Delete] - The confirm button text.
* @param {String} [cancelButtonText=Cancel] - The cancel button text.
* @param {String} [buttonClasses] - A string to indicate the button class.
* @param {String} [horizontalPosition=auto-right] - For the position of the dropdown.
* @param {String} [verticalPosition=below] - For the position of the dropdown.
* @param {Boolean} [isRunning=false] - If action is still running disable the confirm.
* @param {Boolean} [disable=false] - To disable the confirm action.
* @param {Boolean} isInDropdown - If true styles for dropdowns, button color is 'critical', and renders inside `<li>` elements via `<Hds::Dropdown::ListItem::Interactive`
* @param {String} buttonText - Text for the button that toggles modal to open.
* @param {String} [buttonColor=primary] - Color of button that toggles modal, only applies when @isInDropdown=false. Options are primary, secondary (use for toolbars), tertiary, or critical
* @param {String} [modalColor=critical] - Styles modal color, if 'critical' confirm button is also 'critical'. Possible values: critical, warning or neutral ('neutral' used for @disabledMessage modal)
* @param {Boolean} [isRunning] - Disables the modal confirm button - usually a concurrency task that informs the modal if a process is still running
* @param {String} [disabledMessage] - A message explaining why the confirm action is not allowed, usually combined with a conditional that returns a string if true
*
*/
export default class ConfirmActionComponent extends Component {
@tracked showConfirm = false;
@tracked showConfirmModal = false;
get horizontalPosition() {
return this.args.horizontalPosition || 'auto-right';
}
get verticalPosition() {
return this.args.verticalPosition || 'below';
}
get isRunning() {
return this.args.isRunning || false;
}
get disabled() {
return this.args.disabled || false;
}
get confirmTitle() {
return this.args.confirmTitle || 'Delete this?';
constructor() {
super(...arguments);
assert(
'<ConfirmAction> component expects @onConfirmAction arg to be a function',
typeof this.args.onConfirmAction === 'function'
);
assert(`@buttonText is required for ConfirmAction components`, this.args.buttonText);
}
get confirmMessage() {
return this.args.confirmMessage || 'You will not be able to recover it later.';
}
get confirmButtonText() {
return this.args.confirmButtonText || 'Delete';
}
get cancelButtonText() {
return this.args.cancelButtonText || 'Cancel';
get modalColor() {
if (this.args.disabledMessage) return 'neutral';
return this.args.modalColor || 'critical';
}
@action
toggleConfirm() {
// toggle
this.showConfirm = !this.showConfirm;
}
@action
onConfirm(actions) {
const confirmAction = this.args.onConfirmAction;
if (typeof confirmAction !== 'function') {
assert('confirm-action components expects `onConfirmAction` attr to be a function');
} else {
confirmAction();
// close the dropdown content
actions.close();
}
async onConfirm() {
await this.args.onConfirmAction();
// close modal after destructive operation
this.showConfirmModal = false;
}
}

View File

@ -1,72 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from '../templates/components/confirm';
import { next } from '@ember/runloop';
/**
* @module Confirm
* `Confirm` components prevent users from performing actions they do not intend to by showing a confirmation message as an overlay. This is a contextual component that should always be rendered with a `Message` which triggers the message.
*
* @example
* ```js
* <div class="box">
* <Confirm as |c|>
* <c.Message
* @id={{item.id}}
* @triggerText="Delete"
* @message="This will permanently delete this secret and all its versions."
* @onConfirm={{action "delete" item "secret"}}
* />
* </Confirm>
* </div>
* ```
*/
export default Component.extend({
layout,
openTrigger: null,
height: 0,
focusTrigger: null,
wormholeReference: null,
wormholeId: computed('elementId', function () {
return `confirm-${this.elementId}`;
}),
didInsertElement() {
this._super(...arguments);
this.set('wormholeReference', this.element.querySelector(`#${this.wormholeId}`));
},
didRender() {
this._super(...arguments);
this.updateHeight();
},
updateHeight: function () {
const height = this.openTrigger
? this.element.querySelector('.confirm-overlay').clientHeight
: this.element.querySelector('.confirm').clientHeight;
this.element.querySelector('.confirm-wrapper').style = `height: ${height}px;`;
},
actions: {
onTrigger: function (itemId, e) {
this.set('openTrigger', itemId);
// store a reference to the trigger so we can focus the element
// after clicking cancel
this.set('focusTrigger', e.target);
this.updateHeight();
},
onCancel: function () {
this.set('openTrigger', '');
this.updateHeight();
next(() => {
this.focusTrigger.focus();
this.set('focusTrigger', null);
});
},
},
});

View File

@ -1,59 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from '../../templates/components/confirm/message';
/**
* @module Message
* `Message` components trigger and display a confirmation message. They should only be used within a `Confirm` component.
*
* @example
* ```js
* <div class="box">
* <Confirm as |c|>
* <c.Message
* @id={{item.id}}
* @triggerText="Delete"
* @message="This will permanently delete this secret and all its versions."
* @onConfirm={{action "delete" item "secret"}}
* />
* </Confirm>
* </div>
* ```
*
* @property id=null {ID} - A unique identifier used to bind a trigger to a confirmation message.
* @property onConfirm=null {Func} - The action to take when the user clicks the confirm button.
* @property [triggerText='Delete'] {String} - The text on the trigger button.
* @property [title='Delete this?'] {String} - The header text to display in the confirmation message.
* @property [message='You will not be able to recover it later.'] {String} - The message to display above the confirm and cancel buttons.
* @property [confirmButtonText='Delete'] {String} - The text to display on the confirm button.
* @property [cancelButtonText='Cancel'] {String} - The text to display on the cancel button.
*/
export default Component.extend({
layout,
tagName: '',
renderedTrigger: null,
id: null,
onCancel() {},
onConfirm() {},
resetTrigger() {},
title: 'Delete this?',
message: 'You will not be able to recover it later.',
triggerText: 'Delete',
confirmButtonText: 'Delete',
cancelButtonText: 'Cancel',
showConfirm: computed('id', 'renderedTrigger', function () {
return this.renderedTrigger === this.id;
}),
actions: {
onConfirm() {
this.onConfirm();
this.resetTrigger();
},
},
});

View File

@ -1,12 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<li class="action">
{{#if @loadingParam}}
<LoadingDropdownOption />
{{else}}
{{yield}}
{{/if}}
</li>

View File

@ -1,23 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<div class="confirm-wrapper">
<div class="confirm {{if this.openTrigger 'show-confirm'}}" ...attributes>
{{yield
(hash
Message=(component
"confirm/message"
renderedTrigger=(readonly this.openTrigger)
wormholeReference=this.wormholeReference
onCancel=(action "onCancel")
onTrigger=(action "onTrigger")
resetTrigger=(action (mut this.openTrigger) "")
)
)
}}
</div>
<div id={{this.wormholeId}} class="confirm-overlay {{if this.openTrigger 'show-confirm'}}">
</div>
</div>

View File

@ -1,40 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
{{#if this.showConfirm}}
{{#maybe-in-element this.wormholeReference false}}
<div class="confirm-action-message">
<div class="message is-highlight">
<div class="message-title">
<Icon @name="alert-triangle-fill" />
{{this.title}}
</div>
<p>
{{this.message}}
</p>
</div>
<div class="confirm-action-options">
{{! TODO Hds::Button replacement - skipping because modal will replace this confirm inline-popup menu }}
<button type="button" class="link is-destroy" data-test-confirm-button="true" onclick={{action "onConfirm"}}>
{{this.confirmButtonText}}
</button>
<button type="button" class="link" data-test-confirm-cancel-button="true" {{action this.onCancel}}>
{{this.cancelButtonText}}
</button>
</div>
</div>
{{/maybe-in-element}}
{{/if}}
{{! TODO Hds::Button replacement - skipping because modal will replace this confirm inline-popup menu }}
<button
type="button"
class="link is-destroy"
disabled={{this.showConfirm}}
onclick={{action this.onTrigger this.id}}
data-test-confirm-action-trigger={{this.id}}
...attributes
>
{{this.triggerText}}
</button>

View File

@ -5,13 +5,11 @@
{{#if this.hasMenu}}
<PopupMenu>
<Confirm as |c|>
<nav class="menu">
<ul class="menu-list">
{{yield (hash Message=c.Message)}}
</ul>
</nav>
</Confirm>
<nav class="menu">
<ul class="menu-list">
{{yield}}
</ul>
</nav>
</PopupMenu>
{{else}}
{{yield this.item}}

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/components/confirm';

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/components/confirm/message';

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/components/menu-loader';

View File

@ -64,32 +64,33 @@
<Item.content>
<Icon @name="file-text" class="has-text-grey-light" />{{list.item.id}}
</Item.content>
<Item.menu as |m|>
<Item.menu>
<li class="action">
<LinkTo @route="credentials.show" @models={{array this.scope this.role list.item.id}} class="is-block">
View credentials
</LinkTo>
</li>
{{#if list.item.deletePath.canDelete}}
<MenuLoader @loadingParam={{list.item.deletePath.isPending}}>
<m.Message
@id={{list.item.id}}
@triggerText="Revoke credentials"
@title="Revoke this?"
@message="Any client using these credentials will no longer be able to."
@confirmButtonText="Revoke"
@onConfirm={{action
(perform
Item.callMethod
"destroyRecord"
list.item
"Successfully revoked credentials"
"There was an error revoking the credentials"
(action "refresh")
)
}}
/>
</MenuLoader>
{{#if list.item.deletePath.isPending}}
<li class="action">
<LoadingDropdownOption />
</li>
{{else if list.item.deletePath.canDelete}}
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Revoke credentials"
@confirmTitle="Revoke this?"
@confirmMessage="Any client using these credentials will no longer be able to."
@onConfirmAction={{action
(perform
Item.callMethod
"destroyRecord"
list.item
"Successfully revoked credentials"
"There was an error revoking the credentials"
(action "refresh")
)
}}
/>
{{/if}}
</Item.menu>
</ListItem>

View File

@ -17,15 +17,13 @@
<ToolbarActions>
{{#if this.model.deletePath.canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Revoke credentials"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.revokeCredentials}}
@confirmTitle="Revoke this?"
@confirmMessage="Any client using these credentials will no longer be able to."
@cancelButtonText="Cancel"
@confirmButtonText="Revoke"
>
Revoke credentials
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
<ToolbarLink @route="credentials.index" @models={{array this.scope this.role}} data-test-kmip-link-back-to-role>

View File

@ -8,13 +8,12 @@
<ToolbarActions>
{{#if this.model.updatePath.canUpdate}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Delete role"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.deleteRole}}
@confirmMessage={{concat "Are you sure you want to delete " this.model.id "?"}}
@cancelButtonText="Cancel"
>
Delete role
</ConfirmAction>
@confirmMessage="Are you sure you want to delete {{this.model.id}}?"
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.model.updatePath.canUpdate}}

View File

@ -70,7 +70,7 @@
<Item.content>
<Icon @name="user" class="has-text-grey-light" />{{list.item.id}}
</Item.content>
<Item.menu as |m|>
<Item.menu>
<li class="action">
<LinkTo @route="credentials" @models={{array this.scope list.item.id}} class="is-block">
View credentials
@ -81,20 +81,22 @@
View role
</LinkTo>
</li>
{{#if list.item.updatePath.canUpdate}}
<MenuLoader @loadingParam={{list.item.updatePath.isPending}}>
{{#if list.item.updatePath.isPending}}
<li class="action">
<LoadingDropdownOption />
</li>
{{else}}
{{#if list.item.updatePath.canUpdate}}
<LinkTo @route="role.edit" @models={{array this.scope list.item.id}} class="is-block">
Edit role
</LinkTo>
</MenuLoader>
{{/if}}
{{#if list.item.updatePath.canDelete}}
<MenuLoader @loadingParam={{list.item.updatePath.isPending}}>
<m.Message
@id={{list.item.id}}
@triggerText="Delete role"
@message={{concat "Are you sure you want to delete " list.item.id "?"}}
@onConfirm={{action
{{/if}}
{{#if list.item.updatePath.canDelete}}
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Delete role"
@confirmMessage="Are you sure you want to delete {{list.item.id}}?"
@onConfirmAction={{action
(perform
Item.callMethod
"destroyRecord"
@ -104,9 +106,9 @@
(action "refresh")
)
}}
data-test-scope-delete="true"
data-test-scope-delete
/>
</MenuLoader>
{{/if}}
{{/if}}
</Item.menu>
</ListItem>

View File

@ -59,33 +59,34 @@
<Item.content>
<Icon @name="folder" class="has-text-grey-light" />{{list.item.id}}
</Item.content>
<Item.menu as |m|>
<Item.menu>
<li class="action">
<LinkTo @route="scope" @model={{list.item.id}} class="is-block">
View scope
</LinkTo>
</li>
{{#if list.item.updatePath.canDelete}}
<MenuLoader @loadingParam={{list.item.updatePath.isPending}}>
<m.Message
@id={{list.item.id}}
@triggerText="Delete scope"
@title={{concat "Delete scope " list.item.id "?"}}
@message="This will permanently delete this scope and all roles and credentials contained within"
@cancelButtonText="Cancel"
@onConfirm={{action
(perform
Item.callMethod
"destroyRecord"
list.item
(concat "Successfully deleted scope " list.item.id)
(concat "There was an error deleting the scope " list.item.id)
(action "refresh")
)
}}
data-test-scope-delete="true"
/>
</MenuLoader>
{{#if list.item.updatePath.isPending}}
<li class="action">
<LoadingDropdownOption />
</li>
{{else if list.item.updatePath.canDelete}}
<ConfirmAction
@isInDropdown={{true}}
@buttonText="Delete scope"
@confirmTitle="Delete scope {{list.item.id}}?"
@confirmMessage="This will permanently delete this scope and all roles and credentials contained within"
@onConfirmAction={{action
(perform
Item.callMethod
"destroyRecord"
list.item
(concat "Successfully deleted scope " list.item.id)
(concat "There was an error deleting the scope " list.item.id)
(action "refresh")
)
}}
data-test-scope-delete
/>
{{/if}}
</Item.menu>
</ListItem>

View File

@ -17,9 +17,13 @@
<Toolbar>
<ToolbarActions>
{{#if @model.canDelete}}
<ConfirmAction @buttonClasses="toolbar-link" @onConfirmAction={{this.delete}} data-test-delete>
Delete role
</ConfirmAction>
<ConfirmAction
@buttonText="Delete role"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
data-test-delete
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if @model.canGenerateCreds}}

View File

@ -40,7 +40,7 @@
<Icon @name="user" />
<span data-test-role={{role.name}}>{{role.name}}</span>
</Item.content>
<Item.menu as |Menu|>
<Item.menu>
{{#if role.rolesPath.isLoading}}
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
@ -68,16 +68,13 @@
Edit
</LinkTo>
</li>
<li class="action">
<Menu.Message
data-test-delete
@id={{role.id}}
@triggerText="Delete"
@title="Are you sure?"
@message="Deleting this role means that youll need to recreate it in order to generate credentials again."
@onConfirm={{fn this.onDelete role}}
/>
</li>
<ConfirmAction
data-test-delete
@isInDropdown={{true}}
@buttonText="Delete"
@confirmMessage="Deleting this role means that youll need to recreate it in order to generate credentials again."
@onConfirmAction={{fn this.onDelete role}}
/>
{{/if}}
</Item.menu>
</ListItem>

View File

@ -101,17 +101,12 @@
</li>
{{/if}}
{{#if metadata.canDeleteMetadata}}
<li>
<ConfirmAction
@buttonClasses="link is-destroy"
@onConfirmAction={{fn this.onDelete metadata}}
@confirmMessage="This will permanently delete this secret and all its versions."
@cancelButtonText="Cancel"
data-test-delete-metadata={{metadata.path}}
>
Permanently delete
</ConfirmAction>
</li>
<ConfirmAction
@buttonText="Permanently delete"
@isInDropdown={{true}}
@onConfirmAction={{fn this.onDelete metadata}}
@confirmMessage="This will permanently delete this secret and all its versions."
/>
{{/if}}
{{/if}}
</ul>

View File

@ -7,16 +7,16 @@
<:toolbarActions>
{{#if @configModel}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Rotate root"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{perform this.rotateRoot}}
@confirmTitle="Rotate root?"
@confirmMessage="After rotation, Vault will generate a new root password in your directory server."
@confirmButtonText="Rotate"
@disabled={{this.rotateRoot.isRunning}}
@modalColor="warning"
@isRunning={{this.rotateRoot.isRunning}}
data-test-toolbar-rotate-action
>
Rotate root
</ConfirmAction>
/>
{{/if}}
<ToolbarLink @route="configure" data-test-toolbar-config-action>
{{if @configModel "Edit configuration" "Configure LDAP"}}

View File

@ -46,7 +46,7 @@
<Icon @name="folder" />
<span data-test-library={{library.name}}>{{library.name}}</span>
</Item.content>
<Item.menu as |Menu|>
<Item.menu>
{{#if library.libraryPath.isLoading}}
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
@ -75,16 +75,14 @@
</LinkTo>
</li>
{{#if library.canDelete}}
<li class="action">
<Menu.Message
data-test-delete
@id={{library.id}}
@triggerText="Delete"
@title="Are you sure?"
@message="This library and associated accounts will be permanently deleted. You will not be able to recover it."
@onConfirm={{fn this.onDelete library}}
/>
</li>
<ConfirmAction
data-test-delete
@id={{library.id}}
@isInDropdown={{true}}
@buttonText="Delete"
@confirmMessage="This library and associated accounts will be permanently deleted. You will not be able to recover it."
@onConfirmAction={{fn this.onDelete library}}
/>
{{/if}}
{{/if}}
</Item.menu>

View File

@ -26,9 +26,13 @@
<Toolbar>
<ToolbarActions>
{{#if @model.canDelete}}
<ConfirmAction @buttonClasses="toolbar-link" @onConfirmAction={{this.delete}} data-test-delete>
Delete library
</ConfirmAction>
<ConfirmAction
@buttonText="Delete library"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
data-test-delete
/>
{{#if @model.canEdit}}
<div class="toolbar-separator"></div>
{{/if}}

View File

@ -17,9 +17,13 @@
<Toolbar>
<ToolbarActions>
{{#if @model.canDelete}}
<ConfirmAction @buttonClasses="toolbar-link" @onConfirmAction={{this.delete}} data-test-delete>
Delete role
</ConfirmAction>
<ConfirmAction
@buttonText="Delete role"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.delete}}
data-test-delete
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if @model.canReadCreds}}
@ -29,16 +33,16 @@
{{/if}}
{{#if @model.canRotateStaticCreds}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Rotate credentials"
class="toolbar-button"
@buttonColor="secondary"
@confirmTitle="Rotate credentials?"
@confirmMessage="When manually rotating credentials, the rotation period will start over."
@confirmButtonText="Rotate"
@disabled={{this.rotateCredentials.isRunning}}
@modalColor="warning"
@isRunning={{this.rotateCredentials.isRunning}}
@onConfirmAction={{perform this.rotateCredentials}}
data-test-rotate-credentials
>
Rotate credentials
</ConfirmAction>
/>
{{/if}}
{{#if @model.canEdit}}
<ToolbarLink @route="roles.role.edit" data-test-edit>

View File

@ -53,7 +53,7 @@
<span data-test-role={{role.name}}>{{role.name}}</span>
<Hds::Badge @text={{role.type}} data-test-role-type-badge={{role.name}} />
</Item.content>
<Item.menu as |Menu|>
<Item.menu>
{{#if role.rolePath.isLoading}}
<li class="action">
<Hds::Button disabled @color="tertiary" @icon="loading" @text="loading" @isIconOnly={{true}} />
@ -82,17 +82,15 @@
</LinkTo>
</li>
{{#if role.canRotateStaticCreds}}
<li class="action">
<Menu.Message
data-test-rotate-creds
@id={{concat "rotate-" role.id}}
@triggerText="Rotate credentials"
@title="Are you sure?"
@message="When manually rotating credentials, the rotation period will start over."
@confirmButtonText="Rotate"
@onConfirm={{fn this.onRotate role}}
/>
</li>
<ConfirmAction
data-test-rotate-creds
@isInDropdown={{true}}
@id={{concat "rotate-" role.id}}
@buttonText="Rotate credentials"
@confirmMessage="When manually rotating credentials, the rotation period will start over."
@modalColor="warning"
@onConfirmAction={{fn this.onRotate role}}
/>
{{/if}}
<li class="action">
<LinkTo
@ -107,16 +105,13 @@
</LinkTo>
</li>
{{#if role.canDelete}}
<li class="action">
<Menu.Message
data-test-delete
@id={{concat "delete-" role.id}}
@triggerText="Delete"
@title="Are you sure?"
@message="Deleting this role means that youll need to recreate it in order to generate credentials again."
@onConfirm={{fn this.onDelete role}}
/>
</li>
<ConfirmAction
data-test-delete
@isInDropdown={{true}}
@buttonText="Delete"
@confirmMessage="Deleting this role means that youll need to recreate it in order to generate credentials again."
@onConfirmAction={{fn this.onDelete role}}
/>
{{/if}}
{{/if}}
</Item.menu>

View File

@ -15,14 +15,13 @@
/>
{{#if @model.canRevoke}}
<ConfirmAction
@buttonClasses="toolbar-link"
@buttonText="Revoke certificate"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{fn (perform this.revoke)}}
@confirmTitle="Revoke certificate?"
@confirmButtonText="Revoke"
data-test-pki-cert-revoke-button
>
Revoke certificate
</ConfirmAction>
/>
{{/if}}
</ToolbarActions>
</Toolbar>

View File

@ -7,14 +7,13 @@
<ToolbarActions>
{{#if @canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.deleteKey}}
@confirmTitle="Delete key?"
@confirmButtonText="Delete"
@buttonText="Delete"
data-test-pki-key-delete
>
Delete
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if @key.privateKey}}

View File

@ -7,14 +7,13 @@
<ToolbarActions>
{{#if @role.canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
class="toolbar-button"
@buttonColor="secondary"
@onConfirmAction={{this.deleteRole}}
@confirmTitle="Delete role?"
@confirmButtonText="Delete"
@buttonText="Delete"
data-test-pki-role-delete
>
Delete
</ConfirmAction>
/>
<div class="toolbar-separator"></div>
{{/if}}
{{#if @role.canGenerateCert}}

View File

@ -46,15 +46,12 @@
{{#if this.model.canRevokeSecondary}}
<li class="action">
<ConfirmAction
@buttonClasses="button link is-destroy"
@buttonText="Revoke"
@isInDropdown={{true}}
@confirmTitle="Revoke token?"
@confirmMessage="This will revoke this secondary token."
@confirmButtonText="Revoke"
@horizontalPosition="auto-left"
@onConfirmAction={{action "onSubmit" "revoke-secondary" "primary" (hash id=secondary)}}
>
Revoke
</ConfirmAction>
/>
</li>
{{/if}}
</ul>

View File

@ -23,17 +23,12 @@
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="control">
<ConfirmAction
@buttonClasses="button is-primary"
@buttonText="Revoke"
@confirmTitle="Revoke token?"
@confirmMessage="This will revoke this secondary token."
@confirmButtonText="Revoke"
@disabled={{not this.id}}
@disabledMessage="A secondary ID is required perform revocation."
@horizontalPosition="auto-left"
@disabledMessage={{unless this.id "A secondary ID is required perform revocation."}}
@onConfirmAction={{action "onSubmit" "revoke-secondary" "primary" (hash id=this.id)}}
>
Revoke
</ConfirmAction>
/>
</div>
<div class="control">
{{#unless this.isRevoking}}

View File

@ -107,9 +107,13 @@ module('Acceptance | mfa-method', function (hooks) {
await visit('/vault/access/mfa/methods');
await click('[data-test-mfa-method-list-item]');
assert.dom('[data-test-tab="config"]').hasClass('active', 'Configuration tab is active by default');
await click('[data-test-delete-mfa-config]');
assert
.dom('[data-test-confirm-action-trigger]')
.isDisabled('Delete toolbar action disabled when method is attached to an enforcement');
.dom('[data-test-confirm-action-message]')
.hasText(
"This method cannot be deleted until its enforcements are deleted. This can be done from the 'Enforcements' tab."
);
const fields = [
['Issuer', 'Period', 'Key size', 'QR size', 'Algorithm', 'Digits', 'Skew', 'Max validation attempts'],

View File

@ -81,7 +81,7 @@ module('Acceptance | oidc-config clients and keys', function (hooks) {
// navigate to default key details from pop-up menu
await click('[data-test-popup-menu-trigger]');
await click('[data-test-oidc-key-menu-link="details"]');
assert.dom(SELECTORS.keyDeleteButton).isDisabled('delete button is disabled for default key');
assert.dom(SELECTORS.keyDeleteButton).doesNotExist('delete button is hidden for default key');
await click(SELECTORS.keyEditButton);
assert.strictEqual(
currentRouteName(),

View File

@ -376,7 +376,7 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
);
await click('[data-test-oidc-provider-linked-block="default"] [data-test-popup-menu-trigger]');
await click('[data-test-oidc-provider-menu-link="details"]');
assert.dom(SELECTORS.providerDeleteButton).isDisabled('delete button is disabled for default provider');
assert.dom(SELECTORS.providerDeleteButton).doesNotExist('delete button hidden for default provider');
});
// PROVIDER DELETE + EDIT PERMISSIONS

View File

@ -126,8 +126,8 @@ module('Acceptance | pki workflow', function (hooks) {
await visit(`/vault/secrets/${this.mountPath}/pki/roles/some-role/details`);
assert.dom(SELECTORS.deleteRoleButton).exists('Delete role button is shown');
await click(`${SELECTORS.deleteRoleButton} [data-test-confirm-action-trigger]`);
await click(`[data-test-confirm-button]`);
await click(SELECTORS.deleteRoleButton);
await click('[data-test-confirm-button]');
assert.strictEqual(
currentURL(),
`/vault/secrets/${this.mountPath}/pki/roles`,

View File

@ -55,7 +55,7 @@ module('Acceptance | policies (old)', function (hooks) {
await click('[data-test-policy-edit-toggle]');
await click('[data-test-policy-delete] button');
await click('[data-test-confirm-action-trigger]');
await click('[data-test-confirm-button]');
await waitUntil(() => currentURL() === `/vault/policies/acl`);

View File

@ -105,7 +105,7 @@ module('Acceptance | kubernetes | roles', function (hooks) {
this.validateRoute(assert, 'roles.role.edit', 'Transitions to edit route');
await click('[data-test-cancel]');
await click('[data-test-list-item-link]');
await click('[data-test-delete] button');
await click('[data-test-delete]');
await click('[data-test-confirm-button]');
assert
.dom('[data-test-list-item-link]')

View File

@ -25,7 +25,7 @@ module('Acceptance | unseal', function (hooks) {
assert.strictEqual(currentURL(), '/vault/settings/seal');
// seal
await click('[data-test-seal] button');
await click('[data-test-seal]');
await click('[data-test-confirm-button]');

View File

@ -12,12 +12,12 @@ export const SELECTORS = {
oidcClientCreateButton: '[data-test-oidc-configure]',
oidcRouteTabs: '[data-test-oidc-tabs]',
oidcLandingImg: '[data-test-oidc-img]',
confirmActionButton: '[data-test-confirm-button="true"]',
confirmActionButton: '[data-test-confirm-button]',
inlineAlert: '[data-test-inline-alert]',
// client route
clientSaveButton: '[data-test-oidc-client-save]',
clientCancelButton: '[data-test-oidc-client-cancel]',
clientDeleteButton: '[data-test-oidc-client-delete] button',
clientDeleteButton: '[data-test-oidc-client-delete]',
clientEditButton: '[data-test-oidc-client-edit]',
clientDetailsTab: '[data-test-oidc-client-details]',
clientProvidersTab: '[data-test-oidc-client-providers]',
@ -26,14 +26,14 @@ export const SELECTORS = {
assignmentSaveButton: '[data-test-oidc-assignment-save]',
assignmentCreateButton: '[data-test-oidc-assignment-create]',
assignmentEditButton: '[data-test-oidc-assignment-edit]',
assignmentDeleteButton: '[data-test-oidc-assignment-delete] button',
assignmentDeleteButton: '[data-test-oidc-assignment-delete]',
assignmentCancelButton: '[data-test-oidc-assignment-cancel]',
assignmentDetailsTab: '[data-test-oidc-assignment-details]',
// scope routes
scopeSaveButton: '[data-test-oidc-scope-save]',
scopeCancelButton: '[data-test-oidc-scope-cancel]',
scopeDeleteButton: '[data-test-oidc-scope-delete] button',
scopeDeleteButton: '[data-test-oidc-scope-delete]',
scopeEditButton: '[data-test-oidc-scope-edit]',
scopeDetailsTab: '[data-test-oidc-scope-details]',
scopeEmptyState: '[data-test-oidc-scope-empty-state]',
@ -43,16 +43,16 @@ export const SELECTORS = {
// key route
keySaveButton: '[data-test-oidc-key-save]',
keyCancelButton: '[data-test-oidc-key-cancel]',
keyDeleteButton: '[data-test-oidc-key-delete] button',
keyDeleteButton: '[data-test-oidc-key-delete]',
keyEditButton: '[data-test-oidc-key-edit]',
keyRotateButton: '[data-test-oidc-key-rotate] button',
keyRotateButton: '[data-test-oidc-key-rotate]',
keyDetailsTab: '[data-test-oidc-key-details]',
keyClientsTab: '[data-test-oidc-key-clients]',
// provider route
providerSaveButton: '[data-test-oidc-provider-save]',
providerCancelButton: '[data-test-oidc-provider-cancel]',
providerDeleteButton: '[data-test-oidc-provider-delete] button',
providerDeleteButton: '[data-test-oidc-provider-delete]',
providerEditButton: '[data-test-oidc-provider-edit]',
providerDetailsTab: '[data-test-oidc-provider-details]',
providerClientsTab: '[data-test-oidc-provider-clients]',

View File

@ -18,7 +18,7 @@ export const SELECTORS = {
keyNameValue: '[data-test-value-div="Key name"]',
keyTypeValue: '[data-test-value-div="Key type"]',
keyBitsValue: '[data-test-value-div="Key bits"]',
keyDeleteButton: '[data-test-pki-key-delete] button',
keyDeleteButton: '[data-test-pki-key-delete]',
downloadButton: '[data-test-download-button]',
keyEditLink: '[data-test-pki-key-edit]',
confirmDelete: '[data-test-confirm-button]',

View File

@ -5,54 +5,155 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click } from '@ember/test-helpers';
import { render, click, find } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
const SELECTORS = {
modalToggle: '[data-test-confirm-action-trigger]',
title: '[data-test-confirm-action-title]',
message: '[data-test-confirm-action-message]',
confirm: '[data-test-confirm-button]',
cancel: '[data-test-confirm-cancel-button]',
};
module('Integration | Component | confirm-action', function (hooks) {
setupRenderingTest(hooks);
test('it renders and on click shows the correct icon', async function (assert) {
const confirmAction = sinon.spy();
this.set('onConfirm', confirmAction);
await render(hbs`
<ConfirmAction
@onConfirmAction={{this.onConfirm}}
@buttonClasses="toolbar-link"
>
DELETE
</ConfirmAction>
`);
assert.dom('[data-test-icon="chevron-down"]').exists('Icon is pointing down');
await click('[data-test-confirm-action-trigger="true"]');
assert.dom('[data-test-icon="chevron-up"]').exists('Icon is now pointing up');
assert.dom('[data-test-confirm-action-title]').hasText('Delete this?');
hooks.beforeEach(function () {
this.onConfirm = sinon.spy();
});
test('it closes the confirmation modal on successful delete', async function (assert) {
const confirmAction = sinon.spy();
this.set('onConfirm', confirmAction);
test('it renders defaults and calls onConfirmAction', async function (assert) {
await render(hbs`
<ConfirmAction
@buttonText="DELETE"
@onConfirmAction={{this.onConfirm}}
@buttonClasses="toolbar-link"
>
DELETE
</ConfirmAction>
/>
`);
await click('[data-test-confirm-action-trigger="true"]');
await click('[data-test-confirm-cancel-button="true"]');
// assert that after CANCEL the icon button is pointing down.
assert.dom('[data-test-icon="chevron-down"]').exists('Icon is pointing down after clicking cancel');
// open the modal again to test the DELETE action
await click('[data-test-confirm-action-trigger="true"]');
await click('[data-test-confirm-button="true"]');
assert.dom(SELECTORS.modalToggle).hasText('DELETE', 'renders button text');
await click(SELECTORS.modalToggle);
// hasClass assertion wasn't working so this is the workaround
assert.strictEqual(
find('#confirm-action-modal').className,
'hds-modal hds-modal--size-small hds-modal--color-critical',
'renders critical modal color by default'
);
assert.strictEqual(
find(SELECTORS.confirm).className,
'hds-button hds-button--size-medium hds-button--color-critical',
'renders critical confirm button'
);
assert.dom(SELECTORS.title).hasText('Are you sure?', 'renders default title');
assert
.dom('[data-test-icon="chevron-down"]')
.exists('Icon is pointing down after executing the Delete action');
assert.true(confirmAction.called, 'calls the action when Delete is pressed');
.dom(SELECTORS.message)
.hasText('You will not be able to recover it later.', 'renders default body text');
await click(SELECTORS.cancel);
assert.false(this.onConfirm.called, 'does not call the action when Cancel is clicked');
await click(SELECTORS.modalToggle);
await click(SELECTORS.confirm);
assert.true(this.onConfirm.called, 'calls the action when Confirm is clicked');
assert.dom(SELECTORS.title).doesNotExist('modal closes after confirm is clicked');
});
test('it renders isInDropdown defaults and calls onConfirmAction', async function (assert) {
await render(hbs`
<ConfirmAction
@buttonText="DELETE"
@onConfirmAction={{this.onConfirm}}
@isInDropdown={{true}}
/>
`);
assert.dom(`li ${SELECTORS.modalToggle}`).exists('element renders inside <li>');
assert.dom(SELECTORS.modalToggle).hasClass('hds-confirm-action-critical', 'button has dropdown styling');
await click(SELECTORS.modalToggle);
assert.dom(SELECTORS.title).hasText('Are you sure?', 'renders default title');
assert
.dom('[data-test-confirm-action-title]')
.doesNotExist('it has closed the confirm content and does not show the title');
.dom(SELECTORS.message)
.hasText('You will not be able to recover it later.', 'renders default body text');
await click('[data-test-confirm-cancel-button]');
assert.false(this.onConfirm.called, 'does not call the action when Cancel is clicked');
await click(SELECTORS.modalToggle);
await click(SELECTORS.confirm);
assert.true(this.onConfirm.called, 'calls the action when Confirm is clicked');
assert.dom(SELECTORS.title).doesNotExist('modal closes after confirm is clicked');
});
test('it renders loading state', async function (assert) {
await render(hbs`
<ConfirmAction
@buttonText="Open!"
@onConfirmAction={{this.onConfirm}}
@isRunning={{true}}
/>
`);
await click(SELECTORS.modalToggle);
assert.dom(SELECTORS.confirm).isDisabled('disables confirm button when loading');
assert.dom('[data-test-confirm-button] [data-test-icon="loading"]').exists('it renders loading icon');
});
test('it renders disabledMessage modal', async function (assert) {
this.condition = true;
await render(hbs`
<ConfirmAction
@buttonText="Open!"
@onConfirmAction={{this.onConfirm}}
@confirmTitle="Do this?"
@confirmMessage="Are you really, really sure?"
@disabledMessage={{if this.condition "This is the reason you cannot do the thing"}}
/>
`);
await click(SELECTORS.modalToggle);
assert.strictEqual(
find('#confirm-action-modal').className,
'hds-modal hds-modal--size-small hds-modal--color-neutral',
'renders critical modal color by default'
);
assert.dom(SELECTORS.title).hasText('Not allowed', 'renders disabled title');
assert
.dom(SELECTORS.message)
.hasText('This is the reason you cannot do the thing', 'renders disabled message as body text');
assert.dom(SELECTORS.confirm).doesNotExist('does not render confirm action button');
assert.dom(SELECTORS.cancel).hasText('Close');
});
test('it renders passed args', async function (assert) {
this.condition = false;
await render(hbs`
<ConfirmAction
@buttonText="Open!"
@onConfirmAction={{this.onConfirm}}
@modalColor="warning"
@buttonColor="secondary"
@confirmTitle="Do this?"
@confirmMessage="Are you really, really sure?"
@disabledMessage={{if this.condition "This is the reason you cannot do the thing"}}
/>
`);
// hasClass assertion wasn't working so this is the workaround
assert.strictEqual(
find(SELECTORS.modalToggle).className,
'hds-button hds-button--size-medium hds-button--color-secondary',
'renders @buttonColor classes'
);
await click(SELECTORS.modalToggle);
assert.strictEqual(
find('#confirm-action-modal').className,
'hds-modal hds-modal--size-small hds-modal--color-warning',
'renders warning modal'
);
assert.strictEqual(
find(SELECTORS.confirm).className,
'hds-button hds-button--size-medium hds-button--color-primary',
'renders primary confirm button'
);
assert.dom(SELECTORS.title).hasText('Do this?', 'renders passed title');
assert.dom(SELECTORS.message).hasText('Are you really, really sure?', 'renders passed body text');
assert.dom(SELECTORS.confirm).hasText('Confirm');
});
});

View File

@ -1,107 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
module('Integration | Component | Confirm', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.set('id', 'foo');
this.set('title', 'Are you sure?');
this.set('message', 'You will not be able to recover this item later.');
this.set('triggerText', 'Click me!');
this.set('onConfirm', sinon.spy());
});
test('it renders', async function (assert) {
await render(hbs`
<Confirm as |c|>
<c.Message
@id={{this.id}}
@title={{this.title}}
@triggerText={{this.triggerText}}
@message={{this.message}}
@onConfirm={{this.onConfirm}}
/>
</Confirm>
`);
assert.dom('.confirm-wrapper').exists();
assert.dom('.confirm').containsText(this.triggerText);
});
test('does not show the confirmation message until it is triggered', async function (assert) {
await render(hbs`
<Confirm as |c|>
<c.Message
@id={{this.id}}
@title={{this.title}}
@triggerText={{this.triggerText}}
@message={{this.message}}
@onConfirm={{this.onConfirm}}
/>
</Confirm>
`);
assert.dom('.confirm-overlay').doesNotContainText(this.message);
await click('[data-test-confirm-action-trigger]');
assert.dom('.confirm-overlay').containsText(this.title);
assert.dom('.confirm-overlay').containsText(this.message);
});
test('it calls onConfirm when the confirm button is clicked', async function (assert) {
await render(hbs`
<Confirm as |c|>
<c.Message
@id={{this.id}}
@title={{this.title}}
@triggerText={{this.triggerText}}
@message={{this.message}}
@onConfirm={{this.onConfirm}}
/>
</Confirm>
`);
await click('[data-test-confirm-action-trigger]');
await click('[data-test-confirm-button=true]');
assert.ok(this.onConfirm.calledOnce);
});
test('it shows only the active triggers message', async function (assert) {
await render(hbs`
<Confirm as |c|>
<c.Message
@id={{this.id}}
@title={{this.title}}
@triggerText={{this.triggerText}}
@message={{this.message}}
@onConfirm={{this.onConfirm}}
/>
<c.Message
@id='bar'
@title='Wow'
@message='Bazinga!'
@onConfirm={{this.onConfirm}}
/>
</Confirm>
`);
await click(`[data-test-confirm-action-trigger=${this.id}]`);
assert.dom('.confirm-overlay').containsText(this.title);
assert.dom('.confirm-overlay').containsText(this.message);
await click('[data-test-confirm-cancel-button]');
await click("[data-test-confirm-action-trigger='bar']");
assert.dom('.confirm-overlay').containsText('Wow');
assert.dom('.confirm-overlay').containsText('Bazinga!');
});
});

View File

@ -8,7 +8,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { click, triggerEvent, settled, fillIn } from '@ember/test-helpers';
import { click, settled, fillIn } from '@ember/test-helpers';
const ts = 'data-test-kms-provider';
const root = {
@ -48,7 +48,7 @@ module('Integration | Component | keymgmt/provider-edit', function (hooks) {
});
test('it should render show view', async function (assert) {
assert.expect(16);
assert.expect(10);
// override capability getters
Object.defineProperties(this.model, {
@ -64,17 +64,6 @@ module('Integration | Component | keymgmt/provider-edit', function (hooks) {
},
};
});
this.server.delete('/keymgmt/kms/foo-bar', () => {
assert.ok(true, 'Request made to delete key');
return {};
});
this.owner.lookup('service:router').reopen({
transitionTo(path, model, { queryParams: { tab } }) {
assert.strictEqual(path, root.path, 'Root path sent in transitionTo on delete');
assert.strictEqual(model, root.model, 'Root model sent in transitionTo on delete');
assert.deepEqual(tab, 'provider', 'Correct query params sent in transitionTo on delete');
},
});
const changeTab = async (tab) => {
this.set('tab', tab);
@ -104,16 +93,58 @@ module('Integration | Component | keymgmt/provider-edit', function (hooks) {
assert.dom('[data-test-secret-link]').exists({ count: 2 }, 'Keys list renders');
await changeTab('details');
assert.dom(`[${ts}-delete] button`).isDisabled('Delete action disabled when keys exist');
await triggerEvent(`[data-test-tooltip-trigger]`, 'mouseenter');
assert.dom(`[${ts}-delete-tooltip]`).exists('Tooltip is show when delete action is disabled');
await click(`[${ts}-delete]`);
assert
.dom('[data-test-confirm-action-message]')
.hasText(
'This provider cannot be deleted until all 2 key(s) distributed to it are revoked. This can be done from the Keys tab.',
'Renders disabled message'
);
await click('[data-test-confirm-cancel-button]');
});
test('it should delete a provider', async function (assert) {
assert.expect(5);
// override capability getters
Object.defineProperties(this.model, {
canDelete: { value: true },
canListKeys: { value: true },
});
this.server.post('/sys/capabilities-self', () => ({}));
this.server.get('/keymgmt/kms/foo-bar/key', () => {
return {
data: {
keys: [],
},
};
});
this.server.delete('/keymgmt/kms/foo-bar', () => {
assert.ok(true, 'Request made to delete key');
return {};
});
this.owner.lookup('service:router').reopen({
transitionTo(path, model, { queryParams: { tab } }) {
assert.strictEqual(path, root.path, 'Root path sent in transitionTo on delete');
assert.strictEqual(model, root.model, 'Root model sent in transitionTo on delete');
assert.deepEqual(tab, 'provider', 'Correct query params sent in transitionTo on delete');
},
});
await render(hbs`
<Keymgmt::ProviderEdit
@root={{this.root}}
@model={{this.model}}
@mode="show"
@tab={{this.tab}}
/>`);
this.model.keys = [];
await settled();
assert
.dom('[data-test-value-div="Keys"]')
.hasText('None', 'None is displayed when no keys exist for provider');
await click(`[${ts}-delete] button`);
await click(`[${ts}-delete]`);
await click('[data-test-confirm-button]');
});

View File

@ -104,13 +104,13 @@ module('Integration | Component | kubernetes | Page::Role::Details', function (h
return;
});
assert.dom('[data-test-delete] button').hasText('Delete role', 'Delete action renders');
assert.dom('[data-test-delete]').hasText('Delete role', 'Delete action renders');
assert
.dom('[data-test-generate-credentials]')
.hasText('Generate credentials', 'Generate credentials action renders');
assert.dom('[data-test-edit]').hasText('Edit role', 'Edit action renders');
await click('[data-test-delete] button');
await click('[data-test-delete]');
await click('[data-test-confirm-button]');
assert.ok(
transitionStub.calledWith('vault.cluster.secrets.backend.kubernetes.roles'),

View File

@ -80,8 +80,8 @@ module('Integration | Component | kv | Page::List', function (hooks) {
const popupSelector = `${PAGE.list.item('my-secret-0')} ${PAGE.popup}`;
await click(popupSelector);
await click('[data-test-confirm-action-trigger="true"]');
await click('[data-test-confirm-button=true]');
await click('[data-test-confirm-action-trigger]');
await click('[data-test-confirm-button]');
assert.dom(PAGE.list.item('my-secret-0')).doesNotExist('deleted the first record from the list');
});
});

View File

@ -13,7 +13,7 @@ import { duration } from 'core/helpers/format-duration';
import { createSecretsEngine, generateBreadcrumbs } from 'vault/tests/helpers/ldap';
const selectors = {
rotateAction: '[data-test-toolbar-rotate-action] button',
rotateAction: '[data-test-toolbar-rotate-action]',
confirmRotate: '[data-test-confirm-button]',
configAction: '[data-test-toolbar-config-action]',
configCta: '[data-test-config-cta]',

View File

@ -65,11 +65,11 @@ module('Integration | Component | ldap | Page::Library::Details', function (hook
assert.dom('[data-test-tab="accounts"]').hasText('Accounts', 'Accounts tab renders');
assert.dom('[data-test-tab="config"]').hasText('Configuration', 'Configuration tab renders');
assert.dom('[data-test-delete] button').hasText('Delete library', 'Delete action renders');
assert.dom('[data-test-delete]').hasText('Delete library', 'Delete action renders');
assert.dom('[data-test-edit]').hasText('Edit library', 'Edit action renders');
const transitionStub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo');
await click('[data-test-delete] button');
await click('[data-test-delete]');
await click('[data-test-confirm-button]');
assert.ok(
transitionStub.calledWith('vault.cluster.secrets.backend.ldap.libraries'),

View File

@ -61,7 +61,7 @@ module('Integration | Component | ldap | Page::Role::Details', function (hooks)
await this.renderComponent('static');
assert.dom('[data-test-delete] button').hasText('Delete role', 'Delete action renders');
assert.dom('[data-test-delete]').hasText('Delete role', 'Delete action renders');
assert.dom('[data-test-get-credentials]').hasText('Get credentials', 'Get credentials action renders');
assert.dom('[data-test-rotate-credentials]').exists('Rotate credentials action renders for static role');
assert.dom('[data-test-edit]').hasText('Edit role', 'Edit action renders');
@ -78,7 +78,7 @@ module('Integration | Component | ldap | Page::Role::Details', function (hooks)
.dom('[data-test-rotate-credentials]')
.doesNotExist('Rotate credentials action is hidden for dynamic role');
await click('[data-test-delete] button');
await click('[data-test-delete]');
await click('[data-test-confirm-button]');
assert.ok(
transitionStub.calledWith('vault.cluster.secrets.backend.ldap.roles'),

View File

@ -24,7 +24,7 @@ module('Integration | Component | seal-action', function (hooks) {
await render(hbs`<SealAction @onSeal={{action this.handleSeal}} />`);
// attempt seal
await click('[data-test-seal] button');
await click('[data-test-seal]');
await click('[data-test-confirm-button]');
assert.ok(this.sealSuccess.calledOnce, 'called onSeal action');
@ -36,7 +36,7 @@ module('Integration | Component | seal-action', function (hooks) {
await render(hbs`<SealAction @onSeal={{action this.handleSeal}} />`);
// attempt seal
await click('[data-test-seal] button');
await click('[data-test-seal]');
await click('[data-test-confirm-button]');
assert.ok(this.sealError.calledOnce, 'called onSeal action');

View File

@ -16,10 +16,6 @@ export default create({
findPolicyByName(name) {
return this.policies.filterBy('name', name)[0];
},
delete: clickable('[data-test-confirm-action-trigger]', {
testContainer: '#ember-testing',
}),
confirmDelete: clickable('[data-test-confirm-button]', {
testContainer: '#ember-testing',
}),
delete: clickable('[data-test-confirm-action-trigger]'),
confirmDelete: clickable('[data-test-confirm-button]'),
});

View File

@ -6,6 +6,6 @@
import { clickable, create, isPresent, visitable } from 'ember-cli-page-object';
export default create({
visit: visitable('/vault/policy/:type/:name/edit'),
deleteIsPresent: isPresent('[data-test-policy-delete]'),
deleteIsPresent: isPresent('[data-test-confirm-action-trigger]'),
toggleEdit: clickable('[data-test-policy-edit-toggle]'),
});

View File

@ -11,7 +11,7 @@ export default create({
breadcrumbs: collection('[data-test-secret-breadcrumb]', {
text: text(),
}),
deleteBtnV1: clickable('[data-test-secret-v1-delete="true"] button'),
deleteBtnV1: clickable('[data-test-secret-v1-delete]'),
confirmBtn: clickable('[data-test-confirm-button]'),
rows: collection('data-test-row-label'),
edit: clickable('[data-test-secret-edit]'),

View File

@ -33,12 +33,8 @@ export default create({
menuItems: collection('.ember-basic-dropdown-content li', {
testContainer: '#ember-testing',
}),
delete: clickable('[data-test-confirm-action-trigger]', {
testContainer: '#ember-testing',
}),
confirmDelete: clickable('[data-test-confirm-button]', {
testContainer: '#ember-testing',
}),
delete: clickable('[data-test-confirm-action-trigger]'),
confirmDelete: clickable('[data-test-confirm-button]'),
backendIsEmpty: getter(function () {
return this.secrets.length === 0;
}),

View File

@ -16,11 +16,7 @@ export default create({
configLink: clickable('[data-test-engine-config]', {
testContainer: '#ember-testing',
}),
disableButton: clickable('[data-test-confirm-action-trigger]', {
testContainer: '#ember-testing',
}),
confirmDisable: clickable('[data-test-confirm-button]', {
testContainer: '#ember-testing',
}),
disableButton: clickable('[data-test-confirm-action-trigger]'),
confirmDisable: clickable('[data-test-confirm-button]'),
console: uiPanel,
});