UI: HDS adoption replace <Modal> (#23382)

* UI: Part 1 - hds adoption replace <Modal> (#23363)

* replace policy-form modal

* replace clients/attribution modal

* clients/config modal

* scope form odal

* remove button type

* include toolbar to match other example templates

* rotate credentials modal

* add toolbar button class for hds buttons

* transformation-edit modal

* add back test selector

* add route arg to button!

* update link status

* fix link-status tests

* remove prevent default

* update db tests

* update tests

* use page alert for hcp link status banner

* fix scopy button selector

* fix sidebar test

* change to neutral banner

* UI: Part 2 - hds adoption replace <Modal>  (#23398)

* upgrade HDS library (adds support for snippet containers

* cleanup flight icons

* replace transit key action modals

* re-add deps as devDeps

* remove line

* address transit tests

* UI: Part 3 - hds adoption replace <Modal> (#23415)

* cleanup css

* cleanup extra type attr

* masked input download modal

* use Hds::Button in  download button"

* fix size of modal

* tiny icon fix

* refactor download button to always render download icon

* update tests

* UI: Part 3.5 - hds adoption replace <Modal> (#23448)

* replication-promote modal

* replication component modals

* replication add secondary modal

* move update text for diff

* UI: Part 4 - hds adoption replace <Modal>  (#23451)

* k8 configure modal

* kv delete modal

* ldap modals

* pki modals

* add trash icon

* move deps

* UI: Part 5 - hds adoption replace <Modal> (#23471)

* replace confirmation modals
---------

* UI: Part 6 - hds adoption replace <Modal>  (#23484)

* search select with modal

* policy search select modal

* replace date dropdown for client dashboard

* change padding to top

* update policy example args

* lolllll test typo wow

* update dropdown tests

* shamir flow modals!

* add one more container

* update test selectors

* UI: Final hds adoption replace <Modal> cleanup PR (#23522)

* search select with modal

* policy search select modal

* replace date dropdown for client dashboard

* change padding to top

* update policy example args

* lolllll test typo wow

* update dropdown tests

* shamir flow modals!

* add one more container

* update test selectors

* remove wormhole and modal component

* fix selectors

* uninstall wormhole

* remove shamir-modal-flow class

* fix confirm modal test

* fix pki and kv test

* fix toolbar selector kv

* client and download button test

* fix-confirmation-modal-padding

* fix replication modal tests so relevant modal opens (#23540)

* more confirmation modal tests

* adds changelog
This commit is contained in:
claire bontempo
2023-10-06 15:06:36 -07:00
committed by GitHub
parent 92fcfda8ad
commit 43258c28fa
120 changed files with 1555 additions and 2174 deletions

3
changelog/23382.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: Makes modals accessible by implementing Helios Design System modal component
```

View File

@@ -73,15 +73,15 @@ export default class DatabaseConnectionEdit extends Component {
} }
@action @action
continueWithoutRotate(evt) { continueWithoutRotate() {
evt.preventDefault(); this.showSaveModal = false;
const { name } = this.args.model; const { name } = this.args.model;
this.transitionToRoute(SHOW_ROUTE, name); this.transitionToRoute(SHOW_ROUTE, name);
} }
@action @action
continueWithRotate(evt) { continueWithRotate() {
evt.preventDefault(); this.showSaveModal = false;
const { backend, name } = this.args.model; const { backend, name } = this.args.model;
this.rotateCredentials(backend, name) this.rotateCredentials(backend, name)
.then(() => { .then(() => {

View File

@@ -15,10 +15,9 @@ import timestamp from 'core/utils/timestamp';
* *
* @example * @example
* ```js * ```js
* <DateDropdown @handleSubmit={{this.actionFromParent}} @name="startTime" @submitText="Save" @handleCancel={{this.onCancel}}/> * <DateDropdown @handleSubmit={{this.actionFromParent}} @name="startTime" @submitText="Save" />
* ``` * ```
* @param {function} handleSubmit - callback function from parent that the date picker triggers on submit * @param {function} handleSubmit - callback function from parent that the date picker triggers on submit
* @param {function} [handleCancel] - optional callback for cancel action, if exists then buttons appear modal style with a light gray background
* @param {string} [dateType] - optional argument to give the selected month/year a type * @param {string} [dateType] - optional argument to give the selected month/year a type
* @param {string} [submitText] - optional argument to change submit button text * @param {string} [submitText] - optional argument to change submit button text
* @param {function} [validateDate] - parent function to validate date selection, receives date object and returns an error message that's passed to the inline alert * @param {function} [validateDate] - parent function to validate date selection, receives date object and returns an error message that's passed to the inline alert
@@ -69,12 +68,6 @@ export default class DateDropdown extends Component {
this.resetDropdown(); this.resetDropdown();
} }
@action
handleCancel() {
this.args.handleCancel();
this.resetDropdown();
}
resetDropdown() { resetDropdown() {
this.maxMonthIdx = 11; this.maxMonthIdx = 11;
this.disabledYear = null; this.disabledYear = null;

View File

@@ -32,7 +32,7 @@
</nav> </nav>
{{/if}} {{/if}}
{{#if this.showExamplePolicy}} {{#if this.showExamplePolicy}}
<PolicyExample @policyType={{this.policy.policyType}} /> <PolicyExample @policyType={{this.policy.policyType}} @container="#search-select-modal" />
{{else}} {{else}}
<Select <Select
@name="policyType" @name="policyType"

View File

@@ -23,11 +23,24 @@
</div> </div>
{{/if}} {{/if}}
<div class="field"> <div class="field">
{{#if @model.isNew}}
<Toolbar> <Toolbar>
<label class="has-text-weight-bold">Policy</label> <label class="has-text-weight-bold has-right-margin-xxs">Policy</label>
{{#if @renderPolicyExampleModal}}
{{! only true in policy create and edit routes }}
<ToolbarFilters>
<Hds::Button
@text="How to write a policy"
@icon="bulb"
@size="small"
@color="tertiary"
{{on "click" (fn (mut this.showTemplateModal))}}
data-test-policy-example-button
/>
</ToolbarFilters>
{{/if}}
<ToolbarActions> <ToolbarActions>
<div class="toolbar-separator"></div> <div class="toolbar-separator"></div>
{{#if @model.isNew}}
<div class="control is-flex"> <div class="control is-flex">
<Input <Input
id="fileUploadToggle" id="fileUploadToggle"
@@ -40,6 +53,16 @@
/> />
<label for="fileUploadToggle">Upload file</label> <label for="fileUploadToggle">Upload file</label>
</div> </div>
{{else}}
{{! EDITING - no file upload toggle}}
<Hds::Copy::Button
@text="Copy"
@isIconOnly={{true}}
@textToCopy={{@model.policy}}
class="transparent"
data-test-copy-button
/>
{{/if}}
</ToolbarActions> </ToolbarActions>
</Toolbar> </Toolbar>
{{#if this.showFileUpload}} {{#if this.showFileUpload}}
@@ -55,51 +78,10 @@
data-test-policy-editor data-test-policy-editor
/> />
{{/if}} {{/if}}
{{else}}
{{! EDITING - no file upload toggle}}
<JsonEditor
@title="Policy"
@value={{@model.policy}}
@valueUpdated={{action (mut @model.policy)}}
@mode="ruby"
@extraKeys={{hash Shift-Enter=(perform this.save)}}
data-test-policy-editor
/>
{{/if}}
<div class="has-top-margin-xs"> <div class="has-top-margin-xs">
<span class="is-size-9 has-text-grey has-bottom-margin-l"> <span class="is-size-9 has-text-grey has-bottom-margin-l">
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field. You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field.
</span> </span>
{{! Only renders button (and modal) if not already in the "create policy" modal }}
{{#if @renderPolicyExampleModal}}
<span class="is-size-9 has-text-grey has-bottom-margin-l">
See
<button
type="button"
class="text-button has-text-info"
{{on "click" (fn (mut this.showTemplateModal))}}
data-test-policy-example-button
>
example template
</button>.
</span>
{{! Only renders more information if already in the "create policy" modal }}
{{else}}
<p class="has-top-margin-l">
More information about
{{uppercase @model.policyType}}
policies can be found
<DocLink
@path={{if
(eq @model.policyType "acl")
"/vault/docs/concepts/policies#capabilities"
"/vault/tutorials/policies/sentinel#role-governing-policies-rgps"
}}
>
here.
</DocLink>
</p>
{{/if}}
</div> </div>
</div> </div>
{{#each @model.additionalAttrs as |attr|}} {{#each @model.additionalAttrs as |attr|}}
@@ -128,26 +110,26 @@
</div> </div>
</div> </div>
</form> </form>
{{! SAMPLE POLICY MODAL. Only renders modal if not already in create policy modal }}
{{#if @renderPolicyExampleModal}} {{! SAMPLE POLICY MODAL. Only renders in policy create and edit routes }}
<Modal {{#if this.showTemplateModal}}
@title="Example {{uppercase @model.policyType}} Policy" <Hds::Modal
id="policy-example-modal"
@size="large"
@onClose={{fn (mut this.showTemplateModal) false}} @onClose={{fn (mut this.showTemplateModal) false}}
@isActive={{this.showTemplateModal}}
@showCloseButton={{true}}
data-test-policy-example-modal data-test-policy-example-modal
as |M|
> >
<section class="modal-card-body"> <M.Header data-test-modal-title>
{{! code-mirror modifier does not render value initially until focus event fires }} Example
{{! wait until the Modal is rendered and then show the PolicyExample (contains JsonEditor) }} {{uppercase @model.policyType}}
{{#if this.showTemplateModal}} Policy
<PolicyExample @policyType={{@model.policyType}} /> </M.Header>
{{/if}} <M.Body>
</section> <PolicyExample @policyType={{@model.policyType}} @container="#policy-example-modal" />
<div class="modal-card-head has-border-top-light"> </M.Body>
<button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal> <M.Footer as |F|>
Close <Hds::Button @text="Close" {{on "click" F.close}} data-test-modal-close-button />
</button> </M.Footer>
</div> </Hds::Modal>
</Modal>
{{/if}} {{/if}}

View File

@@ -46,7 +46,6 @@
</Frame.Sidebar> </Frame.Sidebar>
<Frame.Main id="app-main-content" class={{if this.console.isOpen "main--console-open"}}> <Frame.Main id="app-main-content" class={{if this.console.isOpen "main--console-open"}}>
{{! outlet for app content }} {{! outlet for app content }}
<div id="modal-wormhole"></div>
<LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} /> <LinkStatus @status={{this.currentCluster.cluster.hcpLinkStatus}} />
{{yield}} {{yield}}
<div data-test-console-panel class={{if this.console.isOpen "panel-open"}}> <div data-test-console-panel class={{if this.console.isOpen "panel-open"}}>

View File

@@ -48,25 +48,19 @@
background-color: transparent; background-color: transparent;
} }
.button.masked-input-toggle, .button.masked-input-toggle {
.button.download-button {
min-width: $spacing-xl; min-width: $spacing-xl;
border-left: 0; border-left: 0;
color: $grey; color: $grey;
box-shadow: 0 3px 1px 0px rgba(10, 10, 10, 0.12); box-shadow: 0 3px 1px 0px rgba(10, 10, 10, 0.12);
} }
.button.download-button {
border-radius: 0;
}
.button.masked-input-toggle { .button.masked-input-toggle {
border-radius: 0 $radius $radius 0; border-radius: 0 $radius $radius 0;
} }
.display-only { .display-only {
.button.masked-input-toggle, .button.masked-input-toggle {
.button.download-button {
background: transparent; background: transparent;
height: auto; height: auto;
line-height: 1rem; line-height: 1rem;

View File

@@ -1,157 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
.modal {
align-items: center;
bottom: 0;
left: 0;
right: 0;
top: 0;
display: none;
justify-content: center;
overflow: hidden;
position: fixed;
z-index: 20;
&.is-active {
display: flex;
}
}
.modal-background {
background: $ui-gray-100;
bottom: 0;
left: 0;
opacity: 0.9;
position: absolute;
right: 0;
top: 0;
}
.modal-card {
box-shadow: $box-shadow-highest;
border: 1px solid $grey-light;
display: flex;
flex-direction: column;
max-height: calc(100vh - 70px);
margin-top: 60px;
min-width: calc(100vw * 0.3);
overflow: hidden;
position: relative;
&-head {
border-radius: 0;
background-color: $grey-lightest;
border-bottom: 1px solid $grey-light;
display: flex;
justify-content: flex-start;
padding: 20px;
}
&-foot {
border-radius: 0;
border: 0;
background-color: $white;
padding: 20px;
.button:not(:last-child) {
margin-right: 10px;
}
}
&-title.title {
margin: 0;
flex-grow: 1;
}
.copy-text {
background-color: $grey-lightest;
padding: $spacing-s;
margin-bottom: $spacing-s;
code {
overflow: scroll;
max-width: calc(100% - 36px);
background-color: inherit;
}
}
.copy-close {
margin-top: $spacing-s;
}
}
.modal-card-title.title {
display: flex;
align-items: center;
}
.modal-card-body {
background-color: $white;
flex-grow: 1;
flex-shrink: 1;
overflow: auto;
padding: 20px;
}
pre {
background-color: inherit;
}
.is-highlight {
.modal-card-head {
background: $yellow-010;
border: 1px solid $yellow-100;
}
.modal-card-title {
color: $yellow-dark;
}
}
.is-danger {
.modal-card-head {
background: $red-010;
border: 1px solid $red-100;
}
.modal-card-title {
color: $red-dark;
}
}
.modal-confirm-section {
margin: $spacing-xl 0 $spacing-m;
}
.modal-card-foot-outlined {
background: $ui-gray-050;
border-top: $base-border;
}
// customize spacing (.modal-card-body is restricted to padding: 20px)
.modal-card-custom {
background-color: white;
flex-grow: 1;
flex-shrink: 1;
overflow: auto;
&.has-padding-m {
padding: $spacing-m;
}
&.has-padding-btm-left {
padding-bottom: $spacing-m;
padding-left: $spacing-m;
}
}
// responsive css
@media screen and (min-width: 769px), print {
.modal-card,
.modal-content {
margin: 0 auto;
max-height: calc(100vh - 40px);
width: 640px;
}
}

View File

@@ -1,9 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
.field-title {
font-weight: 700;
font-size: $size-7;
}

View File

@@ -24,29 +24,3 @@
color: $black; color: $black;
} }
} }
.link-status {
height: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: $size-7;
font-weight: $font-weight-semibold;
&.connected {
background-color: var(--token-color-surface-action);
color: var(--token-color-foreground-action-active);
a {
color: var(--token-color-foreground-action-active);
}
}
&.warning {
background-color: var(--token-color-surface-warning);
color: var(--token-color-palette-amber-300);
a {
color: var(--token-color-palette-amber-300);
}
}
}

View File

@@ -65,6 +65,10 @@
display: flex; display: flex;
flex: 1; flex: 1;
white-space: nowrap; white-space: nowrap;
&:has(.hds-modal) {
// toolbar buttons that open/close a modal pass attrs to the modal content
white-space: wrap;
}
} }
.toolbar-filters + .toolbar-actions { .toolbar-filters + .toolbar-actions {

View File

@@ -80,7 +80,6 @@
@import './components/loader'; @import './components/loader';
@import './components/login-form'; @import './components/login-form';
@import './components/masked-input'; @import './components/masked-input';
@import './components/modal-component.scss';
@import './components/namespace-picker'; @import './components/namespace-picker';
@import './components/namespace-reminder'; @import './components/namespace-reminder';
@import './components/navigate-input'; @import './components/navigate-input';
@@ -104,7 +103,6 @@
@import './components/secrets-engines-card'; @import './components/secrets-engines-card';
// action-block extends selectable-card // action-block extends selectable-card
@import './components/action-block'; @import './components/action-block';
@import './components/shamir-modal-flow';
@import './components/shamir-progress'; @import './components/shamir-progress';
@import './components/sidebar'; @import './components/sidebar';
@import './components/splash-page'; @import './components/splash-page';

View File

@@ -91,12 +91,6 @@
color: $red-500; color: $red-500;
} }
&.is-warning-outlined {
background-color: $yellow-010;
border: 1px solid $yellow-700;
color: $yellow-700;
}
&.is-flat { &.is-flat {
min-width: auto; min-width: auto;
border: none; border: none;
@@ -332,11 +326,11 @@ a.button.disabled {
} }
} }
// TODO HDS adoption cleanup: audit styles with design and see what to keep/remove once buttons are fully HDS
// Existing class on <Hds::Copy::Button> component, modifying to match existing UI Structure buttons // Existing class on <Hds::Copy::Button> component, modifying to match existing UI Structure buttons
.hds-copy-button { .hds-copy-button {
font-weight: $font-weight-semibold; font-weight: $font-weight-semibold; // TODO delete
box-shadow: $box-shadow-low; box-shadow: $box-shadow-low; // TODO delete
border-radius: $radius;
&.white-icon svg { &.white-icon svg {
color: $white; color: $white;
@@ -350,15 +344,14 @@ a.button.disabled {
color: $ui-gray-500; color: $ui-gray-500;
} }
&.icon-only {
margin-right: $spacing-xxs;
margin-left: $spacing-xxs;
}
&.transparent { &.transparent {
background: none; background: none;
border: none;
box-shadow: none; box-shadow: none;
border: 1px solid transparent;
&:hover {
border: 1px solid $grey-light;
border-color: var(--token-color-border-strong);
}
} }
&.primary { &.primary {
@@ -386,3 +379,20 @@ a.button.disabled {
} }
} }
} }
// Existing class on <Hds::Button> component, modifying to match existing UI Structure buttons
.hds-button {
font-weight: $font-weight-semibold; // TODO consult design on font weight after button class audit
// for toolbar-button must pass arg @color="secondary"
&.toolbar-button {
color: $black;
background: none;
border: none;
box-shadow: none;
&:hover:not(.disabled) {
background-color: $ui-gray-100;
border: 0;
color: $blue;
}
}
}

View File

@@ -170,3 +170,27 @@ form {
label { label {
cursor: pointer; cursor: pointer;
} }
// HDS modifications and overrides
// * ONLY for universal changes (i.e. to address component functionality)
// <Hds::Modal>
.hds-modal {
&:has(.hds-dropdown) {
overflow: unset;
}
}
.hds-modal__body {
&:has(.hds-dropdown) {
overflow: unset;
}
}
// <Hds::Dropdown>
.hds-dropdown-list-item {
> button:disabled {
color: $black;
opacity: 0.5;
cursor: not-allowed;
}
}

View File

@@ -15,14 +15,12 @@
</div> </div>
<div class="header-right"> <div class="header-right">
{{#if this.hasCsvData}} {{#if this.hasCsvData}}
<button <Hds::Button
data-test-attribution-export-button data-test-attribution-export-button
type="button" @text="Export attribution data"
class="button is-secondary" @color="secondary"
{{on "click" (fn (mut this.showCSVDownloadModal) true)}} {{on "click" (fn (mut this.showCSVDownloadModal) true)}}
> />
Export attribution data
</button>
{{/if}} {{/if}}
</div> </div>
</div> </div>
@@ -91,32 +89,31 @@
</div> </div>
{{! MODAL FOR CSV DOWNLOAD }} {{! MODAL FOR CSV DOWNLOAD }}
<Modal {{#if this.showCSVDownloadModal}}
@title="Export attribution data" <Hds::Modal id="attribution-csv-download-modal" @onClose={{fn (mut this.showCSVDownloadModal) false}} as |M|>
@type="info" <M.Header @icon="info" data-test-export-modal-title>
@isActive={{this.showCSVDownloadModal}} Export attribution data
@showCloseButton={{true}} </M.Header>
@onClose={{action (mut this.showCSVDownloadModal) false}} <M.Body>
>
<section class="modal-card-body">
<p class="has-bottom-margin-s"> <p class="has-bottom-margin-s">
This export will include the namespace path, authentication method path, and the associated total, entity, and This export will include the namespace path, authentication method path, and the associated total, entity, and
non-entity clients for the below non-entity clients for the below
{{if this.formattedEndDate "date range" "month"}}. {{if this.formattedEndDate "date range" "month"}}.
</p> </p>
<p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</p> <p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</p>
<p class="has-bottom-margin-s">{{this.formattedStartDate}} {{if this.formattedEndDate "-"}} {{this.formattedEndDate}}</p> <p class="has-bottom-margin-s" data-test-export-date-range>
</section> {{this.formattedStartDate}}
<footer class="modal-card-foot modal-card-foot-outlined"> {{if this.formattedEndDate "-"}}
<button type="button" class="button is-primary" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}}> {{this.formattedEndDate}}</p>
Export </M.Body>
</button> <M.Footer as |F|>
<button type="button" class="button is-secondary" onclick={{action (mut this.showCSVDownloadModal) false}}> <Hds::ButtonSet>
Cancel <Hds::Button @text="Export" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}} />
</button> <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet>
{{#if @upgradeExplanation}} {{#if @upgradeExplanation}}
<div class="has-text-grey is-size-8"> <div class="has-text-grey is-size-8 has-top-padding-m">
<AlertInline @type="warning" @isMarginless={{true}}> <AlertInline @type="warning">
Your data contains an upgrade. Your data contains an upgrade.
<DocLink <DocLink
@path="/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts" @path="/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts"
@@ -127,5 +124,6 @@
<p class="has-left-padding-l">{{@upgradeExplanation}}</p> <p class="has-left-padding-l">{{@upgradeExplanation}}</p>
</div> </div>
{{/if}} {{/if}}
</footer> </M.Footer>
</Modal> </Hds::Modal>
{{/if}}

View File

@@ -47,19 +47,17 @@
</div> </div>
</form> </form>
<Modal {{#if this.modalOpen}}
@title={{this.modalTitle}} <Hds::Modal id="clients-config-modal" @color="warning" @onClose={{fn (mut this.modalOpen) false}} as |M|>
@onClose={{action (mut this.modalOpen) false}} <M.Header @icon="alert-triangle" data-test-clients-config-modal="title">
@isActive={{this.modalOpen}} {{this.modalTitle}}
@type="warning" </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
{{#if (eq @model.enabled "On")}} {{#if (eq @model.enabled "On")}}
<p class="has-bottom-margin-s" data-test-clients-config-modal="on"> <p class="has-bottom-margin-s" data-test-clients-config-modal="on">
Vault will start tracking data starting from todays date, Vault will start tracking data starting from todays date,
{{date-format (now) "MMMM d, yyyy"}}. If youve previously enabled usage tracking, that historical data will still {{date-format (now) "MMMM d, yyyy"}}. If youve previously enabled usage tracking, that historical data will
be available to you. still be available to you.
</p> </p>
{{else}} {{else}}
<p class="has-bottom-margin-s" data-test-clients-config-modal="off"> <p class="has-bottom-margin-s" data-test-clients-config-modal="off">
@@ -68,26 +66,15 @@
</p> </p>
<p>Are you sure?</p> <p>Are you sure?</p>
{{/if}} {{/if}}
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::ButtonSet>
type="button" <Hds::Button @text="Continue" {{on "click" (perform this.save)}} data-test-clients-config-modal="continue" />
class="button is-primary" <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-clients-config-modal="cancel" />
data-test-clients-config-modal="continue" </Hds::ButtonSet>
{{on "click" (perform this.save)}} </M.Footer>
> </Hds::Modal>
Continue {{/if}}
</button>
<button
type="button"
class="button is-secondary"
{{on "click" (fn (mut this.modalOpen) false)}}
data-test-clients-config-modal="cancel"
>
Cancel
</button>
</footer>
</Modal>
{{else}} {{else}}
<div class="tabs-container box is-bottomless is-marginless is-fullwidth is-paddingless" data-test-clients-config-table> <div class="tabs-container box is-bottomless is-marginless is-fullwidth is-paddingless" data-test-clients-config-table>
{{#each this.infoRows as |item|}} {{#each this.infoRows as |item|}}

View File

@@ -175,26 +175,28 @@
</EmptyState> </EmptyState>
{{/if}} {{/if}}
{{/if}} {{/if}}
</div>
{{! BILLING START DATE MODAL }} {{! BILLING START DATE MODAL }}
{{#if this.showBillingStartModal}}
<Modal <Hds::Modal id="clients-edit-date-modal" @onClose={{fn (mut this.showBillingStartModal) false}} as |M|>
@title="Edit start month" <M.Header>
@onClose={{action (mut this.showBillingStartModal) false}} Edit start month
@isActive={{this.showBillingStartModal}} </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-custom has-padding-m">
<p class="has-bottom-margin-s"> <p class="has-bottom-margin-s">
{{this.versionText.description}} {{this.versionText.description}}
</p> </p>
<p><strong>{{this.versionText.label}}</strong></p> <p><strong>{{this.versionText.label}}</strong></p>
</section>
<DateDropdown <DateDropdown
class="has-top-padding-s"
@handleSubmit={{this.handleClientActivityQuery}} @handleSubmit={{this.handleClientActivityQuery}}
@dateType="startDate" @dateType="startDate"
@submitText="Save" @submitText="Save"
@handleCancel={{fn this.handleClientActivityQuery (hash dateType="cancel")}}
/> />
</Modal> </M.Body>
</div> <M.Footer as |F|>
<Hds::Button data-test-date-dropdown-cancel @text="Cancel" @color="secondary" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -352,35 +352,32 @@
{{/each}} {{/each}}
{{/if}} {{/if}}
<Modal {{#if this.showSaveModal}}
@title="Rotate your root credentials?" <Hds::Modal id="rotate-credentials-modal" @onClose={{this.continueWithoutRotate}} as |M|>
@onClose={{this.continueWithoutRotate}} <M.Header @icon="info" data-test-db-connection-modal-title>
@isActive={{this.showSaveModal}} Rotate your root credentials?
@type="info" </M.Header>
@showCloseButton={{false}} <M.Body>
>
<section class="modal-card-body">
<p class="has-bottom-margin-s"> <p class="has-bottom-margin-s">
Its best practice to rotate the root credential immediately after the initial configuration of each database. Once Its best practice to rotate the root credential immediately after the initial configuration of each database. Once
rotated, rotated,
<strong>only Vault knows the new root password</strong>. <strong>only Vault knows the new root password</strong>.
</p> </p>
<p>Would you like to rotate your new credentials? You can also do this later.</p> <p>Would you like to rotate your new credentials? You can also do this later.</p>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer>
<button <Hds::ButtonSet>
type="button" <Hds::Button @text="Rotate and enable" {{on "click" this.continueWithRotate}} data-test-enable-rotate-connection />
class="button is-primary" <Hds::Button
{{on "click" this.continueWithRotate}} @text="Enable without rotating"
data-test-enable-rotate-connection @color="secondary"
> {{on "click" this.continueWithoutRotate}}
Rotate and enable data-test-enable-connection
</button> />
<button type="button" class="button is-secondary" {{on "click" this.continueWithoutRotate}} data-test-enable-connection> </Hds::ButtonSet>
Enable without rotating </M.Footer>
</button> </Hds::Modal>
</footer> {{/if}}
</Modal>
<ConfirmationModal <ConfirmationModal
@title="Delete connection?" @title="Delete connection?"

View File

@@ -3,89 +3,36 @@
SPDX-License-Identifier: BUSL-1.1 SPDX-License-Identifier: BUSL-1.1
~}} ~}}
<div class="modal-card-custom has-padding-btm-left"> <Hds::SegmentedGroup ...attributes as |S|>
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|> <S.Dropdown @height="200px" as |dd|>
<D.Trigger <dd.ToggleButton data-test-toggle-month @text={{or this.selectedMonth.name "Month"}} @color="secondary" />
data-test-popup-menu-trigger="month"
class={{concat "toolbar-link" (if D.isOpen " is-active")}}
@htmlTag="button"
>
{{or this.selectedMonth.name "Month"}}
<Chevron @direction="down" @isButton={{true}} />
</D.Trigger>
<D.Content @defaultClass="popup-menu-content is-wide">
<nav class="box menu scroll" aria-label="months">
<ul data-test-month-list class="menu-list">
{{#each this.dropdownMonths as |month|}} {{#each this.dropdownMonths as |month|}}
<button <dd.Interactive
data-test-dropdown-month={{month.name}} data-test-dropdown-month={{month.name}}
type="button"
class="button link"
disabled={{if (gt month.index this.maxMonthIdx) true false}} disabled={{if (gt month.index this.maxMonthIdx) true false}}
{{on "click" (fn this.selectMonth month D.actions)}} {{on "click" (fn this.selectMonth month dd)}}
> @text={{month.name}}
{{month.name}} />
</button>
{{/each}} {{/each}}
</ul> </S.Dropdown>
</nav> <S.Dropdown data-test-year-list @height="200px" as |dd|>
</D.Content> <dd.ToggleButton data-test-toggle-year @text={{or this.selectedYear "Year"}} @color="secondary" />
</BasicDropdown>
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|>
<D.Trigger
data-test-popup-menu-trigger="year"
class={{concat "toolbar-link" (if D.isOpen " is-active")}}
@htmlTag="button"
>
{{or this.selectedYear "Year"}}
<Chevron @direction="down" @isButton={{true}} />
</D.Trigger>
<D.Content @defaultClass="popup-menu-content is-wide">
<nav class="box menu" aria-label="years">
<ul data-test-year-list class="menu-list">
{{#each this.dropdownYears as |year|}} {{#each this.dropdownYears as |year|}}
<button <dd.Interactive
data-test-dropdown-year={{year}} data-test-dropdown-year={{year}}
type="button"
class="button link"
disabled={{if (eq year this.disabledYear) true false}} disabled={{if (eq year this.disabledYear) true false}}
{{on "click" (fn this.selectYear year D.actions)}} {{on "click" (fn this.selectYear year dd)}}
> @text={{year}}
{{year}} />
</button>
{{/each}} {{/each}}
</ul> </S.Dropdown>
</nav> <S.Button
</D.Content>
</BasicDropdown>
{{#unless @handleCancel}}
<button
data-test-date-dropdown-submit data-test-date-dropdown-submit
type="button"
class="button is-primary"
disabled={{if (and this.selectedMonth this.selectedYear) false true}} disabled={{if (and this.selectedMonth this.selectedYear) false true}}
{{on "click" this.handleSubmit}} {{on "click" this.handleSubmit}}
> @text={{or @submitText "Submit"}}
{{or @submitText "Submit"}} />
</button> </Hds::SegmentedGroup>
{{/unless}} {{#if this.invalidDate}}
{{#if this.invalidDate}}
<AlertInline @type="danger" @message={{this.invalidDate}} @paddingTop={{true}} @mimicRefresh={{true}} /> <AlertInline @type="danger" @message={{this.invalidDate}} @paddingTop={{true}} @mimicRefresh={{true}} />
{{/if}}
</div>
{{#if @handleCancel}}
<footer class="modal-card-foot modal-card-foot-outlined">
<button
data-test-date-dropdown-submit
type="button"
class="button is-primary"
disabled={{if (and this.selectedMonth this.selectedYear) false true}}
{{on "click" this.handleSubmit}}
>
{{or @submitText "Submit"}}
</button>
<button data-test-date-dropdown-cancel type="button" class="button is-secondary" {{on "click" this.handleCancel}}>
Cancel
</button>
</footer>
{{/if}} {{/if}}

View File

@@ -4,9 +4,8 @@
~}} ~}}
{{#if (and this.state this.version.isEnterprise)}} {{#if (and this.state this.version.isEnterprise)}}
<div class="link-status {{if (eq this.state 'connected') 'connected' 'warning'}}"> <Hds::Alert @type="page" @color={{if (eq this.state "connected") "neutral" "warning"}} as |A|>
<Icon @name="info" /> <A.Title data-test-link-status>
<p data-test-link-status>
{{#if (eq this.state "connected")}} {{#if (eq this.state "connected")}}
This self-managed Vault is linked to This self-managed Vault is linked to
<ExternalLink @href="https://portal.cloud.hashicorp.com/sign-in"> <ExternalLink @href="https://portal.cloud.hashicorp.com/sign-in">
@@ -19,18 +18,16 @@
</button> </button>
for more information. for more information.
{{/if}} {{/if}}
</p> </A.Title>
</div> </Hds::Alert>
{{/if}} {{/if}}
<Modal {{#if this.showModal}}
@title="HCP Link error" <Hds::Modal id="hcp-link-error-modal" @color="warning" @size="small" @onClose={{fn (mut this.showModal) false}} as |M|>
@onClose={{fn (mut this.showModal) false}} <M.Header @icon="alert-triangle">
@isActive={{this.showModal}} HCP Link error
@type="info" </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
<div> <div>
<p class="has-text-weight-bold">Timestamp</p> <p class="has-text-weight-bold">Timestamp</p>
<p data-test-link-status-timestamp> <p data-test-link-status-timestamp>
@@ -53,15 +50,9 @@
<p class="has-text-weight-bold">Additional information</p> <p class="has-text-weight-bold">Additional information</p>
<p>Check the logs for more information</p> <p>Check the logs for more information</p>
</div> </div>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::Button @text="Close" {{on "click" F.close}} data-test-link-status-close />
type="button" </M.Footer>
class="button is-primary" </Hds::Modal>
{{on "click" (fn (mut this.showModal) false)}} {{/if}}
data-test-link-status-close
>
Close
</button>
</footer>
</Modal>

View File

@@ -23,6 +23,7 @@
@id="entities" @id="entities"
@label="Entities" @label="Entities"
@placeholder="Search" @placeholder="Search"
@renderInPlace={{true}}
@models={{array "identity/entity"}} @models={{array "identity/entity"}}
@inputValue={{@model.entityIds}} @inputValue={{@model.entityIds}}
@shouldRenderName={{true}} @shouldRenderName={{true}}
@@ -35,6 +36,7 @@
@id="groups" @id="groups"
@label="Groups" @label="Groups"
@placeholder="Search" @placeholder="Search"
@renderInPlace={{true}}
@models={{array "identity/group"}} @models={{array "identity/group"}}
@inputValue={{@model.groupIds}} @inputValue={{@model.groupIds}}
@shouldRenderName={{true}} @shouldRenderName={{true}}

View File

@@ -29,6 +29,15 @@
Scope Scope
</h1> </h1>
</p.levelLeft> </p.levelLeft>
<p.levelRight>
<Hds::Button
@text="How to write JSON template for scopes"
@icon="bulb"
@color="tertiary"
{{on "click" (fn (mut this.showTemplateModal))}}
data-test-oidc-scope-example
/>
</p.levelRight>
</PageHeader> </PageHeader>
<form {{on "submit" (perform this.save)}}> <form {{on "submit" (perform this.save)}}>
@@ -41,15 +50,8 @@
<FormField @attr={{field}} @model={{@model}} @modelValidations={{this.modelValidations}} /> <FormField @attr={{field}} @model={{@model}} @modelValidations={{this.modelValidations}} />
{{/each}} {{/each}}
<p class="is-size-9 has-text-grey has-bottom-margin-l"> <p class="is-size-9 has-text-grey has-bottom-margin-l">
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field. See You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field. Click 'How to write JSON
<button template for scopes' to view an example.
type="button"
class="text-button has-text-info"
{{on "click" (fn (mut this.showTemplateModal))}}
data-test-oidc-scope-example
>
example template
</button>.
</p> </p>
</div> </div>
<div class="has-top-margin-l has-bottom-margin-l"> <div class="has-top-margin-l has-bottom-margin-l">
@@ -78,40 +80,25 @@
{{/if}} {{/if}}
</form> </form>
<Modal {{#if this.showTemplateModal}}
@title="Scope template" <Hds::Modal id="scope-template-modal" @size="large" @onClose={{fn (mut this.showTemplateModal) false}} as |M|>
@onClose={{fn (mut this.showTemplateModal) false}} <M.Header data-test-scope-modal="title">
@isActive={{this.showTemplateModal}} Scope template
@showCloseButton={{true}} </M.Header>
> <M.Body>
<section class="modal-card-body"> <p data-test-scope-modal="text">
<div class="is-flex-between is-flex-center has-bottom-margin-s">
<p data-test-modal-text>
Example of a JSON template for scopes: Example of a JSON template for scopes:
</p> </p>
<Hds::Copy::Button <JsonEditor @value={{this.exampleTemplate}} @mode="ruby" @readOnly={{true}} @container=".hds-modal" />
@text="Copy"
@isIconOnly={{true}}
@textToCopy={{this.exampleTemplate}}
class="transparent"
data-test-copy-button
/>
</div>
{{! code-mirror modifier does not render value initially in wormhole until focus event fires }}
{{! wait until the Modal is rendered and then show the JsonEditor }}
{{#if this.showTemplateModal}}
<JsonEditor @value={{this.exampleTemplate}} @mode="ruby" @readOnly={{true}} @showToolbar={{false}} />
{{/if}}
<p class="has-top-margin-m"> <p class="has-top-margin-m">
The full list of template parameters can be found The full list of template parameters can be found
<DocLink @path="/vault/docs/concepts/oidc-provider#scopes"> <DocLink @path="/vault/docs/concepts/oidc-provider#scopes">
here. here.
</DocLink> </DocLink>
</p> </p>
</section> </M.Body>
<div class="modal-card-head has-border-top-light"> <M.Footer as |F|>
<button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal> <Hds::Button @text="Close" {{on "click" F.close}} data-test-close-modal />
Close </M.Footer>
</button> </Hds::Modal>
</div> {{/if}}
</Modal>

View File

@@ -53,14 +53,13 @@
{{/if}} {{/if}}
{{#if this.capabilities.canUpdate}} {{#if this.capabilities.canUpdate}}
{{#if (gt this.model.allowed_roles.length 0)}} {{#if (gt this.model.allowed_roles.length 0)}}
<button <Hds::Button
class="toolbar-link" @text="Edit transformation"
onclick={{action (mut this.isEditModalActive) true}} @color="secondary"
type="button" class="toolbar-button"
{{on "click" (fn (mut this.isEditModalActive) true)}}
data-test-edit-link data-test-edit-link
> />
Edit transformation
</button>
{{else}} {{else}}
<ToolbarSecretLink @secret={{this.model.id}} @mode="edit" data-test-edit-link={{true}} @replace={{true}}> <ToolbarSecretLink @secret={{this.model.id}} @mode="edit" data-test-edit-link={{true}} @replace={{true}}>
Edit transformation Edit transformation
@@ -96,30 +95,27 @@
<MessageError @model={{this.model}} @errorMessage={{this.error}} /> <MessageError @model={{this.model}} @errorMessage={{this.error}} />
</ConfirmationModal> </ConfirmationModal>
<Modal {{#if this.isEditModalActive}}
@title="Edit transformation" <Hds::Modal id="transformation-edit-modal" @color="warning" @onClose={{fn (mut this.isEditModalActive) false}} as |M|>
@onClose={{action (mut this.isEditModalActive) false}} <M.Header @icon="alert-triangle">
@isActive={{this.isEditModalActive}} Edit transformation
@type="warning" </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
<p> <p>
Youre editing a transformation that is in use by at least one role. Editing it may mean that encode and decode Youre editing a transformation that is in use by at least one role. Editing it may mean that encode and decode
operations stop working. Are you sure? operations stop working. Are you sure?
</p> </p>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<LinkTo <Hds::ButtonSet>
<Hds::Button
@text="Confirm"
@route="vault.cluster.secrets.backend.edit" @route="vault.cluster.secrets.backend.edit"
@model={{this.model.id}} @model={{this.model.id}}
class="button is-primary" data-test-edit-confirm-button
data-test-edit-confirm-button={{true}} />
> <Hds::Button @color="secondary" @text="Cancel" {{on "click" F.close}} />
Confirm </Hds::ButtonSet>
</LinkTo> </M.Footer>
<button type="button" class="button is-secondary" onclick={{action (mut this.isEditModalActive) false}}> </Hds::Modal>
Cancel {{/if}}
</button>
</footer>
</Modal>

View File

@@ -74,19 +74,27 @@
</div> </div>
</div> </div>
</form> </form>
<Modal @title="Copy your generated key" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}> {{#if @isModalActive}}
<section class="modal-card-body"> <Hds::Modal id="transit-datakey-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
<div class="box is-shadowless is-fullwidth is-sideless"> <M.Header>
Copy your generated key
</M.Header>
<M.Body>
{{#if (eq @param "plaintext")}} {{#if (eq @param "plaintext")}}
<h2 class="has-text-weight-semibold is-6">Plaintext</h2> <h2 class="has-text-weight-semibold is-6">Plaintext</h2>
<p class="sub-text">Plaintext is base64 encoded</p> <p class="sub-text">Plaintext is base64 encoded</p>
<Hds::Copy::Snippet class="has-bottom-margin-m" @textToCopy={{@plaintext}} @color="secondary" /> <Hds::Copy::Snippet
class="has-bottom-margin-m"
@textToCopy={{@plaintext}}
@color="secondary"
@container="#transit-datakey-modal"
/>
{{/if}} {{/if}}
<h2 class="has-text-weight-semibold is-6">Ciphertext</h2> <h2 class="has-text-weight-semibold is-6">Ciphertext</h2>
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" /> <Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" @container="#transit-datakey-modal" />
</div> </M.Body>
</section> <M.Footer as |F|>
<footer class="modal-card-foot"> <Hds::Button @text="Close" {{on "click" F.close}} />
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button> </M.Footer>
</footer> </Hds::Modal>
</Modal> {{/if}}

View File

@@ -56,16 +56,22 @@
</div> </div>
</form> </form>
{{#if @isModalActive}} {{#if @isModalActive}}
<Modal @title="Copy your unwrapped data" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}> <Hds::Modal id="transit-decrypt-modal" @onClose={{fn (mut @isModalActive) false}} data-test-decrypt-modal as |M|>
<section class="modal-card-body"> <M.Header>
<div class="box is-shadowless is-fullwidth is-sideless"> Copy your unwrapped data
</M.Header>
<M.Body>
<h2 class="has-text-weight-semibold is-6">Plaintext</h2> <h2 class="has-text-weight-semibold is-6">Plaintext</h2>
<p class="sub-text">Plaintext is base64 encoded</p> <p class="sub-text">Plaintext is base64 encoded</p>
<Hds::Copy::Snippet @textToCopy={{@plaintext}} @color="secondary" data-test-encrypted-value="plaintext" /> <Hds::Copy::Snippet
</div> @textToCopy={{@plaintext}}
</section> @color="secondary"
<footer class="modal-card-foot"> @container="#transit-decrypt-modal"
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button> data-test-encrypted-value="plaintext"
</footer> />
</Modal> </M.Body>
<M.Footer as |F|>
<Hds::Button @text="Close" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>
{{/if}} {{/if}}

View File

@@ -69,19 +69,22 @@
</div> </div>
</div> </div>
</form> </form>
<Modal {{#if @isModalActive}}
@title="Copy your token" <Hds::Modal id="transit-encrypt-modal" @onClose={{fn (mut @isModalActive) false}} data-test-encrypt-modal as |M|>
@onClose={{action (mut @isModalActive) false}} <M.Header>
@isActive={{@isModalActive}} Copy your token
data-test-encrypt-modal </M.Header>
> <M.Body>
<section class="modal-card-body">
<div class="box is-shadowless is-fullwidth is-sideless">
<h2 class="title is-6">Ciphertext</h2> <h2 class="title is-6">Ciphertext</h2>
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" data-test-encrypted-value="ciphertext" /> <Hds::Copy::Snippet
</div> @textToCopy={{@ciphertext}}
</section> @color="secondary"
<footer class="modal-card-foot"> @container="#transit-encrypt-modal"
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button> data-test-encrypted-value="ciphertext"
</footer> />
</Modal> </M.Body>
<M.Footer as |F|>
<Hds::Button @text="Close" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -65,15 +65,19 @@
</div> </div>
</div> </div>
</form> </form>
<Modal @title="Copy your wrapped key" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}> {{#if @isModalActive}}
<section class="modal-card-body"> <Hds::Modal id="transit-export-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
<div class="box is-shadowless is-fullwidth is-sideless"> <M.Header>
<h2 class="title is-6">Wrapped Key</h2> Copy your wrapped key
</M.Header>
<M.Body>
<h2 class="title is-6">Wrapped key</h2>
{{#if this.wrapTTL}} {{#if this.wrapTTL}}
<Hds::Copy::Snippet <Hds::Copy::Snippet
class="has-bottom-margin-m" class="has-bottom-margin-m"
@textToCopy={{@wrappedToken}} @textToCopy={{@wrappedToken}}
@color="secondary" @color="secondary"
@container="#transit-export-modal"
data-test-encrypted-value="export" data-test-encrypted-value="export"
/> />
{{else}} {{else}}
@@ -85,13 +89,14 @@
@text="Copy" @text="Copy"
@isIconOnly={{true}} @isIconOnly={{true}}
@textToCopy={{stringify @keys}} @textToCopy={{stringify @keys}}
@container="#transit-export-modal"
class="transparent top-right-absolute" class="transparent top-right-absolute"
/> />
</div> </div>
{{/if}} {{/if}}
</div> </M.Body>
</section> <M.Footer as |F|>
<footer class="modal-card-foot"> <Hds::Button @text="Close" {{on "click" F.close}} />
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button> </M.Footer>
</footer> </Hds::Modal>
</Modal> {{/if}}

View File

@@ -51,14 +51,22 @@
</div> </div>
</div> </div>
</form> </form>
<Modal @title="Copy your unwrapped data" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}> {{#if @isModalActive}}
<section class="modal-card-body"> <Hds::Modal id="transit-hmac-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
<div class="box is-shadowless is-fullwidth is-sideless"> <M.Header>
Copy your unwrapped data
</M.Header>
<M.Body>
<h2 class="title is-6">HMAC</h2> <h2 class="title is-6">HMAC</h2>
<Hds::Copy::Snippet @textToCopy={{@hmac}} @color="secondary" data-test-encrypted-value="hmac" /> <Hds::Copy::Snippet
</div> @textToCopy={{@hmac}}
</section> @color="secondary"
<footer class="modal-card-foot"> @container="#transit-hmac-modal"
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button> data-test-encrypted-value="hmac"
</footer> />
</Modal> </M.Body>
<M.Footer as |F|>
<Hds::Button @text="Close" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -63,14 +63,17 @@
</div> </div>
</div> </div>
</form> </form>
<Modal @title="Copy your token" @onClose={{action (mut @isModalActive)}} @isActive={{@isModalActive}}> {{#if @isModalActive}}
<section class="modal-card-body"> <Hds::Modal id="transit-rewrap-modal" @onClose={{fn (mut @isModalActive) false}} as |M|>
<div class="box is-shadowless is-fullwidth is-sideless"> <M.Header>
Copy your token
</M.Header>
<M.Body>
<h2 class="title is-6">Ciphertext</h2> <h2 class="title is-6">Ciphertext</h2>
<Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" /> <Hds::Copy::Snippet @textToCopy={{@ciphertext}} @color="secondary" @container="#transit-rewrap-modal" />
</div> </M.Body>
</section> <M.Footer as |F|>
<footer class="modal-card-foot"> <Hds::Button @text="Close" {{on "click" F.close}} />
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button> </M.Footer>
</footer> </Hds::Modal>
</Modal> {{/if}}

View File

@@ -120,19 +120,22 @@
</div> </div>
</div> </div>
</form> </form>
<Modal {{#if @isModalActive}}
@title="Copy your signature" <Hds::Modal id="transit-sign-modal" @onClose={{fn (mut @isModalActive) false}} data-test-sign-modal as |M|>
@onClose={{action (mut @isModalActive) false}} <M.Header>
@isActive={{@isModalActive}} Copy your signature
data-test-sign-modal </M.Header>
> <M.Body>
<section class="modal-card-body">
<div class="box is-shadowless is-fullwidth is-sideless">
<h2 class="title is-6">Signature</h2> <h2 class="title is-6">Signature</h2>
<Hds::Copy::Snippet @textToCopy={{@signature}} @color="secondary" data-test-encrypted-value="signature" /> <Hds::Copy::Snippet
</div> @textToCopy={{@signature}}
</section> @color="secondary"
<footer class="modal-card-foot"> @container="#transit-sign-modal"
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button> data-test-encrypted-value="signature"
</footer> />
</Modal> </M.Body>
<M.Footer as |F|>
<Hds::Button @text="Close" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -192,16 +192,25 @@
</div> </div>
</div> </div>
</form> </form>
<Modal @title="Results" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}> {{#if @isModalActive}}
<section class="modal-card-body"> <Hds::Modal id="transit-verify-modal" @size="small" @onClose={{fn (mut @isModalActive) false}} as |M|>
<Hds::Alert @type="inline" @color={{if @valid "success" "critical"}} as |A|> <M.Header>
<A.Title>{{if @valid "Valid" "Not Valid"}}</A.Title> Results
<A.Description> <Hds::Badge
@text={{if @valid "Valid" "Not Valid"}}
@size="large"
@color={{if @valid "success" "critical"}}
@icon={{if @valid "check-circle" "x-circle"}}
/>
</M.Header>
<M.Body>
The input is The input is
{{if @valid "valid" "not valid"}} {{if @valid "valid" "not valid"}}
for the given for the given
{{if @signature "signature." "HMAC."}} {{if @signature "signature." "HMAC."}}
</A.Description> </M.Body>
</Hds::Alert> <M.Footer as |F|>
</section> <Hds::Button @text="Close" {{on "click" F.close}} />
</Modal> </M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -95,15 +95,13 @@
</div> </div>
{{/if}} {{/if}}
<DownloadButton <DownloadButton
class="button is-ghost" @color="tertiary"
@filename={{this.keyFilename}} @filename={{this.keyFilename}}
@data={{this.keyData}} @data={{this.keyData}}
@extension="json" @extension="json"
@stringify={{true}} @stringify={{true}}
> @text="Download keys"
<Icon @name="download" /> />
Download keys
</DownloadButton>
</div> </div>
</div> </div>
</Page.content> </Page.content>

View File

@@ -29,14 +29,13 @@
<Toolbar> <Toolbar>
<ToolbarActions> <ToolbarActions>
<DownloadButton <DownloadButton
class="toolbar-link" class="toolbar-button"
@color="secondary"
@filename={{this.model.name}} @filename={{this.model.name}}
@data={{this.model.policy}} @data={{this.model.policy}}
@extension={{if (eq this.policyType "acl") this.model.format "sentinel"}} @extension={{if (eq this.policyType "acl") this.model.format "sentinel"}}
> @text="Download policy"
Download policy />
<Chevron @isButton={{true}} />
</DownloadButton>
{{#if (and (not-eq this.model.id "root") (or this.capabilities.canUpdate this.capabilities.canDelete))}} {{#if (and (not-eq this.model.id "root") (or this.capabilities.canUpdate this.capabilities.canDelete))}}
<ToolbarLink @route="vault.cluster.policy.edit" @model={{this.model.id}} data-test-policy-edit-toggle> <ToolbarLink @route="vault.cluster.policy.edit" @model={{this.model.id}} data-test-policy-edit-toggle>
Edit policy Edit policy

View File

@@ -19,7 +19,7 @@
) )
}} }}
</p> </p>
<h4 class="field-title has-bottom-padding-m is-fullwidth"> <h4 class="has-text-weight-bold is-size-7 has-bottom-padding-m is-fullwidth">
{{concat "PGP Key " this.pgpKeyFile.filename}} {{concat "PGP Key " this.pgpKeyFile.filename}}
</h4> </h4>
<Hds::Copy::Snippet <Hds::Copy::Snippet
@@ -27,6 +27,7 @@
@textToCopy={{this.pgpKey}} @textToCopy={{this.pgpKey}}
@color="secondary" @color="secondary"
data-test-pgp-key-copy data-test-pgp-key-copy
@container="#shamir-flow-modal"
/> />
</div> </div>
<div class="field is-grouped"> <div class="field is-grouped">

View File

@@ -14,5 +14,6 @@
@text="Copy" @text="Copy"
@textToCopy={{or @clipboardCode @codeBlock}} @textToCopy={{or @clipboardCode @codeBlock}}
@isIconOnly={{@isIconOnly}} @isIconOnly={{@isIconOnly}}
@container={{@container}}
/> />
</div> </div>

View File

@@ -3,17 +3,22 @@
SPDX-License-Identifier: BUSL-1.1 SPDX-License-Identifier: BUSL-1.1
~}} ~}}
<Modal @title={{@title}} @onClose={{@onClose}} @isActive={{@isActive}} @type={{this.type}} @showCloseButton={{true}}> {{#if @isActive}}
<section class="modal-card-body"> <Hds::Modal id="confirmation-modal" @onClose={{@onClose}} @color="critical" as |M|>
<M.Header data-test-confirmation-modal-title @icon="alert-triangle">
{{@title}}
</M.Header>
<M.Body>
{{yield}} {{yield}}
<div class="modal-confirm-section"> <div class="has-top-padding-m">
<p class="has-text-weight-semibold is-size-6"> <p class="has-text-weight-semibold is-size-6">
Confirm Confirm
</p> </p>
<p class="sub-text has-top-bottom-margin-xxs">Type <p class="sub-text has-top-bottom-margin-xxs">Type
<strong>{{this.confirmText}}</strong> <strong>{{this.confirmText}}</strong>
to confirm to confirm
{{this.toConfirmMsg}}</p> {{@toConfirmMsg}}
</p>
<Input <Input
@type="text" @type="text"
@value={{this.confirmationInput}} @value={{this.confirmationInput}}
@@ -21,22 +26,22 @@
class="input has-margin-top" class="input has-margin-top"
autocomplete="off" autocomplete="off"
spellcheck="false" spellcheck="false"
data-test-confirmation-modal-input={{or @title true}} data-test-confirmation-modal-input={{@title}}
/> />
</div> </div>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer>
<button <Hds::ButtonSet>
<Hds::Button
@color="critical"
type="button" type="button"
class="button {{this.buttonClass}}"
disabled={{not-eq this.confirmationInput this.confirmText}} disabled={{not-eq this.confirmationInput this.confirmText}}
{{on "click" @onConfirm}} {{on "click" @onConfirm}}
data-test-confirm-button={{or @title true}} data-test-confirm-button={{@title}}
> @text={{or @buttonText "Confirm"}}
{{this.buttonText}} />
</button> <Hds::Button @text="Cancel" @color="secondary" {{on "click" @onClose}} data-test-cancel-button />
<button type="button" class="button is-secondary" {{on "click" @onClose}} data-test-cancel-button> </Hds::ButtonSet>
Cancel </M.Footer>
</button> </Hds::Modal>
</footer> {{/if}}
</Modal>

View File

@@ -2,11 +2,11 @@
* Copyright (c) HashiCorp, Inc. * Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1 * SPDX-License-Identifier: BUSL-1.1
*/ */
import Component from '@glimmer/component'; import Component from '@glimmer/component';
/** /**
* @module ConfirmationModal * @module ConfirmationModal
* ConfirmationModal components are used to provide an alternative to ConfirmationButton that automatically prompts the user to fill in confirmation text before they can continue with a potentially destructive action. It is built off the Modal component * ConfirmationModal components wrap the <Hds::Modal> component to present a critical (red) type-to-confirm modal.
* They are used for extremely destructive actions that require extra consideration before confirming.
* *
* @example * @example
* ```js * ```js
@@ -15,6 +15,7 @@ import Component from '@glimmer/component';
* @title="Do Dangerous Thing?" * @title="Do Dangerous Thing?"
* @isActive={{isModalActive}} * @isActive={{isModalActive}}
* @onClose={{action (mut isModalActive) false}} * @onClose={{action (mut isModalActive) false}}
* @confirmText="yes"
* @onConfirmMsg="deleting this thing to delete." * @onConfirmMsg="deleting this thing to delete."
* /> * />
* ``` * ```
@@ -23,30 +24,12 @@ import Component from '@glimmer/component';
* @param {boolean} isActive - Controls whether the modal is "active" eg. visible or not. * @param {boolean} isActive - Controls whether the modal is "active" eg. visible or not.
* @param {string} title - Title of the modal * @param {string} title - Title of the modal
* @param {string} [confirmText=Yes] - The confirmation text that the user must type before continuing * @param {string} [confirmText=Yes] - The confirmation text that the user must type before continuing
* @param {string} [toConfirmMsg=''] - Finishes the sentence "Type <confirmText> to confirm <toConfirmMsg>", default is an empty string (ex. 'secret deletion') * @param {string} [toConfirmMsg] - Finishes the sentence "Type <confirmText> to confirm <toConfirmMsg>", default is an empty string (ex. 'secret deletion')
* @param {string} [buttonText=Confirm] - Button text on the confirm button * @param {string} [buttonText=Confirm] - Button text on the confirm button
* @param {string} [buttonClass=is-danger] - extra class to add to confirm button (eg. "is-danger")
* @param {string} [type=warning] - The header styling based on type, passed into the message-types helper (in the Modal component).
*/ */
export default class ConfirmationModal extends Component { export default class ConfirmationModal extends Component {
get buttonClass() {
return this.args.buttonClass || 'is-danger';
}
get buttonText() {
return this.args.buttonText || 'Confirm';
}
get confirmText() { get confirmText() {
return this.args.confirmText || 'Yes'; return this.args.confirmText || 'Yes';
} }
get type() {
return this.args.type || 'warning';
}
get toConfirmMsg() {
return this.args.toConfirmMsg || '';
}
} }

View File

@@ -3,6 +3,13 @@
SPDX-License-Identifier: BUSL-1.1 SPDX-License-Identifier: BUSL-1.1
~}} ~}}
<button data-test-download-button type="button" {{on "click" this.handleDownload}} ...attributes> <Hds::Button
{{yield}} data-test-download-button
</button> @icon={{if @hideIcon "" "download"}}
@text={{or @text "Download"}}
@color={{@color}}
@iconPosition={{or @iconPosition "leading"}}
@isIconOnly={{@isIconOnly}}
{{on "click" this.handleDownload}}
...attributes
/>

View File

@@ -12,22 +12,20 @@ import { tracked } from '@glimmer/tracking';
import { assert } from '@ember/debug'; import { assert } from '@ember/debug';
/** /**
* @module DownloadButton * @module DownloadButton
* DownloadButton components are an action button used to download data. Both the action text and icon are yielded. * DownloadButton wraps an <Hds::Button> to perform a download action.
* * NOTE: when using in an engine, remember to add the 'download' service to its dependencies (in /engine.js) and map to it in /app.js * * NOTE: when using in an engine, remember to add the 'download' service to its dependencies (in /engine.js) and map to it in /app.js
* [ember-docs](https://ember-engines.com/docs/services) * [ember-docs](https://ember-engines.com/docs/services)
* @example * @example
* ```js * ```js
* <DownloadButton * <DownloadButton
* class="button" * @text="Download this stuff"
* @color="secondary"
* @data={{this.data}} * @data={{this.data}}
* @filename={{this.filename}} * @filename={{this.filename}}
* @mime={{this.mime}} * @mime={{this.mime}}
* @extension={{this.extension}} * @extension={{this.extension}}
* @stringify={{true}} * @stringify={{true}}
* > * />
* <Icon @name="download" />
* Download
* </DownloadButton>
* ``` * ```
* @param {string} [filename] - name of file that prefixes the ISO timestamp generated at download * @param {string} [filename] - name of file that prefixes the ISO timestamp generated at download
* @param {string} [data] - data to download * @param {string} [data] - data to download
@@ -35,6 +33,12 @@ import { assert } from '@ember/debug';
* @param {string} [extension='txt'] - file extension, the download service uses this to determine the mimetype * @param {string} [extension='txt'] - file extension, the download service uses this to determine the mimetype
* @param {boolean} [stringify=false] - argument to stringify the data before passing to the File constructor * @param {boolean} [stringify=false] - argument to stringify the data before passing to the File constructor
* @param {callback} [onSuccess] - callback from parent to invoke if download is successful * @param {callback} [onSuccess] - callback from parent to invoke if download is successful
* @param {boolean} [hideIcon=false] - renders the 'download' icon by default, pass true to hide (ex: when download button appears in a dropdown)
* * HDS ARGS https://helios.hashicorp.design/components/button?tab=code
* @param {string} [text="Download"] - button text, defaults to 'Download'
* @param {string} [color] - HDS default is primary, but there are four color options: primary, secondary, tertiary, and critical.
* @param {string} [iconPosition="leading"] - icon position, 'leading' (HDS default) or 'trailing'
* @param {boolean} [isIconOnly] - button only renders an icon, no text
*/ */
export default class DownloadButton extends Component { export default class DownloadButton extends Component {

View File

@@ -40,7 +40,7 @@
@text="Copy" @text="Copy"
@isIconOnly={{true}} @isIconOnly={{true}}
@textToCopy={{@value}} @textToCopy={{@value}}
class="transparent icon-only is-paddingless" class="transparent has-padding-xxs"
data-test-copy-button data-test-copy-button
/> />
{{/if}} {{/if}}

View File

@@ -39,14 +39,20 @@
@text="Copy" @text="Copy"
@isIconOnly={{true}} @isIconOnly={{true}}
@textToCopy={{@value}} @textToCopy={{@value}}
class="transparent icon-only is-paddingless" class="transparent has-padding-xxs"
data-test-copy-button data-test-copy-button
/> />
{{/if}} {{/if}}
{{#if @allowDownload}} {{#if @allowDownload}}
<button type="button" class="button download-button" {{on "click" (fn (mut this.modalOpen) true)}}> <Hds::Button
<Icon data-test-download-icon @name="download" /> @text="Download secret value"
</button> @icon="download"
@isIconOnly={{true}}
@color="tertiary"
class="has-padding-xxs"
data-test-download-icon
{{on "click" (fn (mut this.modalOpen) true)}}
/>
{{/if}} {{/if}}
<button <button
onclick={{this.toggleMask}} onclick={{this.toggleMask}}
@@ -61,31 +67,25 @@
</div> </div>
{{! CONFIRM DOWNLOAD MODAL }} {{! CONFIRM DOWNLOAD MODAL }}
{{#if @allowDownload}} {{#if this.modalOpen}}
<Modal <Hds::Modal @color="warning" id="confirm-download-modal" @onClose={{fn (mut this.modalOpen) false}} as |M|>
@title="Download secret value?" <M.Header @icon="alert-triangle">
@onClose={{action (mut this.modalOpen) false}} Download secret value?
@isActive={{this.modalOpen}} </M.Header>
@type="warning" <M.Body>
>
<section class="modal-card-body">
This download is This download is
<strong>unencrypted</strong>. Are you sure you want to download this secret data as plaintext? <strong>unencrypted</strong>. Are you sure you want to download this secret data as plaintext?
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<Hds::ButtonSet>
<DownloadButton <DownloadButton
class="button is-primary"
@filename={{or @name "secret-value"}} @filename={{or @name "secret-value"}}
@data={{@value}} @data={{@value}}
@stringify={{true}} @stringify={{true}}
aria-label="Download secret value"
@onSuccess={{fn (mut this.modalOpen) false}} @onSuccess={{fn (mut this.modalOpen) false}}
> />
Download <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</DownloadButton> </Hds::ButtonSet>
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.modalOpen) false)}}> </M.Footer>
Cancel </Hds::Modal>
</button>
</footer>
</Modal>
{{/if}} {{/if}}

View File

@@ -1,37 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<EmberWormhole @to="modal-wormhole">
<div class="{{this.modalClass}} {{if this.isActive 'is-active'}}" data-test-modal-div ...attributes>
<div class="modal-background" role="button" {{on "click" @onClose}} data-test-modal-background={{@title}}></div>
<div class="modal-card">
<header class="modal-card-head">
<h2 class="modal-card-title title is-5" data-test-modal-title>
{{#if this.glyph}}
<Icon
class={{this.glyph.glyphClass}}
aria-hidden="true"
@name={{this.glyph.glyph}}
data-test-modal-glyph={{this.glyph.glyph}}
/>
{{/if}}
<span class={{if this.glyph "has-left-padding-xs"}}>{{capitalize @title}}</span>
</h2>
{{#if this.showCloseButton}}
<button
type="button"
class="icon-button has-text-grey"
aria-label="close"
{{on "click" @onClose}}
data-test-modal-close-button
>
<Icon @name="x-circle" class="close-icon" />
</button>
{{/if}}
</header>
{{yield}}
</div>
</div>
</EmberWormhole>

View File

@@ -1,55 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import { messageTypes } from 'core/helpers/message-types';
/**
* @module Modal
* Modal components are used to overlay content on top of the page. Has a darkened background,
* a title, and in order to close it you must pass an onClose function.
*
* @example
* ```js
* <Modal
* @title="Export attribution data"
* @type="info"
* @isActive={{this.showModal}}
* @showCloseButton={{true}}
* @onClose={{action (mut this.showModal) false}}
* >
* Whatever content pops up when the modal isActive!
* </Modal>
* ```
* @callback onClose
* @param {onClose} onClose - onClose is the action taken when someone clicks the modal background or close button (if shown).
* @param {boolean} isActive=false - whether or not modal displays
* @param {string} [title] - This text shows up in the header section of the modal. Only the first word should be capitalized.
* @param {boolean} [showCloseButton=false] - controls whether the close button in the top right corner shows.
* @param {string} [type=null] - The header styling based on type passed into the message-types helper.
*/
export default class ModalComponent extends Component {
get isActive() {
return this.args.isActive || false;
}
get showCloseButton() {
return this.args.showCloseButton || false;
}
get glyph() {
if (!this.args.type) {
return null;
}
return messageTypes([this.args.type]);
}
get modalClass() {
if (!this.args.type) {
return 'modal';
}
return 'modal ' + messageTypes([this.args.type]).class;
}
}

View File

@@ -42,7 +42,14 @@
</p> </p>
{{/if}} {{/if}}
</div> </div>
<JsonEditor @value={{get this.policyTemplates @policyType}} @mode="ruby" @readOnly={{true}} @showToolbar={{true}} /> <JsonEditor
@value={{get this.policyTemplates @policyType}}
@mode="ruby"
@readOnly={{true}}
@showToolbar={{true}}
{{! Passed to copy button }}
@container={{@container}}
/>
<div class="has-bottom-margin-m has-top-padding-s"> <div class="has-bottom-margin-m has-top-padding-s">
<p> <p>
More information about More information about

View File

@@ -14,28 +14,11 @@ import Component from '@glimmer/component';
* @example * @example
* <PolicyExample * <PolicyExample
* @policyType={{@model.policyType}} * @policyType={{@model.policyType}}
* @container="#search-select-modal"
* /> * />
*
* @example (in modal)
* <Modal
* @onClose={{fn (mut this.showTemplateModal) false}}
* @isActive={{this.showTemplateModal}}
* >
* <section class="modal-card-body">
* {{! code-mirror modifier does not render value initially until focus event fires }}
* {{! wait until the Modal is rendered and then show the PolicyExample (contains JsonEditor) }}
* {{#if this.showTemplateModal}}
* <PolicyExample @policyType={{@model.policyType}}/>
* {{/if}}
* </section>
* <div class="modal-card-head has-border-top-light">
* <button type="button" class="button" {{on "click" (fn (mut this.showTemplateModal) false)}} data-test-close-modal>
* Close
* </button>
* </div>
* </Modal>
* ``` * ```
* @param {string} policyType - policy type to decide which template to render; can either be "acl" or "rgp" * @param {string} policyType - policy type to decide which template to render; can either be "acl" or "rgp"
* @param {string} container - selector for the container the example renders inside, passed to the copy button in JsonEditor
*/ */
export default class PolicyExampleComponent extends Component { export default class PolicyExampleComponent extends Component {

View File

@@ -83,32 +83,24 @@
</li> </li>
{{/each}} {{/each}}
</ul> </ul>
{{/if}} {{/if}}
{{! wait until user has selected 'create a new item' before rendering modal}} {{#if this.showModal}}
{{#if this.nameInput}} <Hds::Modal id="search-select-modal" @onClose={{fn (mut this.showModal) false}} as |M|>
<Modal <M.Header data-test-modal-title>
@title="Create new {{singularize @id}}" Create new
@onClose={{action (mut this.showModal) false}} {{singularize @id}}
@isActive={{this.showModal}} </M.Header>
@type="info" <M.Body>
@showCloseButton={{false}}
>
<section class="modal-card-body">
{{#if @modalSubtext}} {{#if @modalSubtext}}
<p class="has-bottom-margin-s" data-test-modal-subtext> <p class="has-bottom-margin-s" data-test-modal-subtext>
{{@modalSubtext}} {{@modalSubtext}}
</p> </p>
{{/if}} {{/if}}
{{#if (has-block)}}
{{yield}}
{{else}}
{{! dynamically render template from modal-form/ folder}} {{! dynamically render template from modal-form/ folder}}
{{! form must receive an @onSave and @onCancel arg that executes the callback}} {{! form must receive an @onSave and @onCancel arg that executes the callback}}
{{component @modalFormTemplate nameInput=this.nameInput onSave=this.resetModal onCancel=this.resetModal}} {{component @modalFormTemplate nameInput=this.nameInput onSave=this.resetModal onCancel=this.resetModal}}
{{/if}} </M.Body>
</section> </Hds::Modal>
</Modal>
{{/if}} {{/if}}
</div> </div>

View File

@@ -43,6 +43,7 @@
@options={{this.dropdownOptions}} @options={{this.dropdownOptions}}
@onChange={{this.selectOrCreate}} @onChange={{this.selectOrCreate}}
@placeholderComponent={{component "search-select-placeholder"}} @placeholderComponent={{component "search-select-placeholder"}}
@renderInPlace={{@renderInPlace}}
@verticalPosition="below" @verticalPosition="below"
@disabled={{@disabled}} @disabled={{@disabled}}
as |option| as |option|

View File

@@ -53,6 +53,7 @@ import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-ut
* @param {string} [wildcardLabel] - string (singular) for rendering label tag beside a wildcard selection (i.e. 'role*'), for the number of items it includes, e.g. @wildcardLabel="role" -> "includes 4 roles" * @param {string} [wildcardLabel] - string (singular) for rendering label tag beside a wildcard selection (i.e. 'role*'), for the number of items it includes, e.g. @wildcardLabel="role" -> "includes 4 roles"
* @param {string} [placeholder] - text you wish to replace the default "search" with * @param {string} [placeholder] - text you wish to replace the default "search" with
* @param {boolean} [displayInherit=false] - if you need the search select component to display inherit instead of box. * @param {boolean} [displayInherit=false] - if you need the search select component to display inherit instead of box.
* @param {boolean} [renderInPlace] - pass `true` when power select renders in a modal
* @param {function} [renderInfoTooltip] - receives each inputValue string and list of dropdownOptions as args, so parent can determine when to render a tooltip beside a selectedOption and the tooltip text. see 'oidc/provider-form.js' * @param {function} [renderInfoTooltip] - receives each inputValue string and list of dropdownOptions as args, so parent can determine when to render a tooltip beside a selectedOption and the tooltip text. see 'oidc/provider-form.js'
* @param {boolean} [disabled] - if true sets the disabled property on the ember-power-select component and makes it unusable. * @param {boolean} [disabled] - if true sets the disabled property on the ember-power-select component and makes it unusable.
* *

View File

@@ -2,35 +2,34 @@
Copyright (c) HashiCorp, Inc. Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1 SPDX-License-Identifier: BUSL-1.1
~}} ~}}
{{! THIS COMPONENT RENDERS INSIDE A MODAL (must pass @container to copy buttons) }}
<section class="modal-card-body"> {{#if this.encodedToken}}
{{#if this.encodedToken}}
<p class="has-bottom-margin-l" data-test-dr-token-flow-step="show-token"> <p class="has-bottom-margin-l" data-test-dr-token-flow-step="show-token">
Below is the process and the values necessary to generate your operation token. Read the instructions carefully! Below is the process and the values necessary to generate your operation token. Read the instructions carefully!
</p> </p>
<div class="has-bottom-margin-m"> <div class="has-bottom-margin-m">
<div class="has-bottom-margin-xl"> <div class="has-bottom-margin-xl">
<h4 class="field-title"> <h4 class="has-text-weight-bold is-size-7">
Encoded operation token Encoded operation token
</h4> </h4>
<p class="help has-text-grey has-bottom-margin-xs"> <p class="help has-text-grey has-bottom-margin-xs">
This is a one-time token that will be used to generate the operation token. Please save it. This is a one-time token that will be used to generate the operation token. Please save it.
</p> </p>
<Hds::Copy::Snippet @textToCopy={{this.encodedToken}} data-test-shamir-encoded-token /> <Hds::Copy::Snippet @textToCopy={{this.encodedToken}} @container="#shamir-flow-modal" data-test-shamir-encoded-token />
</div> </div>
{{#if this.otp}} {{#if this.otp}}
<div class="has-bottom-margin-xl"> <div class="has-bottom-margin-xl">
<h4 class="field-title"> <h4 class="has-text-weight-bold is-size-7">
One time password (OTP) One time password (OTP)
</h4> </h4>
<p class="help has-text-grey has-bottom-margin-xs"> <p class="help has-text-grey has-bottom-margin-xs">
This OTP will be used to decode the generated operation token. Please save it. This OTP will be used to decode the generated operation token. Please save it.
</p> </p>
<Hds::Copy::Snippet @textToCopy={{this.otp}} /> <Hds::Copy::Snippet @textToCopy={{this.otp}} @container="#shamir-flow-modal" />
</div> </div>
{{/if}} {{/if}}
<div class="has-bottom-margin-xl"> <div class="has-bottom-margin-xl">
<h4 class="field-title"> <h4 class="has-text-weight-bold is-size-7">
DR operation token command DR operation token command
</h4> </h4>
<p class="help has-text-grey has-bottom-margin-xs"> <p class="help has-text-grey has-bottom-margin-xs">
@@ -51,17 +50,15 @@
) )
as |cmd| as |cmd|
}} }}
<CodeSnippet @codeBlock={{cmd}} /> <CodeSnippet @codeBlock={{cmd}} @container="#shamir-flow-modal" />
{{/let}} {{/let}}
{{! template-lint-enable quotes }} {{! template-lint-enable quotes }}
</div> </div>
</div> </div>
<div> <div>
<button type="button" class="button is-primary" {{on "click" this.onCancelClose}}> <Hds::Button {{on "click" this.onCancelClose}} @text="Clear & Close" />
Clear &amp; Close
</button>
</div> </div>
{{else if this.started}} {{else if this.started}}
<Shamir::Form <Shamir::Form
@action={{@action}} @action={{@action}}
@progress={{this.progress}} @progress={{this.progress}}
@@ -79,7 +76,7 @@
secondary Disaster Recovery cluster. secondary Disaster Recovery cluster.
</p> </p>
</Shamir::Form> </Shamir::Form>
{{else if this.generateWithPGP}} {{else if this.generateWithPGP}}
<ChoosePgpKeyForm <ChoosePgpKeyForm
@onCancel={{fn (mut this.generateWithPGP) false}} @onCancel={{fn (mut this.generateWithPGP) false}}
@onSubmit={{this.usePgpKey}} @onSubmit={{this.usePgpKey}}
@@ -88,7 +85,7 @@
@buttonText="Generate operation token" @buttonText="Generate operation token"
data-test-dr-token-flow-step="choose-pgp" data-test-dr-token-flow-step="choose-pgp"
/> />
{{else}} {{else}}
{{! Generate token flow not started }} {{! Generate token flow not started }}
<form <form
{{on "submit" this.startGenerate}} {{on "submit" this.startGenerate}}
@@ -97,18 +94,22 @@
data-test-dr-token-flow-step="begin" data-test-dr-token-flow-step="begin"
> >
<MessageError @errors={{this.errors}} /> <MessageError @errors={{this.errors}} />
<div class="has-bottom-margin-m" data-test-shamir-modal-body> <div class="has-bottom-margin-m">
<p> <p>
Updating or promoting this cluster requires an operation token, generated by inputting the root key shares. If Updating or promoting this cluster requires an operation token, generated by inputting the root key shares. If you'd
you'd like to first encrypt the token with a PGP Key, click "Encrypt with PGP key" below, otherwise we can begin like to first encrypt the token with a PGP Key, click "Encrypt with PGP key" below, otherwise we can begin generation
generation of the operation token. of the operation token.
</p> </p>
</div> </div>
<div class="field is-grouped is-flex-center"> <div class="field is-grouped is-flex-center">
<div class="control is-flex-row"> <div class="control is-flex-row">
<button type="button" class="link" {{on "click" (fn (mut this.generateWithPGP) true)}} data-test-use-pgp-key-cta> <Hds::Button
Provide PGP Key @color="tertiary"
</button> @icon="key"
{{on "click" (fn (mut this.generateWithPGP) true)}}
data-test-use-pgp-key-cta
@text="Provide PGP Key"
/>
</div> </div>
<div class="control"> <div class="control">
<span class="has-side-padding-s"> <span class="has-side-padding-s">
@@ -116,16 +117,16 @@
</span> </span>
</div> </div>
<div class="control"> <div class="control">
<button type="submit" class="button is-primary" data-test-generate-token-cta> <Hds::Button type="submit" data-test-generate-token-cta @text="Generate operation token" />
Generate operation token
</button>
</div> </div>
</div> </div>
</form> </form>
{{/if}} {{/if}}
</section> <hr class="has-background-gray-100" />
<footer class="modal-card-foot modal-card-foot-outlined">
<button type="button" class="button is-secondary" {{on "click" this.onCancelClose}} data-test-shamir-modal-cancel-button> <Hds::Button
{{if this.encodedToken "Close" "Cancel"}} @color="secondary"
</button> {{on "click" this.onCancelClose}}
</footer> data-test-shamir-modal-cancel-button
@text={{if this.encodedToken "Close" "Cancel"}}
/>

View File

@@ -21,7 +21,7 @@
<h4 class="hds-alert__title hds-font-weight-semibold"> <h4 class="hds-alert__title hds-font-weight-semibold">
One Time Password (otp) One Time Password (otp)
</h4> </h4>
<Hds::Copy::Snippet data-test-otp @textToCopy={{@otp}} @color="secondary" /> <Hds::Copy::Snippet data-test-otp @textToCopy={{@otp}} @color="secondary" @container="#shamir-flow-modal" />
</A.Description> </A.Description>
</Hds::Alert> </Hds::Alert>
{{/if}} {{/if}}

View File

@@ -37,11 +37,6 @@ export const MESSAGE_TYPES = {
glyph: 'loading', glyph: 'loading',
text: 'Loading', text: 'Loading',
}, },
rotation: {
class: 'is-info',
glyphClass: 'has-text-grey',
glyph: 'rotate-cw',
},
}; };
export function messageTypes([type]) { export function messageTypes([type]) {

View File

@@ -19,7 +19,7 @@
type="button" type="button"
class="button is-secondary" class="button is-secondary"
onclick={{action (mut this.isModalActive) true}} onclick={{action (mut this.isModalActive) true}}
data-test-replication-action-trigger data-test-replication-action-trigger="demote"
> >
Demote Demote
</button> </button>
@@ -30,7 +30,6 @@
@title="Demote to secondary?" @title="Demote to secondary?"
@onClose={{action (mut this.isModalActive) false}} @onClose={{action (mut this.isModalActive) false}}
@isActive={{this.isModalActive}} @isActive={{this.isModalActive}}
@buttonClass="is-primary"
@confirmText={{this.model.replicationModeForDisplay}} @confirmText={{this.model.replicationModeForDisplay}}
@toConfirmMsg="demoting this cluster" @toConfirmMsg="demoting this cluster"
@onConfirm={{action "onSubmit" "demote" this.model.replicationAttrs.modeForUrl}} @onConfirm={{action "onSubmit" "demote" this.model.replicationAttrs.modeForUrl}}

View File

@@ -20,7 +20,7 @@
type="button" type="button"
class="button is-danger" class="button is-danger"
onclick={{action (mut this.isModalActive) true}} onclick={{action (mut this.isModalActive) true}}
data-test-replication-action-trigger data-test-replication-action-trigger="disable"
> >
Disable Replication Disable Replication
</button> </button>

View File

@@ -25,16 +25,14 @@
</div> </div>
</div> </div>
<Modal {{#if this.isModalActive}}
@title="Generate operation token" <Hds::Modal id="shamir-flow-modal" @color="warning" @onClose={{action (mut this.isModalActive) false}} as |M|>
@onClose={{action (mut this.isModalActive) false}} <M.Header>
@isActive={{this.isModalActive}} Generate operation token
@type="warning" </M.Header>
@showCloseButton={{true}} <M.Body>
>
{{#if this.isModalActive}}
{{! Wrapped in if statement so the Shamir constructor fires on modal open }}
<Shamir::DrTokenFlow @action="generate-dr-operation-token" @onCancel={{action (mut this.isModalActive) false}} /> <Shamir::DrTokenFlow @action="generate-dr-operation-token" @onCancel={{action (mut this.isModalActive) false}} />
</M.Body>
{{! Section & Footer is in child component since the form must do side effects on cancel }} {{! Section & Footer is in child component since the form must do side effects on cancel }}
{{/if}} </Hds::Modal>
</Modal> {{/if}}

View File

@@ -20,30 +20,28 @@
type="button" type="button"
class="button is-tertiary" class="button is-tertiary"
onclick={{action (mut this.isModalActive) true}} onclick={{action (mut this.isModalActive) true}}
data-test-replication-action-trigger data-test-replication-action-trigger="promote"
> >
Promote Promote
</button> </button>
</div> </div>
</div> </div>
<Modal {{#if this.isModalActive}}
@title="Promote cluster?" <Hds::Modal id="replication-promote-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
@onClose={{action (mut this.isModalActive) false}} <M.Header @icon="alert-triangle">
@isActive={{this.isModalActive}} Promote cluster?
@type="warning" </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
{{#if (eq this.replicationMode "dr")}} {{#if (eq this.replicationMode "dr")}}
<p class="has-bottom-margin-m"> <p class="has-bottom-margin-m">
To promote this DR Replication Secondary to a primary, enter the DR Operation token. To promote this DR Replication Secondary to a primary, enter the DR Operation token.
</p> </p>
{{/if}} {{/if}}
<p class="has-bottom-margin-m"> <p class="has-bottom-margin-m">
Vault Replication is not designed for active-active usage. Enabling two primaries should never be done, as it can lead Vault Replication is not designed for active-active usage. Enabling two primaries should never be done, as it can
to data loss if they or their secondaries are ever reconnected. If the cluster has a primary, be sure to demote it lead to data loss if they or their secondaries are ever reconnected. If the cluster has a primary, be sure to demote
before promoting a secondary. it before promoting a secondary.
</p> </p>
<div data-test-promote-dr-inputs> <div data-test-promote-dr-inputs>
@@ -68,7 +66,12 @@
<em class="is-optional">(optional)</em> <em class="is-optional">(optional)</em>
</label> </label>
<div class="control"> <div class="control">
<Input class="input" id="primary_cluster_addr" name="primary_cluster_addr" @value={{this.primary_cluster_addr}} /> <Input
class="input"
id="primary_cluster_addr"
name="primary_cluster_addr"
@value={{this.primary_cluster_addr}}
/>
</div> </div>
<p class="help"> <p class="help">
Overrides the cluster address that the primary gives to secondary nodes. Overrides the cluster address that the primary gives to secondary nodes.
@@ -94,14 +97,15 @@
</div> </div>
</div> </div>
</div> </div>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::ButtonSet>
type="button" <Hds::Button
class="button is-primary"
disabled={{if (and (eq this.replicationMode "dr") (not this.dr_operation_token_promote)) true}} disabled={{if (and (eq this.replicationMode "dr") (not this.dr_operation_token_promote)) true}}
onclick={{action {{on
"onSubmit" "click"
(fn
this.onSubmit
"promote" "promote"
this.model.replicationAttrs.modeForUrl this.model.replicationAttrs.modeForUrl
(hash (hash
@@ -109,18 +113,13 @@
primary_cluster_addr=this.primary_cluster_addr primary_cluster_addr=this.primary_cluster_addr
force=this.force force=this.force
) )
)
}} }}
data-test-promote-confirm-button data-test-confirm-button
> @text="Promote"
Promote />
</button> <Hds::Button @color="secondary" @text="Cancel" {{on "click" F.close}} data-test-promote-cancel-button />
<button </Hds::ButtonSet>
type="button" </M.Footer>
class="button is-secondary" </Hds::Modal>
onclick={{action (mut this.isModalActive) false}} {{/if}}
data-test-promote-cancel-button
>
Cancel
</button>
</footer>
</Modal>

View File

@@ -18,36 +18,28 @@
type="button" type="button"
class="button is-secondary" class="button is-secondary"
onclick={{action (mut this.isModalActive) true}} onclick={{action (mut this.isModalActive) true}}
data-test-replication-action-trigger data-test-replication-action-trigger="recover"
> >
Recover Recover
</button> </button>
</div> </div>
</div> </div>
<Modal {{#if this.isModalActive}}
@title="Begin recovery?" <Hds::Modal id="replication-recover-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
@onClose={{action (mut this.isModalActive) false}} <M.Header @icon="alert-triangle">
@isActive={{this.isModalActive}} Begin recovery?
@type="warning" </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
<p> <p>
If replication is in an adverse state, we can begin recovery. This will attempt to recover to continue syncing. If replication is in an adverse state, we can begin recovery. This will attempt to recover to continue syncing.
</p> </p>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button type="button" class="button is-primary" onclick={{action "onSubmit" "recover"}} data-test-recover-confirm-button> <Hds::ButtonSet>
Recover <Hds::Button @text="Recover" {{on "click" (fn this.onSubmit "recover")}} data-test-confirm-button />
</button> <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-recover-cancel-button />
<button </Hds::ButtonSet>
type="button" </M.Footer>
class="button is-secondary" </Hds::Modal>
onclick={{action (mut this.isModalActive) false}} {{/if}}
data-test-recover-cancel-button
>
Cancel
</button>
</footer>
</Modal>

View File

@@ -17,20 +17,18 @@
type="button" type="button"
class="button is-secondary" class="button is-secondary"
onclick={{action (mut this.isModalActive) true}} onclick={{action (mut this.isModalActive) true}}
data-test-replication-action-trigger data-test-replication-action-trigger="reindex"
> >
Reindex Reindex
</button> </button>
</div> </div>
</div> </div>
<Modal {{#if this.isModalActive}}
@title="Begin reindex?" <Hds::Modal id="replication-reindex-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
@onClose={{action (mut this.isModalActive) false}} <M.Header @icon="alert-triangle">
@isActive={{this.isModalActive}} Begin reindex?
@type="warning" </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
<p class="has-bottom-margin-m"> <p class="has-bottom-margin-m">
Reindexing can cause a very long delay depending on the number and size of objects in the data store. Reindexing can cause a very long delay depending on the number and size of objects in the data store.
{{if this.model.replicationAttrs.isPrimary "You should always re-index your secondary first."}} {{if this.model.replicationAttrs.isPrimary "You should always re-index your secondary first."}}
@@ -40,18 +38,12 @@
{{if this.model.replicationAttrs.isPrimary "not"}} {{if this.model.replicationAttrs.isPrimary "not"}}
be able to use Vault during this time. be able to use Vault during this time.
</p> </p>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button type="button" class="button is-primary" onclick={{action "onSubmit" "reindex"}} data-test-reindex-confirm-button> <Hds::ButtonSet>
Reindex <Hds::Button @text="Reindex" {{on "click" (fn this.onSubmit "reindex")}} data-test-confirm-button />
</button> <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-reindex-cancel-button />
<button </Hds::ButtonSet>
type="button" </M.Footer>
class="button is-secondary" </Hds::Modal>
onclick={{action (mut this.isModalActive) false}} {{/if}}
data-test-reindex-cancel-button
>
Cancel
</button>
</footer>
</Modal>

View File

@@ -18,21 +18,19 @@
type="button" type="button"
class="button is-secondary" class="button is-secondary"
onclick={{action (mut this.isModalActive) true}} onclick={{action (mut this.isModalActive) true}}
data-test-update-primary-action-trigger data-test-replication-action-trigger="update-primary"
> >
Update Update
</button> </button>
</div> </div>
</div> </div>
<Modal {{#if this.isModalActive}}
@title="Update primary" <Hds::Modal id="replication-update-primary-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
@onClose={{action (mut this.isModalActive) false}} <M.Header @icon="alert-triangle">
@isActive={{this.isModalActive}} Update primary
@type="warning" </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
<p class="has-bottom-margin-m"> <p class="has-bottom-margin-m">
Use a secondary activation token to change this secondarys assigned primary. This does not wipe all data in the Use a secondary activation token to change this secondarys assigned primary. This does not wipe all data in the
cluster. cluster.
@@ -101,13 +99,14 @@
</p> </p>
</div> </div>
</div> </div>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::ButtonSet>
type="button" <Hds::Button
class="button is-primary" {{on
onclick={{action "click"
"onSubmit" (fn
this.onSubmit
"update-primary" "update-primary"
this.model.replicationAttrs.modeForUrl this.model.replicationAttrs.modeForUrl
(hash (hash
@@ -117,18 +116,13 @@
ca_path=this.ca_path ca_path=this.ca_path
ca_file=this.ca_file ca_file=this.ca_file
) )
)
}} }}
data-test-confirm-action-trigger data-test-confirm-button
> @text="Update"
Update />
</button> <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-update-primary-cancel-button />
<button </Hds::ButtonSet>
type="button" </M.Footer>
class="button is-secondary" </Hds::Modal>
onclick={{action (mut this.isModalActive) false}} {{/if}}
data-test-update-primary-cancel-button
>
Cancel
</button>
</footer>
</Modal>

View File

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

View File

@@ -24,7 +24,6 @@
"ember-router-helpers": "*", "ember-router-helpers": "*",
"ember-svg-jar": "*", "ember-svg-jar": "*",
"ember-truth-helpers": "*", "ember-truth-helpers": "*",
"ember-wormhole": "*",
"escape-string-regexp": "*", "escape-string-regexp": "*",
"@hashicorp/ember-flight-icons": "*", "@hashicorp/ember-flight-icons": "*",
"@hashicorp/flight-icons": "*", "@hashicorp/flight-icons": "*",

View File

@@ -8,14 +8,13 @@
<ToolbarActions> <ToolbarActions>
{{#if this.model}} {{#if this.model}}
<DownloadButton <DownloadButton
class="toolbar-link" class="toolbar-button"
@color="secondary"
@filename={{concat this.model.ca.id "-ca"}} @filename={{concat this.model.ca.id "-ca"}}
@data={{this.model.ca.caPem}} @data={{this.model.ca.caPem}}
@extension="pem" @extension="pem"
> @text="Download CA cert"
Download CA cert />
<Chevron @isButton={{true}} />
</DownloadButton>
{{/if}} {{/if}}
<ToolbarLink @route="configure" data-test-kmip-link-configure> <ToolbarLink @route="configure" data-test-kmip-link-configure>
Configure Configure

View File

@@ -103,26 +103,21 @@
</div> </div>
{{#if this.showConfirm}} {{#if this.showConfirm}}
<Modal <Hds::Modal id="kubernetes-edit-config-modal" @onClose={{fn (mut this.showConfirm) false}} @color="warning" as |M|>
@title="Edit configuration" <M.Header @icon="alert-triangle">
@type="warning" Edit configuration
@isActive={{this.showConfirm}} </M.Header>
@showCloseButton={{true}} <M.Body data-test-edit-config-body>
@onClose={{fn (mut this.showConfirm) false}}
>
<section class="modal-card-body">
<p> <p>
Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are
you sure? you sure?
</p> </p>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button data-test-config-confirm type="button" class="button is-primary" {{on "click" (perform this.save)}}> <Hds::ButtonSet>
Confirm <Hds::Button data-test-config-confirm {{on "click" (perform this.save)}} @text="Confirm" />
</button> <Hds::Button {{on "click" F.close}} @color="secondary" @text="Cancel" />
<button type="button" class="button" onclick={{fn (mut this.showConfirm) false}}> </Hds::ButtonSet>
Cancel </M.Footer>
</button> </Hds::Modal>
</footer>
</Modal>
{{/if}} {{/if}}

View File

@@ -1,16 +1,23 @@
<button type="button" class="toolbar-link" {{on "click" (fn (mut this.modalOpen) true)}} data-test-kv-delete={{@mode}}> <Hds::Button
{{yield}} @text={{or @text (capitalize @mode)}}
</button> @color="secondary"
class="toolbar-button"
{{on "click" (fn (mut this.modalOpen) true)}}
data-test-kv-delete={{@mode}}
/>
{{#if this.modalOpen}} {{#if this.modalOpen}}
<Modal <Hds::Modal
@title={{this.modalDisplay.title}} id="kv-delete-modal-{{@mode}}"
@color={{this.modalDisplay.color}}
@onClose={{fn (mut this.modalOpen) false}} @onClose={{fn (mut this.modalOpen) false}}
@isActive={{this.modalOpen}}
@type={{this.modalDisplay.type}}
@showCloseButton={{true}}
data-test-delete-modal data-test-delete-modal
as |M|
> >
<section class="modal-card-body"> <M.Header data-test-modal-title @icon="trash">
{{this.modalDisplay.title}}
</M.Header>
<M.Body>
<p class="has-bottom-margin-s"> <p class="has-bottom-margin-s">
{{this.modalDisplay.intro}} {{this.modalDisplay.intro}}
</p> </p>
@@ -45,19 +52,19 @@
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::ButtonSet>
type="button" <Hds::Button
class="button {{if (eq this.modalDisplay.type 'danger') 'is-danger-outlined' 'is-warning-outlined'}}" @text="Confirm"
{{on "click" this.onDelete}} {{on "click" this.onDelete}}
@color={{if (eq this.modalDisplay.color "critical") this.modalDisplay.color}}
data-test-delete-modal-confirm data-test-delete-modal-confirm
> />
Confirm
</button> <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.modalOpen) false)}}>
Cancel </Hds::ButtonSet>
</button> </M.Footer>
</footer> </Hds::Modal>
</Modal>
{{/if}} {{/if}}

View File

@@ -21,6 +21,7 @@ import { assert } from '@ember/debug';
* @param {string} mode - delete, delete-metadata, or destroy. * @param {string} mode - delete, delete-metadata, or destroy.
* @param {object} secret - The kv/data model. * @param {object} secret - The kv/data model.
* @param {object} [metadata] - The kv/metadata model. It is only required when mode is "delete" or "metadata-delete". * @param {object} [metadata] - The kv/metadata model. It is only required when mode is "delete" or "metadata-delete".
* @param {string} [text] - Button text that renders in KV v2 toolbar, defaults to capitalize @mode
* @param {callback} onDelete - callback function fired to handle delete event. * @param {callback} onDelete - callback function fired to handle delete event.
*/ */
@@ -34,20 +35,20 @@ export default class KvDeleteModal extends Component {
case 'delete': case 'delete':
return { return {
title: 'Delete version?', title: 'Delete version?',
type: 'warning', color: 'warning',
intro: intro:
'There are two ways to delete a version of a secret. Both delete actions can be undeleted later. How would you like to proceed?', 'There are two ways to delete a version of a secret. Both delete actions can be undeleted later. How would you like to proceed?',
}; };
case 'destroy': case 'destroy':
return { return {
title: 'Destroy version?', title: 'Destroy version?',
type: 'danger', color: 'critical',
intro: `This action will permanently destroy Version ${this.args.version} of the secret, and the secret data cannot be read or recovered later.`, intro: `This action will permanently destroy Version ${this.args.version} of the secret, and the secret data cannot be read or recovered later.`,
}; };
case 'delete-metadata': case 'delete-metadata':
return { return {
title: 'Delete metadata and secret data?', title: 'Delete metadata and secret data?',
type: 'danger', color: 'critical',
intro: intro:
'This will permanently delete the metadata and versions of the secret. All version history will be removed. This cannot be undone.', 'This will permanently delete the metadata and versions of the secret. All version history will be removed. This cannot be undone.',
}; };

View File

@@ -34,14 +34,10 @@
@metadata={{@metadata}} @metadata={{@metadata}}
@onDelete={{this.handleDestruction}} @onDelete={{this.handleDestruction}}
@version={{this.version}} @version={{this.version}}
> />
Delete
</KvDeleteModal>
{{/if}} {{/if}}
{{#if this.showDestroy}} {{#if this.showDestroy}}
<KvDeleteModal @mode="destroy" @secret={{@secret}} @onDelete={{this.handleDestruction}} @version={{this.version}}> <KvDeleteModal @mode="destroy" @secret={{@secret}} @onDelete={{this.handleDestruction}} @version={{this.version}} />
Destroy
</KvDeleteModal>
{{/if}} {{/if}}
{{#if (or @secret.canReadData @secret.canReadMetadata @secret.canEditData)}} {{#if (or @secret.canReadData @secret.canReadMetadata @secret.canEditData)}}
<div class="toolbar-separator"></div> <div class="toolbar-separator"></div>

View File

@@ -10,9 +10,12 @@
<:toolbarActions> <:toolbarActions>
{{#if @secret.canDeleteMetadata}} {{#if @secret.canDeleteMetadata}}
<KvDeleteModal @mode="delete-metadata" @metadata={{@metadata}} @onDelete={{this.onDelete}}> <KvDeleteModal
Permanently delete @mode="delete-metadata"
</KvDeleteModal> @metadata={{@metadata}}
@onDelete={{this.onDelete}}
@text="Permanently delete"
/>
{{/if}} {{/if}}
{{#if @secret.canUpdateMetadata}} {{#if @secret.canUpdateMetadata}}
<ToolbarLink @route="secret.metadata.edit" data-test-edit-metadata>Edit metadata</ToolbarLink> <ToolbarLink @route="secret.metadata.edit" data-test-edit-metadata>Edit metadata</ToolbarLink>

View File

@@ -39,37 +39,35 @@
</OverviewCard> </OverviewCard>
{{#if this.selectedStatus}} {{#if this.selectedStatus}}
<Modal <Hds::Modal id="account-check-in-modal" @onClose={{fn (mut this.selectedStatus) undefined}} as |M|>
@title="Account Check-in" <M.Header>
@isActive={{this.selectedStatus}} Account Check-in
@showCloseButton={{true}} </M.Header>
@onClose={{fn (mut this.selectedStatus) undefined}} <M.Body>
>
<section class="modal-card-body">
<p> <p>
This action will check-in account This action will check-in account
{{this.selectedStatus.account}} {{this.selectedStatus.account}}
back to the library. Do you want to proceed? back to the library. Do you want to proceed?
</p> </p>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer>
<button <Hds::ButtonSet>
type="button" <Hds::Button
class="button is-primary {{if this.save.isRunning 'is-loading'}}" @icon={{if this.save.isRunning "is-loading"}}
disabled={{this.checkIn.isRunning}} disabled={{this.checkIn.isRunning}}
data-test-check-in-confirm data-test-check-in-confirm
{{on "click" (perform this.checkIn)}} {{on "click" (perform this.checkIn)}}
> @text="Confirm"
Confirm />
</button> <Hds::Button
<button @icon={{if this.save.isRunning "is-loading"}}
type="button" @color="secondary"
class="button"
disabled={{this.checkIn.isRunning}} disabled={{this.checkIn.isRunning}}
{{on "click" (fn (mut this.selectedStatus) "")}} {{on "click" (fn (mut this.selectedStatus) "")}}
> @text="Cancel"
Cancel />
</button>
</footer> </Hds::ButtonSet>
</Modal> </M.Footer>
</Hds::Modal>
{{/if}} {{/if}}

View File

@@ -78,14 +78,11 @@
</form> </form>
{{#if this.showRotatePrompt}} {{#if this.showRotatePrompt}}
<Modal <Hds::Modal id="ldap-rotate-password-modal" @onClose={{fn (mut this.showRotatePrompt) false}} as |M|>
@title="Rotate your root password?" <M.Header @icon="info">
@type="info" Rotate your root password?
@isActive={{this.showRotatePrompt}} </M.Header>
@showCloseButton={{true}} <M.Body>
@onClose={{fn (mut this.showRotatePrompt) false}}
>
<section class="modal-card-body">
<p> <p>
Its best practice to rotate the administrator (root) password immediately after the initial configuration of the Its best practice to rotate the administrator (root) password immediately after the initial configuration of the
LDAP engine. The rotation will update the password both in Vault and your directory server. Once rotated, LDAP engine. The rotation will update the password both in Vault and your directory server. Once rotated,
@@ -95,19 +92,17 @@
<p> <p>
Would you like to rotate your new credentials? You can also do this later. Would you like to rotate your new credentials? You can also do this later.
</p> </p>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer>
<button <Hds::ButtonSet>
data-test-save-with-rotate <Hds::Button data-test-save-with-rotate @text="Save and rotate" {{on "click" (fn (perform this.save) null true)}} />
type="button" <Hds::Button
class="button is-primary" data-test-save-without-rotate
{{on "click" (fn (perform this.save) null true)}} @text="Save without rotating"
> @color="secondary"
Save and rotate {{on "click" (fn (perform this.save) null false)}}
</button> />
<button data-test-save-without-rotate type="button" class="button" {{on "click" (fn (perform this.save) null false)}}> </Hds::ButtonSet>
Save without rotating </M.Footer>
</button> </Hds::Modal>
</footer>
</Modal>
{{/if}} {{/if}}

View File

@@ -54,32 +54,23 @@
</div> </div>
{{#if this.showCheckOutPrompt}} {{#if this.showCheckOutPrompt}}
<Modal <Hds::Modal id="account-check-out-modal" @onClose={{fn (mut this.showCheckOutPrompt) false}} as |M|>
@title="Account Check-out" <M.Header>
@isActive={{this.showCheckOutPrompt}} Account Check-out
@showCloseButton={{true}} </M.Header>
@onClose={{fn (mut this.showCheckOutPrompt) false}} <M.Body>
>
<section class="modal-card-body">
<p> <p>
Current generated credentials time-to-live is set at Current generated credentials time-to-live is set at
{{format-duration @library.ttl}}. You can set a different limit if youd like: {{format-duration @library.ttl}}. You can set a different limit if youd like:
</p> </p>
<br /> <br />
<TtlPicker @label="TTL" @hideToggle={{true}} @initialValue={{@library.ttl}} @onChange={{this.setTtl}} /> <TtlPicker @label="TTL" @hideToggle={{true}} @initialValue={{@library.ttl}} @onChange={{this.setTtl}} />
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button data-test-check-out="save" type="button" class="button is-primary" {{on "click" this.checkOut}}> <Hds::ButtonSet>
Check-out <Hds::Button data-test-check-out="save" @text="Check-out" {{on "click" this.checkOut}} />
</button> <Hds::Button data-test-check-out="cancel" @text="Cancel" @color="secondary" {{on "click" F.close}} />
<button </Hds::ButtonSet>
data-test-check-out="cancel" </M.Footer>
type="button" </Hds::Modal>
class="button"
{{on "click" (fn (mut this.showCheckOutPrompt) false)}}
>
Cancel
</button>
</footer>
</Modal>
{{/if}} {{/if}}

View File

@@ -111,19 +111,18 @@
{{#if this.showDeleteAllIssuers}} {{#if this.showDeleteAllIssuers}}
<ConfirmationModal <ConfirmationModal
@title="Delete All Issuers?" @title="Delete all issuers?"
@toConfirmMsg="deleting all issuers and keys." @toConfirmMsg="deleting all issuers and keys."
@buttonText="Confirm"
@confirmText="delete-all" @confirmText="delete-all"
@isActive={{this.showDeleteAllIssuers}} @isActive={{this.showDeleteAllIssuers}}
@onClose={{action (mut this.showDeleteAllIssuers) false}} @onClose={{action (mut this.showDeleteAllIssuers) false}}
@onConfirm={{this.deleteAllIssuers}} @onConfirm={{this.deleteAllIssuers}}
> >
<section class="modal-card-custom"> <p>
This endpoint deletes This endpoint deletes
<strong>all</strong> <strong>all</strong>
issuers and keys within the mount. It is highly recommended to use the individual delete operations instead. This mount issuers and keys within the mount. It is highly recommended to use the individual delete operations instead. This mount
will be unusable until new issuers and keys are provisioned. will be unusable until new issuers and keys are provisioned.
</section> </p>
</ConfirmationModal> </ConfirmationModal>
{{/if}} {{/if}}

View File

@@ -48,9 +48,9 @@
@data={{@pem}} @data={{@pem}}
@extension="pem" @extension="pem"
data-test-issuer-download-type="pem" data-test-issuer-download-type="pem"
> @text="PEM format"
PEM format @hideIcon={{true}}
</DownloadButton> />
</li> </li>
{{/if}} {{/if}}
{{#if @der}} {{#if @der}}
@@ -62,9 +62,9 @@
@data={{@der}} @data={{@der}}
@extension="der" @extension="der"
data-test-issuer-download-type="der" data-test-issuer-download-type="der"
> @text="DER format"
DER format @hideIcon={{true}}
</DownloadButton> />
</li> </li>
{{/if}} {{/if}}
</ul> </ul>
@@ -148,14 +148,12 @@
{{/if}} {{/if}}
{{! ROOT ROTATION MODAL }} {{! ROOT ROTATION MODAL }}
<Modal {{#if this.showRotationModal}}
@type="rotation" <Hds::Modal id="pki-rotate-root-modal" @size="large" @onClose={{fn (mut this.showRotationModal) false}} as |M|>
@title="Rotate this root" <M.Header @icon="rotate-cw">
@onClose={{fn (mut this.showRotationModal) false}} Rotate this root
@isActive={{this.showRotationModal}} </M.Header>
@showCloseButton={{true}} <M.Body>
>
<section class="modal-card-body">
<h3 class="title is-5">Root rotation</h3> <h3 class="title is-5">Root rotation</h3>
<p class="has-text-grey has-bottom-padding-s"> <p class="has-text-grey has-bottom-padding-s">
Root rotation is an impactful process. Please be ready to ensure that the new root is properly distributed to Root rotation is an impactful process. Please be ready to ensure that the new root is properly distributed to
@@ -175,18 +173,12 @@
<div class="has-top-margin-l has-tall-padding"> <div class="has-top-margin-l has-tall-padding">
<img src={{img-path "~/pki-rotate-root.png"}} alt="pki root rotation diagram" /> <img src={{img-path "~/pki-rotate-root.png"}} alt="pki root rotation diagram" />
</div> </div>
</section> </M.Body>
<footer class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::ButtonSet>
type="button" <Hds::Button @text="Generate new root" @route="issuers.issuer.rotate-root" data-test-root-rotate-step-one />
class="button is-primary" <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.issuers.issuer.rotate-root")}} </Hds::ButtonSet>
data-test-root-rotate-step-one </M.Footer>
> </Hds::Modal>
Generate new root {{/if}}
</button>
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.showRotationModal) false)}}>
Cancel
</button>
</footer>
</Modal>

View File

@@ -53,9 +53,9 @@
@extension="pem" @extension="pem"
@fetchData={{fn this.fetchDataForDownload "pem"}} @fetchData={{fn this.fetchDataForDownload "pem"}}
data-test-issuer-download-type="pem" data-test-issuer-download-type="pem"
> @text="PEM format"
PEM format @hideIcon={{true}}
</DownloadButton> />
</li> </li>
<li class="action"> <li class="action">
<DownloadButton <DownloadButton
@@ -64,9 +64,9 @@
@extension="der" @extension="der"
@fetchData={{fn this.fetchDataForDownload "der"}} @fetchData={{fn this.fetchDataForDownload "der"}}
data-test-issuer-download-type="der" data-test-issuer-download-type="der"
> @text="DER format"
DER format @hideIcon={{true}}
</DownloadButton> />
</li> </li>
</ul> </ul>
</nav> </nav>

View File

@@ -19,14 +19,13 @@
{{/if}} {{/if}}
{{#if @key.privateKey}} {{#if @key.privateKey}}
<DownloadButton <DownloadButton
class="toolbar-link" class="toolbar-button"
@color="secondary"
@filename="{{@key.backend}}-{{or @key.keyName 'private-key'}}" @filename="{{@key.backend}}-{{or @key.keyName 'private-key'}}"
@data={{@key.privateKey}} @data={{@key.privateKey}}
@extension="pem" @extension="pem"
> @text="Download private key"
Download private key />
<Chevron @isButton={{true}} />
</DownloadButton>
{{/if}} {{/if}}
{{#if @canEdit}} {{#if @canEdit}}
<ToolbarLink @route="keys.key.edit" @model={{@key.keyId}} data-test-pki-key-edit> <ToolbarLink @route="keys.key.edit" @model={{@key.keyId}} data-test-pki-key-edit>

View File

@@ -6,7 +6,7 @@
<PkiPaginatedList @listRoute="keys.index" @list={{@keyModels}} @hasConfig={{@hasConfig}}> <PkiPaginatedList @listRoute="keys.index" @list={{@keyModels}} @hasConfig={{@hasConfig}}>
<:actions> <:actions>
{{#if @canImportKey}} {{#if @canImportKey}}
<ToolbarLink @route="keys.import" @type="download" data-test-pki-key-import> <ToolbarLink @route="keys.import" @type="upload" data-test-pki-key-import>
Import Import
</ToolbarLink> </ToolbarLink>
{{/if}} {{/if}}

View File

@@ -14,15 +14,15 @@
Perform manual tidy Perform manual tidy
</ToolbarLink> </ToolbarLink>
{{else}} {{else}}
<button <Hds::Button
type="button" class="toolbar-button"
class="toolbar-link" @color="secondary"
@icon="chevron-right"
@iconPosition="trailing"
{{on "click" (fn (mut this.tidyOptionsModal) true)}} {{on "click" (fn (mut this.tidyOptionsModal) true)}}
data-test-pki-tidy-options-modal data-test-pki-tidy-options-modal
> @text="Tidy"
Tidy />
<Icon @name="chevron-right" />
</button>
{{/if}} {{/if}}
</ToolbarActions> </ToolbarActions>
</Toolbar> </Toolbar>
@@ -101,25 +101,24 @@
@title="Tidy status unavailable" @title="Tidy status unavailable"
@message="After the next tidy operation has been performed, information about the current or most recent tidy operation will display here." @message="After the next tidy operation has been performed, information about the current or most recent tidy operation will display here."
> >
<button <Hds::Button
type="button" @color="tertiary"
class="link" @icon="chevron-right"
@iconPosition="trailing"
{{on "click" (fn (mut this.tidyOptionsModal) true)}} {{on "click" (fn (mut this.tidyOptionsModal) true)}}
data-test-tidy-empty-state-configure data-test-tidy-empty-state-configure
> @text="Tidy"
Tidy />
</button>
</EmptyState> </EmptyState>
{{/if}} {{/if}}
{{! TIDY OPTIONS MODAL }} {{! TIDY OPTIONS MODAL }}
<Modal {{#if this.tidyOptionsModal}}
@title="Tidy this mount" <Hds::Modal id="pki-tidy-modal" @size="large" @onClose={{fn (mut this.tidyOptionsModal) false}} as |M|>
@onClose={{fn (mut this.tidyOptionsModal) false}} <M.Header>
@isActive={{this.tidyOptionsModal}} Tidy this mount
@showCloseButton={{true}} </M.Header>
> <M.Body>
<section aria-label="tidy-options-modal-content" class="modal-card-body">
<h3 class="title is-5">How tidying will work</h3> <h3 class="title is-5">How tidying will work</h3>
<p class="has-text-grey has-bottom-padding-s"> <p class="has-text-grey has-bottom-padding-s">
Tidying cleans up the storage backend and/or CRL by removing certificates that have expired and are past a certain Tidying cleans up the storage backend and/or CRL by removing certificates that have expired and are past a certain
@@ -144,61 +143,33 @@
<div class="has-top-margin-l has-padding"> <div class="has-top-margin-l has-padding">
<img src={{img-path "~/pki-tidy.png"}} alt="tidy operation diagram" /> <img src={{img-path "~/pki-tidy.png"}} alt="tidy operation diagram" />
</div> </div>
</section> </M.Body>
<footer aria-label="tidy-option-buttons" class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::ButtonSet>
type="button" <Hds::Button @text="Automatic tidy" @route="tidy.auto.configure" data-test-tidy-modal-auto-button />
class="button is-primary" <Hds::Button @text="Manual tidy" @route="tidy.manual" data-test-tidy-modal-manual-button />
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.tidy.auto.configure")}} <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-tidy-modal-cancel-button />
data-test-tidy-modal-auto-button </Hds::ButtonSet>
> </M.Footer>
Automatic tidy </Hds::Modal>
</button> {{/if}}
<button
type="button"
class="button is-primary"
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.tidy.manual")}}
data-test-tidy-modal-manual-button
>
Manual tidy
</button>
<button
type="button"
class="button is-secondary"
{{on "click" (fn (mut this.tidyOptionsModal) false)}}
data-test-tidy-modal-cancel-button
>
Cancel
</button>
</footer>
</Modal>
{{! CANCEL TIDY CONFIRMATION MODAL }} {{! CANCEL TIDY CONFIRMATION MODAL }}
{{#if this.confirmCancelTidy}} {{#if this.confirmCancelTidy}}
<Modal <Hds::Modal id="pki-cancel-tidy-modal" @color="warning" @onClose={{fn (mut this.confirmCancelTidy) false}} as |M|>
@type="warning" <M.Header @icon="alert-triangle">
@title="Cancel tidy?" Cancel tidy?
@onClose={{fn (mut this.confirmCancelTidy) false}} </M.Header>
@isActive={{this.confirmCancelTidy}} <M.Body>
@showCloseButton={{true}}
>
<section aria-label="confirm-cancel-modal-content" class="modal-card-body">
This will cancel the tidy at the next available checkpoint, which may process additional certificates between when the This will cancel the tidy at the next available checkpoint, which may process additional certificates between when the
operation was marked as cancelled and when the operation stopped. operation was marked as cancelled and when the operation stopped.
<p class="has-top-margin-s">Click “Confirm” to cancel the running tidy operation.</p> <p class="has-top-margin-s">Click “Confirm” to cancel the running tidy operation.</p>
</section> </M.Body>
<footer aria-label="confirm-cancel-buttons" class="modal-card-foot modal-card-foot-outlined"> <M.Footer as |F|>
<button <Hds::ButtonSet>
type="button" <Hds::Button @text="Confirm" {{on "click" (perform this.cancelTidy)}} data-test-tidy-modal-cancel-button />
class="button is-primary" <Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
{{on "click" (perform this.cancelTidy)}} </Hds::ButtonSet>
data-test-tidy-modal-cancel-button </M.Footer>
> </Hds::Modal>
Confirm
</button>
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.confirmCancelTidy) false)}}>
Cancel
</button>
</footer>
</Modal>
{{/if}} {{/if}}

View File

@@ -26,11 +26,11 @@ const DEFAULTS = {
export default Controller.extend(copy(DEFAULTS, true), { export default Controller.extend(copy(DEFAULTS, true), {
isModalActive: false, isModalActive: false,
isTokenCopied: false,
expirationDate: null, expirationDate: null,
store: service(), store: service(),
rm: service('replication-mode'), rm: service('replication-mode'),
replicationMode: alias('rm.mode'), replicationMode: alias('rm.mode'),
flashMessages: service(),
submitError(e) { submitError(e) {
if (e.errors) { if (e.errors) {
@@ -121,25 +121,13 @@ export default Controller.extend(copy(DEFAULTS, true), {
onSubmit(/*action, mode, data, event*/) { onSubmit(/*action, mode, data, event*/) {
return this.submitHandler(...arguments); return this.submitHandler(...arguments);
}, },
copyClose(successMessage) { closeTokenModal() {
// separate action for copy & close button so it does not try and use execCommand to copy token to clipboard
if (!!successMessage && typeof successMessage === 'string') {
this.flashMessages.success(successMessage);
}
this.toggleProperty('isModalActive'); this.toggleProperty('isModalActive');
this.transitionToRoute('mode.secondaries'); this.transitionToRoute('mode.secondaries');
this.set('isTokenCopied', false);
}, },
toggleModal(successMessage) { onCopy() {
if (!!successMessage && typeof successMessage === 'string') { this.set('isTokenCopied', true);
this.flashMessages.success(successMessage);
}
// use copy browser extension to copy token if you close the modal by clicking outside of it.
const htmlSelectedToken = document.querySelector('#token-textarea');
htmlSelectedToken.select();
document.execCommand('copy');
this.toggleProperty('isModalActive');
this.transitionToRoute('mode.secondaries');
}, },
clear() { clear() {
this.reset(); this.reset();

View File

@@ -60,8 +60,16 @@
</form> </form>
{{#if this.isModalActive}} {{#if this.isModalActive}}
<Modal @title="Copy your token" @onClose={{action "toggleModal" "Token copied!"}} @isActive={{this.isModalActive}}> <Hds::Modal
<section class="modal-card-body"> id="replication-copy-token-modal"
@onClose={{action "closeTokenModal"}}
@isDismissDisabled={{not this.isTokenCopied}}
as |M|
>
<M.Header>
Copy your token
</M.Header>
<M.Body>
<p> <p>
This token can be used to enable This token can be used to enable
{{this.model.replicationModeForDisplay}} {{this.model.replicationModeForDisplay}}
@@ -79,14 +87,28 @@
<InfoTableRow @label="Expires" @value={{date-format this.expirationDate "MMM dd, yyyy hh:mm:ss a"}} /> <InfoTableRow @label="Expires" @value={{date-format this.expirationDate "MMM dd, yyyy hh:mm:ss a"}} />
</div> </div>
</div> </div>
</section> </M.Body>
<footer class="modal-card-foot"> <M.Footer>
<Hds::ButtonSet>
<Hds::Copy::Button <Hds::Copy::Button
@text="Copy & Close" data-test-modal-copy
@text="Copy token"
@textToCopy={{this.token}} @textToCopy={{this.token}}
class="primary" class="primary"
{{on "click" (action "copyClose" "Token copied!")}} @container=".hds-modal"
{{on "click" (action "onCopy")}}
/> />
</footer> <Hds::Button
</Modal> data-test-modal-close
disabled={{not this.isTokenCopied}}
@text="Close"
@color="secondary"
{{on "click" (action "closeTokenModal")}}
/>
{{#unless this.isTokenCopied}}
<AlertInline @type="warning" @message="Copy token to dismiss modal" />
{{/unless}}
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}} {{/if}}

View File

@@ -65,7 +65,6 @@
"@ember/test-waiters": "^3.0.0", "@ember/test-waiters": "^3.0.0",
"@glimmer/component": "^1.1.2", "@glimmer/component": "^1.1.2",
"@glimmer/tracking": "^1.1.2", "@glimmer/tracking": "^1.1.2",
"@hashicorp/ember-flight-icons": "^3.0.9",
"@hashicorp/structure-icons": "^1.3.0", "@hashicorp/structure-icons": "^1.3.0",
"@icholy/duration": "^5.1.0", "@icholy/duration": "^5.1.0",
"@tsconfig/ember": "^1.0.1", "@tsconfig/ember": "^1.0.1",
@@ -166,7 +165,6 @@
"ember-test-selectors": "6.0.0", "ember-test-selectors": "6.0.0",
"ember-tether": "^2.0.1", "ember-tether": "^2.0.1",
"ember-truth-helpers": "3.0.0", "ember-truth-helpers": "3.0.0",
"ember-wormhole": "0.6.0",
"escape-string-regexp": "^2.0.0", "escape-string-regexp": "^2.0.0",
"eslint": "^8.37.0", "eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
@@ -248,7 +246,8 @@
] ]
}, },
"dependencies": { "dependencies": {
"@hashicorp/design-system-components": "^2.9.0", "@hashicorp/design-system-components": "^2.12.2",
"@hashicorp/ember-flight-icons": "^3.1.3",
"handlebars": "4.7.7", "handlebars": "4.7.7",
"highlight.js": "^10.4.1", "highlight.js": "^10.4.1",
"node-notifier": "^8.0.1", "node-notifier": "^8.0.1",

View File

@@ -390,9 +390,9 @@ module('Acceptance | client counts dashboard tab', function (hooks) {
.dom(SELECTORS.emptyStateTitle) .dom(SELECTORS.emptyStateTitle)
.includesText('start date found', 'Empty state shows no billing start date'); .includesText('start date found', 'Empty state shows no billing start date');
await click(SELECTORS.monthDropdown); await click(SELECTORS.monthDropdown);
await click(this.element.querySelector('[data-test-month-list] button:not([disabled])')); await click(this.element.querySelector('[data-test-dropdown-month]:not([disabled])'));
await click(SELECTORS.yearDropdown); await click(SELECTORS.yearDropdown);
await click(this.element.querySelector('[data-test-year-list] button:not([disabled])')); await click(this.element.querySelector('[data-test-dropdown-year]:not([disabled])'));
await click(SELECTORS.dateDropdownSubmit); await click(SELECTORS.dateDropdownSubmit);
assert assert
.dom(SELECTORS.emptyStateTitle) .dom(SELECTORS.emptyStateTitle)

View File

@@ -250,11 +250,16 @@ module('Acceptance | Enterprise | replication', function (hooks) {
await pollCluster(this.owner); await pollCluster(this.owner);
await settled(); await settled();
const modalDefaultTtl = document.querySelector('[data-test-row-value="TTL"]').innerText; const modalDefaultTtl = document.querySelector('[data-test-row-value="TTL"]').innerText;
// checks on secondary token modal // checks on secondary token modal
assert.dom('#modal-wormhole').exists(); assert.dom('.hds-modal#replication-copy-token-modal').exists();
assert.dom('[data-test-inline-error-message]').hasText('Copy token to dismiss modal');
assert.strictEqual(modalDefaultTtl, '1800s', 'shows the correct TTL of 1800s'); assert.strictEqual(modalDefaultTtl, '1800s', 'shows the correct TTL of 1800s');
// click off the modal to make sure you don't just have to click on the copy-close button to copy the token // click off the modal to make sure you don't just have to click on the copy-close button to copy the token
await click('[data-test-modal-background="Copy your token"]'); assert.dom('[data-test-modal-close]').isDisabled('cancel is disabled');
await click('[data-test-modal-copy]');
assert.dom('[data-test-modal-close]').isEnabled('cancel is enabled after token is copied');
await click('[data-test-modal-close]');
// add another secondary not using the default ttl // add another secondary not using the default ttl
await click('[data-test-secondary-add]'); await click('[data-test-secondary-add]');
@@ -269,7 +274,8 @@ module('Acceptance | Enterprise | replication', function (hooks) {
await settled(); await settled();
const modalTtl = document.querySelector('[data-test-row-value="TTL"]').innerText; const modalTtl = document.querySelector('[data-test-row-value="TTL"]').innerText;
assert.strictEqual(modalTtl, '180s', 'shows the correct TTL of 180s'); assert.strictEqual(modalTtl, '180s', 'shows the correct TTL of 180s');
await click('[data-test-modal-background="Copy your token"]'); await click('[data-test-modal-copy]');
await click('[data-test-modal-close]');
// confirm you were redirected to the secondaries page // confirm you were redirected to the secondaries page
assert.strictEqual( assert.strictEqual(

View File

@@ -432,7 +432,7 @@ module('Acceptance | pki workflow', function (hooks) {
.dom(SELECTORS.issuerDetails.configure) .dom(SELECTORS.issuerDetails.configure)
.hasAttribute('href', `/ui/vault/secrets/${this.mountPath}/pki/issuers/${issuerId}/edit`); .hasAttribute('href', `/ui/vault/secrets/${this.mountPath}/pki/issuers/${issuerId}/edit`);
await click(SELECTORS.issuerDetails.rotateRoot); await click(SELECTORS.issuerDetails.rotateRoot);
assert.dom(find(SELECTORS.issuerDetails.rotateModal).parentElement).hasClass('is-active'); assert.dom(SELECTORS.issuerDetails.rotateModal).exists('rotate root modal opens');
await click(SELECTORS.issuerDetails.rotateModalGenerate); await click(SELECTORS.issuerDetails.rotateModalGenerate);
assert.strictEqual( assert.strictEqual(
currentURL(), currentURL(),

View File

@@ -163,7 +163,6 @@ module('Acceptance | pki tidy', function (hooks) {
.exists('Configure tidy modal options button exists'); .exists('Configure tidy modal options button exists');
await click(SELECTORS.tidyConfigureModal.tidyOptionsModal); await click(SELECTORS.tidyConfigureModal.tidyOptionsModal);
assert.dom(SELECTORS.tidyConfigureModal.configureTidyModal).exists('Configure tidy modal exists'); assert.dom(SELECTORS.tidyConfigureModal.configureTidyModal).exists('Configure tidy modal exists');
await click(SELECTORS.tidyConfigureModal.tidyOptionsModal);
await click(SELECTORS.tidyConfigureModal.tidyModalAutoButton); await click(SELECTORS.tidyConfigureModal.tidyModalAutoButton);
await click(SELECTORS.tidyForm.toggleLabel('Automatic tidy disabled')); await click(SELECTORS.tidyForm.toggleLabel('Automatic tidy disabled'));
await click(SELECTORS.tidyForm.inputByAttr('tidyCertStore')); await click(SELECTORS.tidyForm.inputByAttr('tidyCertStore'));

View File

@@ -281,7 +281,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
await connectionPage.save(); await connectionPage.save();
await settled(); await settled();
assert assert
.dom('.modal.is-active .title') .dom('[data-test-db-connection-modal-title]')
.hasText('Rotate your root credentials?', 'Modal appears asking to rotate root credentials'); .hasText('Rotate your root credentials?', 'Modal appears asking to rotate root credentials');
assert.dom('[data-test-enable-connection]').exists('Enable button exists'); assert.dom('[data-test-enable-connection]').exists('Enable button exists');
await click('[data-test-enable-connection]'); await click('[data-test-enable-connection]');
@@ -397,7 +397,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
await connectionPage.save(); await connectionPage.save();
await settled(); await settled();
assert assert
.dom('.modal.is-active .title') .dom('[data-test-db-connection-modal-title]')
.hasText('Rotate your root credentials?', 'Modal appears asking to '); .hasText('Rotate your root credentials?', 'Modal appears asking to ');
await connectionPage.enable(); await connectionPage.enable();
assert.strictEqual( assert.strictEqual(
@@ -418,7 +418,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
}); });
await connectionPage.delete(); await connectionPage.delete();
assert assert
.dom('.modal.is-active .title') .dom('[data-test-confirmation-modal-title]')
.hasText('Delete connection?', 'Modal appears asking to confirm delete action'); .hasText('Delete connection?', 'Modal appears asking to confirm delete action');
await fillIn('[data-test-confirmation-modal-input="Delete connection?"]', connectionDetails.id); await fillIn('[data-test-confirmation-modal-input="Delete connection?"]', connectionDetails.id);
await click('[data-test-confirm-button]'); await click('[data-test-confirm-button]');

View File

@@ -37,14 +37,14 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: false, encodePlaintext: false,
encodeContext: false, encodeContext: false,
assertAfterEncrypt: (key) => { assertAfterEncrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`); assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
assert.ok( assert.ok(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText), /vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext` `${key}: ciphertext shows a vault-prefixed ciphertext`
); );
}, },
assertBeforeDecrypt: (key) => { assertBeforeDecrypt: (key) => {
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`); assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
assert assert
.dom('[data-test-transit-input="context"]') .dom('[data-test-transit-input="context"]')
.hasValue( .hasValue(
@@ -52,9 +52,8 @@ const testConvergentEncryption = async function (assert, keyName) {
`${key}: the ui shows the base64-encoded context` `${key}: the ui shows the base64-encoded context`
); );
}, },
assertAfterDecrypt: (key) => { assertAfterDecrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`); assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
assert.strictEqual( assert.strictEqual(
find('[data-test-encrypted-value="plaintext"]').innerText, find('[data-test-encrypted-value="plaintext"]').innerText,
'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=', 'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=',
@@ -69,20 +68,20 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: false, encodePlaintext: false,
encodeContext: false, encodeContext: false,
assertAfterEncrypt: (key) => { assertAfterEncrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`); assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
assert.ok( assert.ok(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText), /vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext` `${key}: ciphertext shows a vault-prefixed ciphertext`
); );
}, },
assertBeforeDecrypt: (key) => { assertBeforeDecrypt: (key) => {
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`); assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
assert assert
.dom('[data-test-transit-input="context"]') .dom('[data-test-transit-input="context"]')
.hasValue(encodeString('context'), `${key}: the ui shows the input context`); .hasValue(encodeString('context'), `${key}: the ui shows the input context`);
}, },
assertAfterDecrypt: (key) => { assertAfterDecrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`); assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
assert.strictEqual( assert.strictEqual(
find('[data-test-encrypted-value="plaintext"]').innerText, find('[data-test-encrypted-value="plaintext"]').innerText,
'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=', 'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=',
@@ -97,20 +96,20 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: false, encodePlaintext: false,
encodeContext: false, encodeContext: false,
assertAfterEncrypt: (key) => { assertAfterEncrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`); assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
assert.ok( assert.ok(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText), /vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext` `${key}: ciphertext shows a vault-prefixed ciphertext`
); );
}, },
assertBeforeDecrypt: (key) => { assertBeforeDecrypt: (key) => {
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`); assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
assert assert
.dom('[data-test-transit-input="context"]') .dom('[data-test-transit-input="context"]')
.hasValue(encodeString('context'), `${key}: the ui shows the input context`); .hasValue(encodeString('context'), `${key}: the ui shows the input context`);
}, },
assertAfterDecrypt: (key) => { assertAfterDecrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`); assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
assert.strictEqual( assert.strictEqual(
find('[data-test-encrypted-value="plaintext"]').innerText, find('[data-test-encrypted-value="plaintext"]').innerText,
encodeString('This is the secret'), encodeString('This is the secret'),
@@ -125,20 +124,20 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: true, encodePlaintext: true,
encodeContext: true, encodeContext: true,
assertAfterEncrypt: (key) => { assertAfterEncrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after encrypt`); assert.dom('[data-test-encrypt-modal]').exists(`${key}: Modal opens after encrypt`);
assert.ok( assert.ok(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText), /vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext` `${key}: ciphertext shows a vault-prefixed ciphertext`
); );
}, },
assertBeforeDecrypt: (key) => { assertBeforeDecrypt: (key) => {
assert.dom('.modal.is-active').doesNotExist(`${key}: Modal not open before decrypt`); assert.dom('[data-test-decrypt-modal]').doesNotExist(`${key}: Modal not open before decrypt`);
assert assert
.dom('[data-test-transit-input="context"]') .dom('[data-test-transit-input="context"]')
.hasValue(encodeString('secret 2'), `${key}: the ui shows the encoded context`); .hasValue(encodeString('secret 2'), `${key}: the ui shows the encoded context`);
}, },
assertAfterDecrypt: (key) => { assertAfterDecrypt: (key) => {
assert.dom('.modal.is-active').exists(`${key}: Modal opens after decrypt`); assert.dom('[data-test-decrypt-modal]').exists(`${key}: Modal opens after decrypt`);
assert.strictEqual( assert.strictEqual(
find('[data-test-encrypted-value="plaintext"]').innerText, find('[data-test-encrypted-value="plaintext"]').innerText,
encodeString('There are many secrets 🤐'), encodeString('There are many secrets 🤐'),
@@ -161,7 +160,7 @@ const testConvergentEncryption = async function (assert, keyName) {
if (testCase.encodeContext) { if (testCase.encodeContext) {
await click('[data-test-transit-b64-toggle="context"]'); await click('[data-test-transit-b64-toggle="context"]');
} }
assert.dom('.modal.is-active').doesNotExist(`${name}: is not open before encrypt`); assert.dom('[data-test-encrypt-modal]').doesNotExist(`${name}: is not open before encrypt`);
await click('[data-test-button-encrypt]'); await click('[data-test-button-encrypt]');
if (testCase.assertAfterEncrypt) { if (testCase.assertAfterEncrypt) {
@@ -170,9 +169,9 @@ const testConvergentEncryption = async function (assert, keyName) {
} }
// store ciphertext for decryption step // store ciphertext for decryption step
const copiedCiphertext = find('[data-test-encrypted-value="ciphertext"]').innerText; const copiedCiphertext = find('[data-test-encrypted-value="ciphertext"]').innerText;
await click('.modal.is-active [data-test-modal-background]'); await click('dialog button');
assert.dom('.modal.is-active').doesNotExist(`${name}: Modal closes after background clicked`); assert.dom('dialog.hds-modal').doesNotExist(`${name}: Modal closes after background clicked`);
await click('[data-test-transit-action-link="decrypt"]'); await click('[data-test-transit-action-link="decrypt"]');
if (testCase.assertBeforeDecrypt) { if (testCase.assertBeforeDecrypt) {
@@ -187,9 +186,9 @@ const testConvergentEncryption = async function (assert, keyName) {
testCase.assertAfterDecrypt(keyName); testCase.assertAfterDecrypt(keyName);
} }
await click('.modal.is-active [data-test-modal-background]'); await click('dialog button');
assert.dom('.modal.is-active').doesNotExist(`${name}: Modal closes after background clicked`); assert.dom('dialog.hds-modal').doesNotExist(`${name}: Modal closes after background clicked`);
} }
}; };

View File

@@ -29,8 +29,8 @@ export const SELECTORS = {
attributionBlock: '[data-test-clients-attribution]', attributionBlock: '[data-test-clients-attribution]',
filterBar: '[data-test-clients-filter-bar]', filterBar: '[data-test-clients-filter-bar]',
rangeDropdown: '[data-test-calendar-widget-trigger]', rangeDropdown: '[data-test-calendar-widget-trigger]',
monthDropdown: '[data-test-popup-menu-trigger="month"]', monthDropdown: '[data-test-toggle-month]',
yearDropdown: '[data-test-popup-menu-trigger="year"]', yearDropdown: '[data-test-toggle-year]',
dateDropdownSubmit: '[data-test-date-dropdown-submit]', dateDropdownSubmit: '[data-test-date-dropdown-submit]',
runningTotalMonthStats: '[data-test-running-total="single-month-stats"]', runningTotalMonthStats: '[data-test-running-total="single-month-stats"]',
runningTotalMonthlyCharts: '[data-test-running-total="monthly-charts"]', runningTotalMonthlyCharts: '[data-test-running-total="monthly-charts"]',

View File

@@ -22,7 +22,7 @@ export const PAGE = {
message: '[data-test-page-error] p', message: '[data-test-page-error] p',
}, },
toolbar: 'nav.toolbar', toolbar: 'nav.toolbar',
toolbarAction: 'nav.toolbar-actions .toolbar-link', toolbarAction: 'nav.toolbar-actions .toolbar-link, nav.toolbar-actions .toolbar-button',
secretRow: '[data-test-component="info-table-row"]', // replace with infoRow secretRow: '[data-test-component="info-table-row"]', // replace with infoRow
// specific page selectors // specific page selectors
backends: { backends: {

View File

@@ -12,12 +12,12 @@ export const SELECTORS = {
hdsAlertButtonText: '[data-test-cancel-tidy-action] .hds-button__text', hdsAlertButtonText: '[data-test-cancel-tidy-action] .hds-button__text',
timeStartedRow: '[data-test-value-div="Time started"]', timeStartedRow: '[data-test-value-div="Time started"]',
timeFinishedRow: '[data-test-value-div="Time finished"]', timeFinishedRow: '[data-test-value-div="Time finished"]',
cancelTidyModalBackground: '[data-test-modal-background="Cancel tidy?"]', cancelTidyModalBackground: '.hds-modal#pki-cancel-tidy-modal',
tidyEmptyStateConfigure: '[data-test-tidy-empty-state-configure]', tidyEmptyStateConfigure: '[data-test-tidy-empty-state-configure]',
manualTidyToolbar: '[data-test-pki-manual-tidy-config]', manualTidyToolbar: '[data-test-pki-manual-tidy-config]',
autoTidyToolbar: '[data-test-pki-auto-tidy-config]', autoTidyToolbar: '[data-test-pki-auto-tidy-config]',
tidyConfigureModal: { tidyConfigureModal: {
configureTidyModal: '[data-test-modal-background="Tidy this mount"]', configureTidyModal: '.hds-modal#pki-tidy-modal',
tidyModalAutoButton: '[data-test-tidy-modal-auto-button]', tidyModalAutoButton: '[data-test-tidy-modal-auto-button]',
tidyModalManualButton: '[data-test-tidy-modal-manual-button]', tidyModalManualButton: '[data-test-tidy-modal-manual-button]',
tidyModalCancelButton: '[data-test-tidy-modal-cancel-button]', tidyModalCancelButton: '[data-test-tidy-modal-cancel-button]',

View File

@@ -5,7 +5,7 @@
export const SELECTORS = { export const SELECTORS = {
issuerLink: '[data-test-delete-all-issuers-link]', issuerLink: '[data-test-delete-all-issuers-link]',
deleteAllIssuerModal: '[data-test-modal-background="Delete All Issuers?"]', deleteAllIssuerModal: '.hds-modal#confirmation-modal',
deleteAllIssuerInput: '[data-test-confirmation-modal-input="Delete All Issuers?"]', deleteAllIssuerInput: '[data-test-confirmation-modal-input="Delete all issuers?"]',
deleteAllIssuerButton: '[data-test-confirm-button="Delete All Issuers?"]', deleteAllIssuerButton: '[data-test-confirm-button="Delete all issuers?"]',
}; };

View File

@@ -11,7 +11,7 @@ export const SELECTORS = {
download: '[data-test-issuer-download]', download: '[data-test-issuer-download]',
groupTitle: '[data-test-group-title]', groupTitle: '[data-test-group-title]',
parsingAlertBanner: '[data-test-parsing-error-alert-banner]', parsingAlertBanner: '[data-test-parsing-error-alert-banner]',
rotateModal: '[data-test-modal-background="Rotate this root"]', rotateModal: '.hds-modal#pki-rotate-root-modal',
rotateModalGenerate: '[data-test-root-rotate-step-one]', rotateModalGenerate: '[data-test-root-rotate-step-one]',
rotateRoot: '[data-test-pki-issuer-rotate-root]', rotateRoot: '[data-test-pki-issuer-rotate-root]',
row: '[data-test-component="info-table-row"]', row: '[data-test-component="info-table-row"]',

View File

@@ -26,7 +26,6 @@
<div id="qunit-fixture"> <div id="qunit-fixture">
<div id="ember-testing-container"> <div id="ember-testing-container">
<div id="ember-testing"></div> <div id="ember-testing"></div>
<div id="modal-wormhole"></div>
</div> </div>
</div> </div>

View File

@@ -46,7 +46,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
test('it renders empty state with no data', async function (assert) { test('it renders empty state with no data', async function (assert) {
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution @chartLegend={{this.chartLegend}} /> <Clients::Attribution @chartLegend={{this.chartLegend}} />
`); `);
@@ -59,7 +58,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
test('it renders with data for namespaces', async function (assert) { test('it renders with data for namespaces', async function (assert) {
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution <Clients::Attribution
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.totalClientAttribution}} @totalClientAttribution={{this.totalClientAttribution}}
@@ -93,7 +91,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
this.start = formatRFC3339(subMonths(this.mockNow, 1)); this.start = formatRFC3339(subMonths(this.mockNow, 1));
this.end = formatRFC3339(subMonths(endOfMonth(this.mockNow), 1)); this.end = formatRFC3339(subMonths(endOfMonth(this.mockNow), 1));
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution <Clients::Attribution
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.totalClientAttribution}} @totalClientAttribution={{this.totalClientAttribution}}
@@ -147,7 +144,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
test('it renders single chart for current month', async function (assert) { test('it renders single chart for current month', async function (assert) {
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution <Clients::Attribution
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.totalClientAttribution}} @totalClientAttribution={{this.totalClientAttribution}}
@@ -169,7 +165,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
test('it renders single chart and correct text for for date range', async function (assert) { test('it renders single chart and correct text for for date range', async function (assert) {
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution <Clients::Attribution
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.totalClientAttribution}} @totalClientAttribution={{this.totalClientAttribution}}
@@ -193,7 +188,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
test('it renders with data for selected namespace auth methods for a date range', async function (assert) { test('it renders with data for selected namespace auth methods for a date range', async function (assert) {
this.set('selectedNamespace', 'second'); this.set('selectedNamespace', 'second');
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution <Clients::Attribution
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.namespaceMountsData}} @totalClientAttribution={{this.namespaceMountsData}}
@@ -225,7 +219,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
test('it renders modal', async function (assert) { test('it renders modal', async function (assert) {
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution <Clients::Attribution
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.namespaceMountsData}} @totalClientAttribution={{this.namespaceMountsData}}
@@ -235,7 +228,9 @@ module('Integration | Component | clients/attribution', function (hooks) {
/> />
`); `);
await click('[data-test-attribution-export-button]'); await click('[data-test-attribution-export-button]');
assert.dom('.modal.is-active .title').hasText('Export attribution data', 'modal appears to export csv'); assert
assert.dom('.modal.is-active').includesText('June 2022 - December 2022'); .dom('[data-test-export-modal-title]')
.hasText('Export attribution data', 'modal appears to export csv');
assert.dom('[ data-test-export-date-range]').includesText('June 2022 - December 2022');
}); });
}); });

View File

@@ -52,7 +52,7 @@ module('Integration | Component | client count config', function (hooks) {
}); });
test('it should function in edit mode when reporting is disabled', async function (assert) { test('it should function in edit mode when reporting is disabled', async function (assert) {
assert.expect(13); assert.expect(12);
this.server.put('/sys/internal/counters/config', (schema, req) => { this.server.put('/sys/internal/counters/config', (schema, req) => {
const { enabled, retention_months } = JSON.parse(req.requestBody); const { enabled, retention_months } = JSON.parse(req.requestBody);
@@ -64,7 +64,6 @@ module('Integration | Component | client count config', function (hooks) {
this.createModel('disable'); this.createModel('disable');
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Config @model={{this.model}} @mode="edit" /> <Clients::Config @model={{this.model}} @mode="edit" />
`); `);
@@ -86,9 +85,8 @@ module('Integration | Component | client count config', function (hooks) {
await fillIn('[data-test-input="retentionMonths"]', 24); await fillIn('[data-test-input="retentionMonths"]', 24);
await click('[data-test-clients-config-save]'); await click('[data-test-clients-config-save]');
assert.dom('.modal.is-active').exists('Modal renders');
assert assert
.dom('[data-test-modal-title] span') .dom('[data-test-clients-config-modal="title"]')
.hasText('Turn usage tracking on?', 'Correct modal title renders'); .hasText('Turn usage tracking on?', 'Correct modal title renders');
assert.dom('[data-test-clients-config-modal="on"]').exists('Correct modal description block renders'); assert.dom('[data-test-clients-config-modal="on"]').exists('Correct modal description block renders');
@@ -100,14 +98,14 @@ module('Integration | Component | client count config', function (hooks) {
await click('[data-test-input="enabled"]'); await click('[data-test-input="enabled"]');
await click('[data-test-clients-config-save]'); await click('[data-test-clients-config-save]');
assert.dom('.modal.is-active').exists('Modal renders'); assert.dom('[data-test-clients-config-modal]').exists('Modal renders');
assert assert
.dom('[data-test-modal-title] span') .dom('[data-test-clients-config-modal="title"]')
.hasText('Turn usage tracking off?', 'Correct modal title renders'); .hasText('Turn usage tracking off?', 'Correct modal title renders');
assert.dom('[data-test-clients-config-modal="off"]').exists('Correct modal description block renders'); assert.dom('[data-test-clients-config-modal="off"]').exists('Correct modal description block renders');
await click('[data-test-clients-config-modal="cancel"]'); await click('[data-test-clients-config-modal="cancel"]');
assert.dom('.modal.is-active').doesNotExist('Modal is hidden on cancel'); assert.dom('[data-test-clients-config-modal]').doesNotExist('Modal is hidden on cancel');
}); });
test('it should function in edit mode when reporting is enabled', async function (assert) { test('it should function in edit mode when reporting is enabled', async function (assert) {
@@ -123,7 +121,6 @@ module('Integration | Component | client count config', function (hooks) {
this.createModel('enable', true, 24); this.createModel('enable', true, 24);
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Config @model={{this.model}} @mode="edit" /> <Clients::Config @model={{this.model}} @mode="edit" />
`); `);
@@ -162,7 +159,6 @@ module('Integration | Component | client count config', function (hooks) {
this.createModel(); this.createModel();
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Config @model={{this.model}} @mode="edit" /> <Clients::Config @model={{this.model}} @mode="edit" />
`); `);
await fillIn('[data-test-input="retentionMonths"]', 24); await fillIn('[data-test-input="retentionMonths"]', 24);

View File

@@ -1433,7 +1433,6 @@ module('Integration | Component | clients/monthly-usage', function (hooks) {
test('it renders empty state with no data', async function (assert) { test('it renders empty state with no data', async function (assert) {
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::MonthlyUsage @chartLegend={{this.chartLegend}} @responseTimestamp={{this.timestamp}}/> <Clients::MonthlyUsage @chartLegend={{this.chartLegend}} @responseTimestamp={{this.timestamp}}/>
`); `);
assert.dom('[data-test-monthly-usage]').exists('monthly usage component renders'); assert.dom('[data-test-monthly-usage]').exists('monthly usage component renders');
@@ -1455,7 +1454,6 @@ module('Integration | Component | clients/monthly-usage', function (hooks) {
), ),
]); ]);
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::MonthlyUsage <Clients::MonthlyUsage
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@verticalBarChartData={{this.byMonthActivityData}} @verticalBarChartData={{this.byMonthActivityData}}

View File

@@ -1444,7 +1444,6 @@ module('Integration | Component | clients/running-total', function (hooks) {
const expectedNewNonEntity = formatNumber([calculateAverage(NEW_ACTIVITY, 'non_entity_clients')]); const expectedNewNonEntity = formatNumber([calculateAverage(NEW_ACTIVITY, 'non_entity_clients')]);
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::RunningTotal <Clients::RunningTotal
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@selectedAuthMethod={{this.selectedAuthMethod}} @selectedAuthMethod={{this.selectedAuthMethod}}
@@ -1515,7 +1514,6 @@ module('Integration | Component | clients/running-total', function (hooks) {
const expectedTotalNonEntity = formatNumber([TOTAL_USAGE_COUNTS.non_entity_clients]); const expectedTotalNonEntity = formatNumber([TOTAL_USAGE_COUNTS.non_entity_clients]);
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::RunningTotal <Clients::RunningTotal
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@selectedAuthMethod={{this.selectedAuthMethod}} @selectedAuthMethod={{this.selectedAuthMethod}}
@@ -1558,7 +1556,6 @@ module('Integration | Component | clients/running-total', function (hooks) {
const expectedNewNonEntity = formatNumber([singleMonthNew.non_entity_clients]); const expectedNewNonEntity = formatNumber([singleMonthNew.non_entity_clients]);
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<Clients::RunningTotal <Clients::RunningTotal
@chartLegend={{this.chartLegend}} @chartLegend={{this.chartLegend}}
@selectedAuthMethod={{this.selectedAuthMethod}} @selectedAuthMethod={{this.selectedAuthMethod}}

View File

@@ -18,7 +18,6 @@ module('Integration | Component | confirmation-modal', function (hooks) {
this.set('onConfirm', confirmAction); this.set('onConfirm', confirmAction);
this.set('onClose', closeAction); this.set('onClose', closeAction);
await render(hbs` await render(hbs`
<div id="modal-wormhole"></div>
<ConfirmationModal <ConfirmationModal
@title="Confirmation Modal" @title="Confirmation Modal"
@isActive={{true}} @isActive={{true}}
@@ -30,11 +29,11 @@ module('Integration | Component | confirmation-modal', function (hooks) {
`); `);
assert.dom('[data-test-confirm-button]').isDisabled(); assert.dom('[data-test-confirm-button]').isDisabled();
assert.dom('[data-test-modal-div]').hasAttribute('class', 'modal is-highlight is-active'); assert.dom('.hds-modal#confirmation-modal').exists('modal is active');
assert.dom('[data-test-confirm-button]').hasText('Plz Continue', 'Confirm button has specified value'); assert.dom('[data-test-confirm-button]').hasText('Plz Continue', 'Confirm button has specified value');
assert assert
.dom('[data-test-modal-title]') .dom('[data-test-confirmation-modal-title] [data-test-icon="alert-triangle"]')
.hasStyle({ color: 'rgb(160, 125, 2)' }, 'title exists with warning header'); .exists('title has with warning icon');
await fillIn('[data-test-confirmation-modal-input="Confirmation Modal"]', 'Destructive Thing'); await fillIn('[data-test-confirmation-modal-input="Confirmation Modal"]', 'Destructive Thing');
assert.dom('[data-test-confirm-button="Confirmation Modal"]').isNotDisabled(); assert.dom('[data-test-confirm-button="Confirmation Modal"]').isNotDisabled();

View File

@@ -12,14 +12,12 @@ import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
import timestamp from 'core/utils/timestamp'; import timestamp from 'core/utils/timestamp';
const SELECTORS = { const SELECTORS = {
monthDropdown: '[data-test-popup-menu-trigger="month"]', monthDropdown: '[data-test-toggle-month]',
specificMonth: (m) => `[data-test-dropdown-month="${m}"]`, specificMonth: (m) => `[data-test-dropdown-month="${m}"]`,
yearDropdown: '[data-test-popup-menu-trigger="year"]', yearDropdown: '[data-test-toggle-year]',
specificYear: (y) => `[data-test-dropdown-year="${y}"]`, specificYear: (y) => `[data-test-dropdown-year="${y}"]`,
submitButton: '[data-test-date-dropdown-submit]', submitButton: '[data-test-date-dropdown-submit]',
cancelButton: '[data-test-date-dropdown-cancel]', monthOptions: '[data-test-dropdown-month]',
monthOptions: '[data-test-month-list] button',
}; };
module('Integration | Component | date-dropdown', function (hooks) { module('Integration | Component | date-dropdown', function (hooks) {
@@ -34,27 +32,11 @@ module('Integration | Component | date-dropdown', function (hooks) {
test('it renders dropdown', async function (assert) { test('it renders dropdown', async function (assert) {
await render(hbs` await render(hbs`
<div class="is-flex-align-baseline"> <div class="has-padding-l">
<DateDropdown/> <DateDropdown/>
</div> </div>
`); `);
assert.dom(SELECTORS.submitButton).hasText('Submit', 'button renders default text'); assert.dom(SELECTORS.submitButton).hasText('Submit', 'button renders default text');
assert.dom(SELECTORS.cancelButton).doesNotExist('it does not render cancel button by default');
});
test('it fires off cancel callback', async function (assert) {
assert.expect(2);
const onCancel = () => {
assert.ok('fires onCancel callback');
};
this.set('onCancel', onCancel);
await render(hbs`
<div class="is-flex-align-baseline">
<DateDropdown @handleCancel={{this.onCancel}} @submitText="Save"/>
</div>
`);
assert.dom(SELECTORS.submitButton).hasText('Save', 'button renders passed in text');
await click(SELECTORS.cancelButton);
}); });
test('it renders dropdown and selects month and year', async function (assert) { test('it renders dropdown and selects month and year', async function (assert) {
@@ -74,7 +56,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
this.set('parentAction', parentAction); this.set('parentAction', parentAction);
await render(hbs` await render(hbs`
<div class="is-flex-align-baseline"> <div class="has-padding-l">
<DateDropdown <DateDropdown
@handleSubmit={{this.parentAction}} @handleSubmit={{this.parentAction}}
@dateType="start" @dateType="start"
@@ -112,7 +94,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
test('selecting month first: current year enabled when current month selected', async function (assert) { test('selecting month first: current year enabled when current month selected', async function (assert) {
assert.expect(5); assert.expect(5);
await render(hbs` await render(hbs`
<div class="is-flex-align-baseline"> <div class="has-padding-l">
<DateDropdown/> <DateDropdown/>
</div> </div>
`); `);
@@ -129,7 +111,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
test('selecting month first: it disables current year when future months selected', async function (assert) { test('selecting month first: it disables current year when future months selected', async function (assert) {
assert.expect(5); assert.expect(5);
await render(hbs` await render(hbs`
<div class="is-flex-align-baseline"> <div class="has-padding-l">
<DateDropdown/> <DateDropdown/>
</div> </div>
`); `);
@@ -149,7 +131,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
test('selecting year first: it disables future months when current year selected', async function (assert) { test('selecting year first: it disables future months when current year selected', async function (assert) {
assert.expect(12); assert.expect(12);
await render(hbs` await render(hbs`
<div class="is-flex-align-baseline"> <div class="has-padding-l">
<DateDropdown/> <DateDropdown/>
</div> </div>
`); `);
@@ -170,7 +152,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
test('selecting year first: it enables all months when past year is selected', async function (assert) { test('selecting year first: it enables all months when past year is selected', async function (assert) {
assert.expect(12); assert.expect(12);
await render(hbs` await render(hbs`
<div class="is-flex-align-baseline"> <div class="has-padding-l">
<DateDropdown/> <DateDropdown/>
</div> </div>
`); `);

View File

@@ -6,7 +6,6 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit'; import { setupRenderingTest } from 'ember-qunit';
import { click, render, resetOnerror, setupOnerror } from '@ember/test-helpers'; import { click, render, resetOnerror, setupOnerror } from '@ember/test-helpers';
import { isPresent } from 'ember-cli-page-object';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon'; import sinon from 'sinon';
@@ -28,25 +27,23 @@ module('Integration | Component | download button', function (hooks) {
test('it renders', async function (assert) { test('it renders', async function (assert) {
await render(hbs` await render(hbs`
<DownloadButton class="button"> <DownloadButton /> `);
<Icon @name="download" /> assert.dom(SELECTORS.icon).exists('renders download icon');
Download assert.dom(SELECTORS.button).hasText('Download', 'renders default text');
</DownloadButton> });
`);
assert.dom(SELECTORS.button).hasClass('button'); test('it renders passed args', async function (assert) {
assert.ok(isPresent(SELECTORS.icon), 'renders yielded icon'); await render(hbs`
assert.dom(SELECTORS.button).hasTextContaining('Download', 'renders yielded text'); <DownloadButton @text="I do something" @hideIcon={{true}} /> `);
assert.dom(SELECTORS.icon).doesNotExist('hides icon');
assert.dom(SELECTORS.button).hasText('I do something', 'renders passed text');
}); });
test('it downloads with defaults when only passed @data arg', async function (assert) { test('it downloads with defaults when only passed @data arg', async function (assert) {
assert.expect(3); assert.expect(3);
await render(hbs` await render(hbs`
<DownloadButton class="button" <DownloadButton @data={{this.data}} />
@data={{this.data}}
>
Download
</DownloadButton>
`); `);
await click(SELECTORS.button); await click(SELECTORS.button);
const [filename, content, extension] = this.downloadSpy.getCall(0).args; const [filename, content, extension] = this.downloadSpy.getCall(0).args;
@@ -59,16 +56,13 @@ module('Integration | Component | download button', function (hooks) {
assert.expect(3); assert.expect(3);
await render(hbs` await render(hbs`
<DownloadButton class="button" <DownloadButton
@data={{this.data}} @data={{this.data}}
@filename={{this.filename}} @filename={{this.filename}}
@mime={{this.mime}} @mime={{this.mime}}
@extension={{this.extension}} @extension={{this.extension}}
> />
Download
</DownloadButton>
`); `);
await click(SELECTORS.button); await click(SELECTORS.button);
const [filename, content, extension] = this.downloadSpy.getCall(0).args; const [filename, content, extension] = this.downloadSpy.getCall(0).args;
assert.ok(filename.includes(`${this.filename}-`), 'filename added to ISO string'); assert.ok(filename.includes(`${this.filename}-`), 'filename added to ISO string');
@@ -80,9 +74,7 @@ module('Integration | Component | download button', function (hooks) {
assert.expect(3); assert.expect(3);
this.fetchData = () => 'this is fetched data from a parent function'; this.fetchData = () => 'this is fetched data from a parent function';
await render(hbs` await render(hbs`
<DownloadButton class="button" @fetchData={{this.fetchData}} > <DownloadButton @fetchData={{this.fetchData}} />
Download
</DownloadButton>
`); `);
await click(SELECTORS.button); await click(SELECTORS.button);
@@ -103,7 +95,7 @@ module('Integration | Component | download button', function (hooks) {
}); });
this.fetchData = () => 'this is fetched data from a parent function'; this.fetchData = () => 'this is fetched data from a parent function';
await render(hbs` await render(hbs`
<DownloadButton class="button" @data={{this.data}} @fetchData={{this.fetchData}} /> <DownloadButton @data={{this.data}} @fetchData={{this.fetchData}} />
`); `);
resetOnerror(); resetOnerror();
}); });

View File

@@ -45,9 +45,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
// TODO: Add capabilities tests // TODO: Add capabilities tests
test('it renders show view as default', async function (assert) { test('it renders show view as default', async function (assert) {
assert.expect(8); assert.expect(8);
await render( await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @tab={{this.tab}} />`);
hbs`<Keymgmt::KeyEdit @model={{this.model}} @tab={{this.tab}} /><div id="modal-wormhole" />`
);
assert.dom('[data-test-secret-header]').hasText('Unicorns', 'Shows key name'); assert.dom('[data-test-secret-header]').hasText('Unicorns', 'Shows key name');
assert.dom('[data-test-keymgmt-key-toolbar]').exists('Subnav toolbar exists'); assert.dom('[data-test-keymgmt-key-toolbar]').exists('Subnav toolbar exists');
assert.dom('[data-test-tab="Details"]').exists('Details tab exists'); assert.dom('[data-test-tab="Details"]').exists('Details tab exists');
@@ -71,9 +69,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
this.set('mode', 'edit'); this.set('mode', 'edit');
this.set('model', model); this.set('model', model);
await render( await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
);
assert.dom('[data-test-secret-header]').hasText('Edit Key', 'Shows edit header'); assert.dom('[data-test-secret-header]').hasText('Edit Key', 'Shows edit header');
assert.dom('[data-test-keymgmt-key-toolbar]').doesNotExist('Subnav toolbar does not exist'); assert.dom('[data-test-keymgmt-key-toolbar]').doesNotExist('Subnav toolbar does not exist');
assert.dom('[data-test-tab="Details"]').doesNotExist('Details tab does not exist'); assert.dom('[data-test-tab="Details"]').doesNotExist('Details tab does not exist');
@@ -86,9 +82,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
this.set('mode', 'create'); this.set('mode', 'create');
this.set('model', model); this.set('model', model);
await render( await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
);
assert.dom('[data-test-secret-header]').hasText('Create Key', 'Shows edit header'); assert.dom('[data-test-secret-header]').hasText('Create Key', 'Shows edit header');
assert.dom('[data-test-keymgmt-key-toolbar]').doesNotExist('Subnav toolbar does not exist'); assert.dom('[data-test-keymgmt-key-toolbar]').doesNotExist('Subnav toolbar does not exist');
assert.dom('[data-test-tab="Details"]').doesNotExist('Details tab does not exist'); assert.dom('[data-test-tab="Details"]').doesNotExist('Details tab does not exist');
@@ -100,9 +94,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
const store = this.owner.lookup('service:store'); const store = this.owner.lookup('service:store');
this.model = store.createRecord('keymgmt/key'); this.model = store.createRecord('keymgmt/key');
this.set('mode', 'create'); this.set('mode', 'create');
await render( await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
);
assert.dom('[data-test-input="type"]').hasValue('rsa-2048', 'Has type rsa-2048 by default'); assert.dom('[data-test-input="type"]').hasValue('rsa-2048', 'Has type rsa-2048 by default');
}); });
}); });

View File

@@ -199,14 +199,13 @@ module('Integration | Component | kubernetes | Page::Configure', function (hooks
await render( await render(
hbs` hbs`
<div id="modal-wormhole"></div>
<Page::Configure @model={{this.editModel}} @breadcrumbs={{this.breadcrumbs}} /> <Page::Configure @model={{this.editModel}} @breadcrumbs={{this.breadcrumbs}} />
`, `,
{ owner: this.engine } { owner: this.engine }
); );
await click('[data-test-config-save]'); await click('[data-test-config-save]');
assert assert
.dom('.modal-card-body') .dom('[data-test-edit-config-body]')
.hasText( .hasText(
'Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are you sure?', 'Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are you sure?',
'Confirm modal renders' 'Confirm modal renders'

View File

@@ -44,7 +44,6 @@ module('Integration | Component | ldap | AccountsCheckedOut', function (hooks) {
this.renderComponent = () => { this.renderComponent = () => {
return render( return render(
hbs` hbs`
<div id="modal-wormhole"></div>
<AccountsCheckedOut <AccountsCheckedOut
@libraries={{array this.library}} @libraries={{array this.library}}
@statuses={{this.statuses}} @statuses={{this.statuses}}

Some files were not shown because too many files have changed in this diff Show More