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
continueWithoutRotate(evt) {
evt.preventDefault();
continueWithoutRotate() {
this.showSaveModal = false;
const { name } = this.args.model;
this.transitionToRoute(SHOW_ROUTE, name);
}
@action
continueWithRotate(evt) {
evt.preventDefault();
continueWithRotate() {
this.showSaveModal = false;
const { backend, name } = this.args.model;
this.rotateCredentials(backend, name)
.then(() => {

View File

@@ -15,10 +15,9 @@ import timestamp from 'core/utils/timestamp';
*
* @example
* ```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} [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} [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
@@ -69,12 +68,6 @@ export default class DateDropdown extends Component {
this.resetDropdown();
}
@action
handleCancel() {
this.args.handleCancel();
this.resetDropdown();
}
resetDropdown() {
this.maxMonthIdx = 11;
this.disabledYear = null;

View File

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

View File

@@ -23,11 +23,24 @@
</div>
{{/if}}
<div class="field">
{{#if @model.isNew}}
<Toolbar>
<label class="has-text-weight-bold">Policy</label>
<ToolbarActions>
<div class="toolbar-separator"></div>
<Toolbar>
<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>
<div class="toolbar-separator"></div>
{{#if @model.isNew}}
<div class="control is-flex">
<Input
id="fileUploadToggle"
@@ -40,25 +53,24 @@
/>
<label for="fileUploadToggle">Upload file</label>
</div>
</ToolbarActions>
</Toolbar>
{{#if this.showFileUpload}}
<TextFile @uploadOnly={{true}} @onChange={{this.setPolicyFromFile}} />
{{else}}
<JsonEditor
@title="Policy"
@showToolbar={{false}}
@value={{@model.policy}}
@valueUpdated={{action (mut @model.policy)}}
@mode="ruby"
@extraKeys={{hash Shift-Enter=(perform this.save)}}
data-test-policy-editor
/>
{{/if}}
{{else}}
{{! EDITING - no file upload toggle}}
<Hds::Copy::Button
@text="Copy"
@isIconOnly={{true}}
@textToCopy={{@model.policy}}
class="transparent"
data-test-copy-button
/>
{{/if}}
</ToolbarActions>
</Toolbar>
{{#if this.showFileUpload}}
<TextFile @uploadOnly={{true}} @onChange={{this.setPolicyFromFile}} />
{{else}}
{{! EDITING - no file upload toggle}}
<JsonEditor
@title="Policy"
@showToolbar={{false}}
@value={{@model.policy}}
@valueUpdated={{action (mut @model.policy)}}
@mode="ruby"
@@ -70,36 +82,6 @@
<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.
</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>
{{#each @model.additionalAttrs as |attr|}}
@@ -128,26 +110,26 @@
</div>
</div>
</form>
{{! SAMPLE POLICY MODAL. Only renders modal if not already in create policy modal }}
{{#if @renderPolicyExampleModal}}
<Modal
@title="Example {{uppercase @model.policyType}} Policy"
{{! SAMPLE POLICY MODAL. Only renders in policy create and edit routes }}
{{#if this.showTemplateModal}}
<Hds::Modal
id="policy-example-modal"
@size="large"
@onClose={{fn (mut this.showTemplateModal) false}}
@isActive={{this.showTemplateModal}}
@showCloseButton={{true}}
data-test-policy-example-modal
as |M|
>
<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>
<M.Header data-test-modal-title>
Example
{{uppercase @model.policyType}}
Policy
</M.Header>
<M.Body>
<PolicyExample @policyType={{@model.policyType}} @container="#policy-example-modal" />
</M.Body>
<M.Footer as |F|>
<Hds::Button @text="Close" {{on "click" F.close}} data-test-modal-close-button />
</M.Footer>
</Hds::Modal>
{{/if}}

View File

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

View File

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

View File

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

View File

@@ -91,12 +91,6 @@
color: $red-500;
}
&.is-warning-outlined {
background-color: $yellow-010;
border: 1px solid $yellow-700;
color: $yellow-700;
}
&.is-flat {
min-width: auto;
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
.hds-copy-button {
font-weight: $font-weight-semibold;
box-shadow: $box-shadow-low;
border-radius: $radius;
font-weight: $font-weight-semibold; // TODO delete
box-shadow: $box-shadow-low; // TODO delete
&.white-icon svg {
color: $white;
@@ -350,15 +344,14 @@ a.button.disabled {
color: $ui-gray-500;
}
&.icon-only {
margin-right: $spacing-xxs;
margin-left: $spacing-xxs;
}
&.transparent {
background: none;
border: none;
box-shadow: none;
border: 1px solid transparent;
&:hover {
border: 1px solid $grey-light;
border-color: var(--token-color-border-strong);
}
}
&.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 {
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 class="header-right">
{{#if this.hasCsvData}}
<button
<Hds::Button
data-test-attribution-export-button
type="button"
class="button is-secondary"
@text="Export attribution data"
@color="secondary"
{{on "click" (fn (mut this.showCSVDownloadModal) true)}}
>
Export attribution data
</button>
/>
{{/if}}
</div>
</div>
@@ -91,41 +89,41 @@
</div>
{{! MODAL FOR CSV DOWNLOAD }}
<Modal
@title="Export attribution data"
@type="info"
@isActive={{this.showCSVDownloadModal}}
@showCloseButton={{true}}
@onClose={{action (mut this.showCSVDownloadModal) false}}
>
<section class="modal-card-body">
<p class="has-bottom-margin-s">
This export will include the namespace path, authentication method path, and the associated total, entity, and
non-entity clients for the below
{{if this.formattedEndDate "date range" "month"}}.
</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>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button type="button" class="button is-primary" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}}>
Export
</button>
<button type="button" class="button is-secondary" onclick={{action (mut this.showCSVDownloadModal) false}}>
Cancel
</button>
{{#if @upgradeExplanation}}
<div class="has-text-grey is-size-8">
<AlertInline @type="warning" @isMarginless={{true}}>
Your data contains an upgrade.
<DocLink
@path="/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts"
>
Learn more here.
</DocLink>
</AlertInline>
<p class="has-left-padding-l">{{@upgradeExplanation}}</p>
</div>
{{/if}}
</footer>
</Modal>
{{#if this.showCSVDownloadModal}}
<Hds::Modal id="attribution-csv-download-modal" @onClose={{fn (mut this.showCSVDownloadModal) false}} as |M|>
<M.Header @icon="info" data-test-export-modal-title>
Export attribution data
</M.Header>
<M.Body>
<p class="has-bottom-margin-s">
This export will include the namespace path, authentication method path, and the associated total, entity, and
non-entity clients for the below
{{if this.formattedEndDate "date range" "month"}}.
</p>
<p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</p>
<p class="has-bottom-margin-s" data-test-export-date-range>
{{this.formattedStartDate}}
{{if this.formattedEndDate "-"}}
{{this.formattedEndDate}}</p>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button @text="Export" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}} />
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet>
{{#if @upgradeExplanation}}
<div class="has-text-grey is-size-8 has-top-padding-m">
<AlertInline @type="warning">
Your data contains an upgrade.
<DocLink
@path="/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts"
>
Learn more here.
</DocLink>
</AlertInline>
<p class="has-left-padding-l">{{@upgradeExplanation}}</p>
</div>
{{/if}}
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -47,47 +47,34 @@
</div>
</form>
<Modal
@title={{this.modalTitle}}
@onClose={{action (mut this.modalOpen) false}}
@isActive={{this.modalOpen}}
@type="warning"
@showCloseButton={{true}}
>
<section class="modal-card-body">
{{#if (eq @model.enabled "On")}}
<p class="has-bottom-margin-s" data-test-clients-config-modal="on">
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
be available to you.
</p>
{{else}}
<p class="has-bottom-margin-s" data-test-clients-config-modal="off">
Turning usage tracking off means that all data for the current month will be deleted. You will still be able to
query previous months.
</p>
<p>Are you sure?</p>
{{/if}}
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button
type="button"
class="button is-primary"
data-test-clients-config-modal="continue"
{{on "click" (perform this.save)}}
>
Continue
</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>
{{#if this.modalOpen}}
<Hds::Modal id="clients-config-modal" @color="warning" @onClose={{fn (mut this.modalOpen) false}} as |M|>
<M.Header @icon="alert-triangle" data-test-clients-config-modal="title">
{{this.modalTitle}}
</M.Header>
<M.Body>
{{#if (eq @model.enabled "On")}}
<p class="has-bottom-margin-s" data-test-clients-config-modal="on">
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 be available to you.
</p>
{{else}}
<p class="has-bottom-margin-s" data-test-clients-config-modal="off">
Turning usage tracking off means that all data for the current month will be deleted. You will still be able to
query previous months.
</p>
<p>Are you sure?</p>
{{/if}}
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button @text="Continue" {{on "click" (perform this.save)}} data-test-clients-config-modal="continue" />
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-clients-config-modal="cancel" />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}
{{else}}
<div class="tabs-container box is-bottomless is-marginless is-fullwidth is-paddingless" data-test-clients-config-table>
{{#each this.infoRows as |item|}}

View File

@@ -175,26 +175,28 @@
</EmptyState>
{{/if}}
{{/if}}
</div>
{{! BILLING START DATE MODAL }}
<Modal
@title="Edit start month"
@onClose={{action (mut this.showBillingStartModal) false}}
@isActive={{this.showBillingStartModal}}
@showCloseButton={{true}}
>
<section class="modal-card-custom has-padding-m">
{{! BILLING START DATE MODAL }}
{{#if this.showBillingStartModal}}
<Hds::Modal id="clients-edit-date-modal" @onClose={{fn (mut this.showBillingStartModal) false}} as |M|>
<M.Header>
Edit start month
</M.Header>
<M.Body>
<p class="has-bottom-margin-s">
{{this.versionText.description}}
</p>
<p><strong>{{this.versionText.label}}</strong></p>
</section>
<DateDropdown
@handleSubmit={{this.handleClientActivityQuery}}
@dateType="startDate"
@submitText="Save"
@handleCancel={{fn this.handleClientActivityQuery (hash dateType="cancel")}}
/>
</Modal>
</div>
<DateDropdown
class="has-top-padding-s"
@handleSubmit={{this.handleClientActivityQuery}}
@dateType="startDate"
@submitText="Save"
/>
</M.Body>
<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}}
{{/if}}
<Modal
@title="Rotate your root credentials?"
@onClose={{this.continueWithoutRotate}}
@isActive={{this.showSaveModal}}
@type="info"
@showCloseButton={{false}}
>
<section class="modal-card-body">
<p class="has-bottom-margin-s">
Its best practice to rotate the root credential immediately after the initial configuration of each database. Once
rotated,
<strong>only Vault knows the new root password</strong>.
</p>
<p>Would you like to rotate your new credentials? You can also do this later.</p>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button
type="button"
class="button is-primary"
{{on "click" this.continueWithRotate}}
data-test-enable-rotate-connection
>
Rotate and enable
</button>
<button type="button" class="button is-secondary" {{on "click" this.continueWithoutRotate}} data-test-enable-connection>
Enable without rotating
</button>
</footer>
</Modal>
{{#if this.showSaveModal}}
<Hds::Modal id="rotate-credentials-modal" @onClose={{this.continueWithoutRotate}} as |M|>
<M.Header @icon="info" data-test-db-connection-modal-title>
Rotate your root credentials?
</M.Header>
<M.Body>
<p class="has-bottom-margin-s">
Its best practice to rotate the root credential immediately after the initial configuration of each database. Once
rotated,
<strong>only Vault knows the new root password</strong>.
</p>
<p>Would you like to rotate your new credentials? You can also do this later.</p>
</M.Body>
<M.Footer>
<Hds::ButtonSet>
<Hds::Button @text="Rotate and enable" {{on "click" this.continueWithRotate}} data-test-enable-rotate-connection />
<Hds::Button
@text="Enable without rotating"
@color="secondary"
{{on "click" this.continueWithoutRotate}}
data-test-enable-connection
/>
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}
<ConfirmationModal
@title="Delete connection?"

View File

@@ -3,89 +3,36 @@
SPDX-License-Identifier: BUSL-1.1
~}}
<div class="modal-card-custom has-padding-btm-left">
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|>
<D.Trigger
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|}}
<button
data-test-dropdown-month={{month.name}}
type="button"
class="button link"
disabled={{if (gt month.index this.maxMonthIdx) true false}}
{{on "click" (fn this.selectMonth month D.actions)}}
>
{{month.name}}
</button>
{{/each}}
</ul>
</nav>
</D.Content>
</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|}}
<button
data-test-dropdown-year={{year}}
type="button"
class="button link"
disabled={{if (eq year this.disabledYear) true false}}
{{on "click" (fn this.selectYear year D.actions)}}
>
{{year}}
</button>
{{/each}}
</ul>
</nav>
</D.Content>
</BasicDropdown>
{{#unless @handleCancel}}
<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>
{{/unless}}
{{#if this.invalidDate}}
<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>
<Hds::SegmentedGroup ...attributes as |S|>
<S.Dropdown @height="200px" as |dd|>
<dd.ToggleButton data-test-toggle-month @text={{or this.selectedMonth.name "Month"}} @color="secondary" />
{{#each this.dropdownMonths as |month|}}
<dd.Interactive
data-test-dropdown-month={{month.name}}
disabled={{if (gt month.index this.maxMonthIdx) true false}}
{{on "click" (fn this.selectMonth month dd)}}
@text={{month.name}}
/>
{{/each}}
</S.Dropdown>
<S.Dropdown data-test-year-list @height="200px" as |dd|>
<dd.ToggleButton data-test-toggle-year @text={{or this.selectedYear "Year"}} @color="secondary" />
{{#each this.dropdownYears as |year|}}
<dd.Interactive
data-test-dropdown-year={{year}}
disabled={{if (eq year this.disabledYear) true false}}
{{on "click" (fn this.selectYear year dd)}}
@text={{year}}
/>
{{/each}}
</S.Dropdown>
<S.Button
data-test-date-dropdown-submit
disabled={{if (and this.selectedMonth this.selectedYear) false true}}
{{on "click" this.handleSubmit}}
@text={{or @submitText "Submit"}}
/>
</Hds::SegmentedGroup>
{{#if this.invalidDate}}
<AlertInline @type="danger" @message={{this.invalidDate}} @paddingTop={{true}} @mimicRefresh={{true}} />
{{/if}}

View File

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

View File

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

View File

@@ -29,6 +29,15 @@
Scope
</h1>
</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>
<form {{on "submit" (perform this.save)}}>
@@ -41,15 +50,8 @@
<FormField @attr={{field}} @model={{@model}} @modelValidations={{this.modelValidations}} />
{{/each}}
<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
<button
type="button"
class="text-button has-text-info"
{{on "click" (fn (mut this.showTemplateModal))}}
data-test-oidc-scope-example
>
example template
</button>.
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field. Click 'How to write JSON
template for scopes' to view an example.
</p>
</div>
<div class="has-top-margin-l has-bottom-margin-l">
@@ -78,40 +80,25 @@
{{/if}}
</form>
<Modal
@title="Scope template"
@onClose={{fn (mut this.showTemplateModal) false}}
@isActive={{this.showTemplateModal}}
@showCloseButton={{true}}
>
<section class="modal-card-body">
<div class="is-flex-between is-flex-center has-bottom-margin-s">
<p data-test-modal-text>
{{#if this.showTemplateModal}}
<Hds::Modal id="scope-template-modal" @size="large" @onClose={{fn (mut this.showTemplateModal) false}} as |M|>
<M.Header data-test-scope-modal="title">
Scope template
</M.Header>
<M.Body>
<p data-test-scope-modal="text">
Example of a JSON template for scopes:
</p>
<Hds::Copy::Button
@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">
The full list of template parameters can be found
<DocLink @path="/vault/docs/concepts/oidc-provider#scopes">
here.
</DocLink>
</p>
</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>
<JsonEditor @value={{this.exampleTemplate}} @mode="ruby" @readOnly={{true}} @container=".hds-modal" />
<p class="has-top-margin-m">
The full list of template parameters can be found
<DocLink @path="/vault/docs/concepts/oidc-provider#scopes">
here.
</DocLink>
</p>
</M.Body>
<M.Footer as |F|>
<Hds::Button @text="Close" {{on "click" F.close}} data-test-close-modal />
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -53,14 +53,13 @@
{{/if}}
{{#if this.capabilities.canUpdate}}
{{#if (gt this.model.allowed_roles.length 0)}}
<button
class="toolbar-link"
onclick={{action (mut this.isEditModalActive) true}}
type="button"
<Hds::Button
@text="Edit transformation"
@color="secondary"
class="toolbar-button"
{{on "click" (fn (mut this.isEditModalActive) true)}}
data-test-edit-link
>
Edit transformation
</button>
/>
{{else}}
<ToolbarSecretLink @secret={{this.model.id}} @mode="edit" data-test-edit-link={{true}} @replace={{true}}>
Edit transformation
@@ -96,30 +95,27 @@
<MessageError @model={{this.model}} @errorMessage={{this.error}} />
</ConfirmationModal>
<Modal
@title="Edit transformation"
@onClose={{action (mut this.isEditModalActive) false}}
@isActive={{this.isEditModalActive}}
@type="warning"
@showCloseButton={{true}}
>
<section class="modal-card-body">
<p>
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?
</p>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<LinkTo
@route="vault.cluster.secrets.backend.edit"
@model={{this.model.id}}
class="button is-primary"
data-test-edit-confirm-button={{true}}
>
Confirm
</LinkTo>
<button type="button" class="button is-secondary" onclick={{action (mut this.isEditModalActive) false}}>
Cancel
</button>
</footer>
</Modal>
{{#if this.isEditModalActive}}
<Hds::Modal id="transformation-edit-modal" @color="warning" @onClose={{fn (mut this.isEditModalActive) false}} as |M|>
<M.Header @icon="alert-triangle">
Edit transformation
</M.Header>
<M.Body>
<p>
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?
</p>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button
@text="Confirm"
@route="vault.cluster.secrets.backend.edit"
@model={{this.model.id}}
data-test-edit-confirm-button
/>
<Hds::Button @color="secondary" @text="Cancel" {{on "click" F.close}} />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}

View File

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

View File

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

View File

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

View File

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

View File

@@ -120,19 +120,22 @@
</div>
</div>
</form>
<Modal
@title="Copy your signature"
@onClose={{action (mut @isModalActive) false}}
@isActive={{@isModalActive}}
data-test-sign-modal
>
<section class="modal-card-body">
<div class="box is-shadowless is-fullwidth is-sideless">
{{#if @isModalActive}}
<Hds::Modal id="transit-sign-modal" @onClose={{fn (mut @isModalActive) false}} data-test-sign-modal as |M|>
<M.Header>
Copy your signature
</M.Header>
<M.Body>
<h2 class="title is-6">Signature</h2>
<Hds::Copy::Snippet @textToCopy={{@signature}} @color="secondary" data-test-encrypted-value="signature" />
</div>
</section>
<footer class="modal-card-foot">
<button type="button" class="button is-primary" {{on "click" (fn (mut @isModalActive) false)}}>Close</button>
</footer>
</Modal>
<Hds::Copy::Snippet
@textToCopy={{@signature}}
@color="secondary"
@container="#transit-sign-modal"
data-test-encrypted-value="signature"
/>
</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>
</form>
<Modal @title="Results" @onClose={{action (mut @isModalActive) false}} @isActive={{@isModalActive}}>
<section class="modal-card-body">
<Hds::Alert @type="inline" @color={{if @valid "success" "critical"}} as |A|>
<A.Title>{{if @valid "Valid" "Not Valid"}}</A.Title>
<A.Description>
The input is
{{if @valid "valid" "not valid"}}
for the given
{{if @signature "signature." "HMAC."}}
</A.Description>
</Hds::Alert>
</section>
</Modal>
{{#if @isModalActive}}
<Hds::Modal id="transit-verify-modal" @size="small" @onClose={{fn (mut @isModalActive) false}} as |M|>
<M.Header>
Results
<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
{{if @valid "valid" "not valid"}}
for the given
{{if @signature "signature." "HMAC."}}
</M.Body>
<M.Footer as |F|>
<Hds::Button @text="Close" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>
{{/if}}

View File

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

View File

@@ -29,14 +29,13 @@
<Toolbar>
<ToolbarActions>
<DownloadButton
class="toolbar-link"
class="toolbar-button"
@color="secondary"
@filename={{this.model.name}}
@data={{this.model.policy}}
@extension={{if (eq this.policyType "acl") this.model.format "sentinel"}}
>
Download policy
<Chevron @isButton={{true}} />
</DownloadButton>
@text="Download policy"
/>
{{#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>
Edit policy

View File

@@ -19,7 +19,7 @@
)
}}
</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}}
</h4>
<Hds::Copy::Snippet
@@ -27,6 +27,7 @@
@textToCopy={{this.pgpKey}}
@color="secondary"
data-test-pgp-key-copy
@container="#shamir-flow-modal"
/>
</div>
<div class="field is-grouped">

View File

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

View File

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

View File

@@ -2,11 +2,11 @@
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
/**
* @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
* ```js
@@ -15,6 +15,7 @@ import Component from '@glimmer/component';
* @title="Do Dangerous Thing?"
* @isActive={{isModalActive}}
* @onClose={{action (mut isModalActive) false}}
* @confirmText="yes"
* @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 {string} title - Title of the modal
* @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} [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 {
get buttonClass() {
return this.args.buttonClass || 'is-danger';
}
get buttonText() {
return this.args.buttonText || 'Confirm';
}
get confirmText() {
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
~}}
<button data-test-download-button type="button" {{on "click" this.handleDownload}} ...attributes>
{{yield}}
</button>
<Hds::Button
data-test-download-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';
/**
* @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
* [ember-docs](https://ember-engines.com/docs/services)
* @example
* ```js
* <DownloadButton
* class="button"
* @text="Download this stuff"
* @color="secondary"
* @data={{this.data}}
* @filename={{this.filename}}
* @mime={{this.mime}}
* @extension={{this.extension}}
* @stringify={{true}}
* >
* <Icon @name="download" />
* Download
* </DownloadButton>
* />
* ```
* @param {string} [filename] - name of file that prefixes the ISO timestamp generated at 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 {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 {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 {

View File

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

View File

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

View File

@@ -14,28 +14,11 @@ import Component from '@glimmer/component';
* @example
* <PolicyExample
* @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} container - selector for the container the example renders inside, passed to the copy button in JsonEditor
*/
export default class PolicyExampleComponent extends Component {

View File

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

View File

@@ -43,6 +43,7 @@
@options={{this.dropdownOptions}}
@onChange={{this.selectOrCreate}}
@placeholderComponent={{component "search-select-placeholder"}}
@renderInPlace={{@renderInPlace}}
@verticalPosition="below"
@disabled={{@disabled}}
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} [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} [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 {boolean} [disabled] - if true sets the disabled property on the ember-power-select component and makes it unusable.
*

View File

@@ -2,130 +2,131 @@
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<section class="modal-card-body">
{{#if this.encodedToken}}
<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!
</p>
<div class="has-bottom-margin-m">
<div class="has-bottom-margin-xl">
<h4 class="field-title">
Encoded operation token
</h4>
<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.
</p>
<Hds::Copy::Snippet @textToCopy={{this.encodedToken}} data-test-shamir-encoded-token />
</div>
{{#if this.otp}}
<div class="has-bottom-margin-xl">
<h4 class="field-title">
One time password (OTP)
</h4>
<p class="help has-text-grey has-bottom-margin-xs">
This OTP will be used to decode the generated operation token. Please save it.
</p>
<Hds::Copy::Snippet @textToCopy={{this.otp}} />
</div>
{{/if}}
<div class="has-bottom-margin-xl">
<h4 class="field-title">
DR operation token command
</h4>
<p class="help has-text-grey has-bottom-margin-xs">
{{#if this.otp}}
This command contains both the encoded token and the OTP. It should be executed on the secondary cluster in order
to generate the operation token.
{{else}}
This command requires the OTP saved earlier. It should be executed on the secondary cluster in order to generate
the operation token.
{{/if}}
</p>
{{! template-lint-disable quotes }}
{{#let
(if
this.otp
(concat 'vault operator generate-root -dr-token -otp="' this.otp '" -decode="' this.encodedToken '"')
(concat 'vault operator generate-root -dr-token -otp="<enter your otp here>" -decode="' this.encodedToken '"')
)
as |cmd|
}}
<CodeSnippet @codeBlock={{cmd}} />
{{/let}}
{{! template-lint-enable quotes }}
</div>
</div>
<div>
<button type="button" class="button is-primary" {{on "click" this.onCancelClose}}>
Clear &amp; Close
</button>
</div>
{{else if this.started}}
<Shamir::Form
@action={{@action}}
@progress={{this.progress}}
@threshold={{this.threshold}}
@errors={{this.errors}}
@inputLabel="Root key portion"
@buttonText="Generate token"
@onSubmit={{this.onSubmitKey}}
@otp={{this.otp}}
@alwaysShowProgress={{true}}
data-test-dr-token-flow-step="shamir"
>
<p>Generate an operation token by entering a portion of the
<strong>primary's root key</strong>. Once all portions are entered, the generated token may be used to manage your
secondary Disaster Recovery cluster.
{{! THIS COMPONENT RENDERS INSIDE A MODAL (must pass @container to copy buttons) }}
{{#if this.encodedToken}}
<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!
</p>
<div class="has-bottom-margin-m">
<div class="has-bottom-margin-xl">
<h4 class="has-text-weight-bold is-size-7">
Encoded operation token
</h4>
<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.
</p>
</Shamir::Form>
{{else if this.generateWithPGP}}
<ChoosePgpKeyForm
@onCancel={{fn (mut this.generateWithPGP) false}}
@onSubmit={{this.usePgpKey}}
@formText={{this.pgpText.form}}
@confirmText={{this.pgpText.confirm}}
@buttonText="Generate operation token"
data-test-dr-token-flow-step="choose-pgp"
/>
{{else}}
{{! Generate token flow not started }}
<form
{{on "submit" this.startGenerate}}
id="shamir"
aria-label="shamir generate form"
data-test-dr-token-flow-step="begin"
>
<MessageError @errors={{this.errors}} />
<div class="has-bottom-margin-m" data-test-shamir-modal-body>
<p>
Updating or promoting this cluster requires an operation token, generated by inputting the root key shares. If
you'd like to first encrypt the token with a PGP Key, click "Encrypt with PGP key" below, otherwise we can begin
generation of the operation token.
<Hds::Copy::Snippet @textToCopy={{this.encodedToken}} @container="#shamir-flow-modal" data-test-shamir-encoded-token />
</div>
{{#if this.otp}}
<div class="has-bottom-margin-xl">
<h4 class="has-text-weight-bold is-size-7">
One time password (OTP)
</h4>
<p class="help has-text-grey has-bottom-margin-xs">
This OTP will be used to decode the generated operation token. Please save it.
</p>
<Hds::Copy::Snippet @textToCopy={{this.otp}} @container="#shamir-flow-modal" />
</div>
<div class="field is-grouped is-flex-center">
<div class="control is-flex-row">
<button type="button" class="link" {{on "click" (fn (mut this.generateWithPGP) true)}} data-test-use-pgp-key-cta>
Provide PGP Key
</button>
</div>
<div class="control">
<span class="has-side-padding-s">
or
</span>
</div>
<div class="control">
<button type="submit" class="button is-primary" data-test-generate-token-cta>
Generate operation token
</button>
</div>
{{/if}}
<div class="has-bottom-margin-xl">
<h4 class="has-text-weight-bold is-size-7">
DR operation token command
</h4>
<p class="help has-text-grey has-bottom-margin-xs">
{{#if this.otp}}
This command contains both the encoded token and the OTP. It should be executed on the secondary cluster in order
to generate the operation token.
{{else}}
This command requires the OTP saved earlier. It should be executed on the secondary cluster in order to generate
the operation token.
{{/if}}
</p>
{{! template-lint-disable quotes }}
{{#let
(if
this.otp
(concat 'vault operator generate-root -dr-token -otp="' this.otp '" -decode="' this.encodedToken '"')
(concat 'vault operator generate-root -dr-token -otp="<enter your otp here>" -decode="' this.encodedToken '"')
)
as |cmd|
}}
<CodeSnippet @codeBlock={{cmd}} @container="#shamir-flow-modal" />
{{/let}}
{{! template-lint-enable quotes }}
</div>
</div>
<div>
<Hds::Button {{on "click" this.onCancelClose}} @text="Clear & Close" />
</div>
{{else if this.started}}
<Shamir::Form
@action={{@action}}
@progress={{this.progress}}
@threshold={{this.threshold}}
@errors={{this.errors}}
@inputLabel="Root key portion"
@buttonText="Generate token"
@onSubmit={{this.onSubmitKey}}
@otp={{this.otp}}
@alwaysShowProgress={{true}}
data-test-dr-token-flow-step="shamir"
>
<p>Generate an operation token by entering a portion of the
<strong>primary's root key</strong>. Once all portions are entered, the generated token may be used to manage your
secondary Disaster Recovery cluster.
</p>
</Shamir::Form>
{{else if this.generateWithPGP}}
<ChoosePgpKeyForm
@onCancel={{fn (mut this.generateWithPGP) false}}
@onSubmit={{this.usePgpKey}}
@formText={{this.pgpText.form}}
@confirmText={{this.pgpText.confirm}}
@buttonText="Generate operation token"
data-test-dr-token-flow-step="choose-pgp"
/>
{{else}}
{{! Generate token flow not started }}
<form
{{on "submit" this.startGenerate}}
id="shamir"
aria-label="shamir generate form"
data-test-dr-token-flow-step="begin"
>
<MessageError @errors={{this.errors}} />
<div class="has-bottom-margin-m">
<p>
Updating or promoting this cluster requires an operation token, generated by inputting the root key shares. If you'd
like to first encrypt the token with a PGP Key, click "Encrypt with PGP key" below, otherwise we can begin generation
of the operation token.
</p>
</div>
<div class="field is-grouped is-flex-center">
<div class="control is-flex-row">
<Hds::Button
@color="tertiary"
@icon="key"
{{on "click" (fn (mut this.generateWithPGP) true)}}
data-test-use-pgp-key-cta
@text="Provide PGP Key"
/>
</div>
</form>
{{/if}}
</section>
<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>
{{if this.encodedToken "Close" "Cancel"}}
</button>
</footer>
<div class="control">
<span class="has-side-padding-s">
or
</span>
</div>
<div class="control">
<Hds::Button type="submit" data-test-generate-token-cta @text="Generate operation token" />
</div>
</div>
</form>
{{/if}}
<hr class="has-background-gray-100" />
<Hds::Button
@color="secondary"
{{on "click" this.onCancelClose}}
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">
One Time Password (otp)
</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>
</Hds::Alert>
{{/if}}

View File

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

View File

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

View File

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

View File

@@ -25,16 +25,14 @@
</div>
</div>
<Modal
@title="Generate operation token"
@onClose={{action (mut this.isModalActive) false}}
@isActive={{this.isModalActive}}
@type="warning"
@showCloseButton={{true}}
>
{{#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}} />
{{#if this.isModalActive}}
<Hds::Modal id="shamir-flow-modal" @color="warning" @onClose={{action (mut this.isModalActive) false}} as |M|>
<M.Header>
Generate operation token
</M.Header>
<M.Body>
<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 }}
{{/if}}
</Modal>
</Hds::Modal>
{{/if}}

View File

@@ -20,107 +20,106 @@
type="button"
class="button is-tertiary"
onclick={{action (mut this.isModalActive) true}}
data-test-replication-action-trigger
data-test-replication-action-trigger="promote"
>
Promote
</button>
</div>
</div>
<Modal
@title="Promote cluster?"
@onClose={{action (mut this.isModalActive) false}}
@isActive={{this.isModalActive}}
@type="warning"
@showCloseButton={{true}}
>
<section class="modal-card-body">
{{#if (eq this.replicationMode "dr")}}
<p class="has-bottom-margin-m">
To promote this DR Replication Secondary to a primary, enter the DR Operation token.
</p>
{{/if}}
<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
to data loss if they or their secondaries are ever reconnected. If the cluster has a primary, be sure to demote it
before promoting a secondary.
</p>
<div data-test-promote-dr-inputs>
{{#if this.isModalActive}}
<Hds::Modal id="replication-promote-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
<M.Header @icon="alert-triangle">
Promote cluster?
</M.Header>
<M.Body>
{{#if (eq this.replicationMode "dr")}}
<div class="field is-borderless">
<label for="dr_operation_token_promote" class="is-label is-size-6">
DR Operation Token
<p class="has-bottom-margin-m">
To promote this DR Replication Secondary to a primary, enter the DR Operation token.
</p>
{{/if}}
<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 to data loss if they or their secondaries are ever reconnected. If the cluster has a primary, be sure to demote
it before promoting a secondary.
</p>
<div data-test-promote-dr-inputs>
{{#if (eq this.replicationMode "dr")}}
<div class="field is-borderless">
<label for="dr_operation_token_promote" class="is-label is-size-6">
DR Operation Token
</label>
<div class="control">
<Input
class="input"
id="dr_operation_token_promote"
name="dr_operation_token_promote"
@value={{this.dr_operation_token_promote}}
/>
</div>
</div>
{{/if}}
<div class="field">
<label for="primary_cluster_addr" class="is-label is-size-6">
Primary cluster address
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input
class="input"
id="dr_operation_token_promote"
name="dr_operation_token_promote"
@value={{this.dr_operation_token_promote}}
id="primary_cluster_addr"
name="primary_cluster_addr"
@value={{this.primary_cluster_addr}}
/>
</div>
</div>
{{/if}}
<div class="field">
<label for="primary_cluster_addr" class="is-label is-size-6">
Primary cluster address
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input class="input" id="primary_cluster_addr" name="primary_cluster_addr" @value={{this.primary_cluster_addr}} />
</div>
<p class="help">
Overrides the cluster address that the primary gives to secondary nodes.
</p>
</div>
<div class="field">
<div class="b-checkbox">
<input
type="checkbox"
id="forcePromote"
class="styled"
checked={{this.force}}
onchange={{action (mut this.force) value="target.checked"}}
/>
<label for="forcePromote" class="is-label is-size-6">
Force promotion of this cluster
</label>
<p>
Promote the cluster even if certain safety checks fail. This could result in data loss of data isn't fully
replicated
<p class="help">
Overrides the cluster address that the primary gives to secondary nodes.
</p>
</div>
<div class="field">
<div class="b-checkbox">
<input
type="checkbox"
id="forcePromote"
class="styled"
checked={{this.force}}
onchange={{action (mut this.force) value="target.checked"}}
/>
<label for="forcePromote" class="is-label is-size-6">
Force promotion of this cluster
</label>
<p>
Promote the cluster even if certain safety checks fail. This could result in data loss of data isn't fully
replicated
</p>
</div>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button
type="button"
class="button is-primary"
disabled={{if (and (eq this.replicationMode "dr") (not this.dr_operation_token_promote)) true}}
onclick={{action
"onSubmit"
"promote"
this.model.replicationAttrs.modeForUrl
(hash
dr_operation_token_promote=this.dr_operation_token_promote
primary_cluster_addr=this.primary_cluster_addr
force=this.force
)
}}
data-test-promote-confirm-button
>
Promote
</button>
<button
type="button"
class="button is-secondary"
onclick={{action (mut this.isModalActive) false}}
data-test-promote-cancel-button
>
Cancel
</button>
</footer>
</Modal>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button
disabled={{if (and (eq this.replicationMode "dr") (not this.dr_operation_token_promote)) true}}
{{on
"click"
(fn
this.onSubmit
"promote"
this.model.replicationAttrs.modeForUrl
(hash
dr_operation_token_promote=this.dr_operation_token_promote
primary_cluster_addr=this.primary_cluster_addr
force=this.force
)
)
}}
data-test-confirm-button
@text="Promote"
/>
<Hds::Button @color="secondary" @text="Cancel" {{on "click" F.close}} data-test-promote-cancel-button />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}

View File

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

View File

@@ -17,41 +17,33 @@
type="button"
class="button is-secondary"
onclick={{action (mut this.isModalActive) true}}
data-test-replication-action-trigger
data-test-replication-action-trigger="reindex"
>
Reindex
</button>
</div>
</div>
<Modal
@title="Begin reindex?"
@onClose={{action (mut this.isModalActive) false}}
@isActive={{this.isModalActive}}
@type="warning"
@showCloseButton={{true}}
>
<section class="modal-card-body">
<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.
{{if this.model.replicationAttrs.isPrimary "You should always re-index your secondary first."}}
</p>
<p>
Progress will be shown, and you will
{{if this.model.replicationAttrs.isPrimary "not"}}
be able to use Vault during this time.
</p>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button type="button" class="button is-primary" onclick={{action "onSubmit" "reindex"}} data-test-reindex-confirm-button>
Reindex
</button>
<button
type="button"
class="button is-secondary"
onclick={{action (mut this.isModalActive) false}}
data-test-reindex-cancel-button
>
Cancel
</button>
</footer>
</Modal>
{{#if this.isModalActive}}
<Hds::Modal id="replication-reindex-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
<M.Header @icon="alert-triangle">
Begin reindex?
</M.Header>
<M.Body>
<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.
{{if this.model.replicationAttrs.isPrimary "You should always re-index your secondary first."}}
</p>
<p>
Progress will be shown, and you will
{{if this.model.replicationAttrs.isPrimary "not"}}
be able to use Vault during this time.
</p>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button @text="Reindex" {{on "click" (fn this.onSubmit "reindex")}} data-test-confirm-button />
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-reindex-cancel-button />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -18,117 +18,111 @@
type="button"
class="button is-secondary"
onclick={{action (mut this.isModalActive) true}}
data-test-update-primary-action-trigger
data-test-replication-action-trigger="update-primary"
>
Update
</button>
</div>
</div>
<Modal
@title="Update primary"
@onClose={{action (mut this.isModalActive) false}}
@isActive={{this.isModalActive}}
@type="warning"
@showCloseButton={{true}}
>
<section class="modal-card-body">
<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
cluster.
</p>
{{#if this.isModalActive}}
<Hds::Modal id="replication-update-primary-modal" @color="warning" @onClose={{fn (mut this.isModalActive) false}} as |M|>
<M.Header @icon="alert-triangle">
Update primary
</M.Header>
<M.Body>
<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
cluster.
</p>
<div data-test-update-primary-inputs>
{{#if (eq this.replicationMode "dr")}}
<div data-test-update-primary-inputs>
{{#if (eq this.replicationMode "dr")}}
<div class="field">
<label for="dr_operation_token_primary" class="is-label">
DR operation token
</label>
<div class="control">
<Input
class="input"
id="dr_operation_token_primary"
name="dr_operation_token_primary"
@value={{this.dr_operation_token_primary}}
/>
</div>
</div>
{{/if}}
<div class="field">
<label for="dr_operation_token_primary" class="is-label">
DR operation token
<label for="secondary-token" class="is-label">
Secondary activation token
</label>
<div class="control">
<Input
class="input"
id="dr_operation_token_primary"
name="dr_operation_token_primary"
@value={{this.dr_operation_token_primary}}
/>
<Textarea @value={{this.token}} id="secondary-token" name="secondary-token" class="textarea" />
</div>
</div>
{{/if}}
<div class="field">
<label for="secondary-token" class="is-label">
Secondary activation token
</label>
<div class="control">
<Textarea @value={{this.token}} id="secondary-token" name="secondary-token" class="textarea" />
<div class="field">
<label for="primary_api_addr" class="is-label">
Primary API address
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input class="input" @value={{this.primary_api_addr}} id="primary_api_addr" name="primary_api_addr" />
</div>
<p class="help">
Set this to the API address (normal Vault address) to override the value embedded in the token.
</p>
</div>
<div class="field">
<label for="ca_file" class="is-label">
CA file
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input @value={{this.ca_file}} id="ca_file" name="ca_file" class="input" />
</div>
<p class="help">
Specifies the path to a CA root file (PEM format) that the secondary can use when unwrapping the token from the
primary.
</p>
</div>
<div class="field">
<label for="ca_path" class="is-label">
CA path
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input @value={{this.ca_path}} id="ca_path" name="ca_file" class="input" />
</div>
<p class="help">
Specifies the path to a CA root directory containing PEM-format files that the secondary can use when unwrapping
the token from the primary.
</p>
</div>
</div>
<div class="field">
<label for="primary_api_addr" class="is-label">
Primary API address
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input class="input" @value={{this.primary_api_addr}} id="primary_api_addr" name="primary_api_addr" />
</div>
<p class="help">
Set this to the API address (normal Vault address) to override the value embedded in the token.
</p>
</div>
<div class="field">
<label for="ca_file" class="is-label">
CA file
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input @value={{this.ca_file}} id="ca_file" name="ca_file" class="input" />
</div>
<p class="help">
Specifies the path to a CA root file (PEM format) that the secondary can use when unwrapping the token from the
primary.
</p>
</div>
<div class="field">
<label for="ca_path" class="is-label">
CA path
<em class="is-optional">(optional)</em>
</label>
<div class="control">
<Input @value={{this.ca_path}} id="ca_path" name="ca_file" class="input" />
</div>
<p class="help">
Specifies the path to a CA root directory containing PEM-format files that the secondary can use when unwrapping
the token from the primary.
</p>
</div>
</div>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button
type="button"
class="button is-primary"
onclick={{action
"onSubmit"
"update-primary"
this.model.replicationAttrs.modeForUrl
(hash
token=this.token
dr_operation_token_primary=this.dr_operation_token_primary
primary_api_addr=this.primary_api_addr
ca_path=this.ca_path
ca_file=this.ca_file
)
}}
data-test-confirm-action-trigger
>
Update
</button>
<button
type="button"
class="button is-secondary"
onclick={{action (mut this.isModalActive) false}}
data-test-update-primary-cancel-button
>
Cancel
</button>
</footer>
</Modal>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button
{{on
"click"
(fn
this.onSubmit
"update-primary"
this.model.replicationAttrs.modeForUrl
(hash
token=this.token
dr_operation_token_primary=this.dr_operation_token_primary
primary_api_addr=this.primary_api_addr
ca_path=this.ca_path
ca_file=this.ca_file
)
)
}}
data-test-confirm-button
@text="Update"
/>
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-update-primary-cancel-button />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}

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-svg-jar": "*",
"ember-truth-helpers": "*",
"ember-wormhole": "*",
"escape-string-regexp": "*",
"@hashicorp/ember-flight-icons": "*",
"@hashicorp/flight-icons": "*",

View File

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

View File

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

View File

@@ -21,6 +21,7 @@ import { assert } from '@ember/debug';
* @param {string} mode - delete, delete-metadata, or destroy.
* @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 {string} [text] - Button text that renders in KV v2 toolbar, defaults to capitalize @mode
* @param {callback} onDelete - callback function fired to handle delete event.
*/
@@ -34,20 +35,20 @@ export default class KvDeleteModal extends Component {
case 'delete':
return {
title: 'Delete version?',
type: 'warning',
color: 'warning',
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?',
};
case 'destroy':
return {
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.`,
};
case 'delete-metadata':
return {
title: 'Delete metadata and secret data?',
type: 'danger',
color: 'critical',
intro:
'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}}
@onDelete={{this.handleDestruction}}
@version={{this.version}}
>
Delete
</KvDeleteModal>
/>
{{/if}}
{{#if this.showDestroy}}
<KvDeleteModal @mode="destroy" @secret={{@secret}} @onDelete={{this.handleDestruction}} @version={{this.version}}>
Destroy
</KvDeleteModal>
<KvDeleteModal @mode="destroy" @secret={{@secret}} @onDelete={{this.handleDestruction}} @version={{this.version}} />
{{/if}}
{{#if (or @secret.canReadData @secret.canReadMetadata @secret.canEditData)}}
<div class="toolbar-separator"></div>

View File

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

View File

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

View File

@@ -78,14 +78,11 @@
</form>
{{#if this.showRotatePrompt}}
<Modal
@title="Rotate your root password?"
@type="info"
@isActive={{this.showRotatePrompt}}
@showCloseButton={{true}}
@onClose={{fn (mut this.showRotatePrompt) false}}
>
<section class="modal-card-body">
<Hds::Modal id="ldap-rotate-password-modal" @onClose={{fn (mut this.showRotatePrompt) false}} as |M|>
<M.Header @icon="info">
Rotate your root password?
</M.Header>
<M.Body>
<p>
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,
@@ -95,19 +92,17 @@
<p>
Would you like to rotate your new credentials? You can also do this later.
</p>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button
data-test-save-with-rotate
type="button"
class="button is-primary"
{{on "click" (fn (perform this.save) null true)}}
>
Save and rotate
</button>
<button data-test-save-without-rotate type="button" class="button" {{on "click" (fn (perform this.save) null false)}}>
Save without rotating
</button>
</footer>
</Modal>
</M.Body>
<M.Footer>
<Hds::ButtonSet>
<Hds::Button data-test-save-with-rotate @text="Save and rotate" {{on "click" (fn (perform this.save) null true)}} />
<Hds::Button
data-test-save-without-rotate
@text="Save without rotating"
@color="secondary"
{{on "click" (fn (perform this.save) null false)}}
/>
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}

View File

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

View File

@@ -111,19 +111,18 @@
{{#if this.showDeleteAllIssuers}}
<ConfirmationModal
@title="Delete All Issuers?"
@title="Delete all issuers?"
@toConfirmMsg="deleting all issuers and keys."
@buttonText="Confirm"
@confirmText="delete-all"
@isActive={{this.showDeleteAllIssuers}}
@onClose={{action (mut this.showDeleteAllIssuers) false}}
@onConfirm={{this.deleteAllIssuers}}
>
<section class="modal-card-custom">
<p>
This endpoint deletes
<strong>all</strong>
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.
</section>
</p>
</ConfirmationModal>
{{/if}}

View File

@@ -48,9 +48,9 @@
@data={{@pem}}
@extension="pem"
data-test-issuer-download-type="pem"
>
PEM format
</DownloadButton>
@text="PEM format"
@hideIcon={{true}}
/>
</li>
{{/if}}
{{#if @der}}
@@ -62,9 +62,9 @@
@data={{@der}}
@extension="der"
data-test-issuer-download-type="der"
>
DER format
</DownloadButton>
@text="DER format"
@hideIcon={{true}}
/>
</li>
{{/if}}
</ul>
@@ -148,45 +148,37 @@
{{/if}}
{{! ROOT ROTATION MODAL }}
<Modal
@type="rotation"
@title="Rotate this root"
@onClose={{fn (mut this.showRotationModal) false}}
@isActive={{this.showRotationModal}}
@showCloseButton={{true}}
>
<section class="modal-card-body">
<h3 class="title is-5">Root rotation</h3>
<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
end-users trust stores. You can also do this manually by
<DocLink @path="/vault/docs/secrets/pki/rotation-primitives#suggested-root-rotation-procedure">
following our documentation.
</DocLink>
</p>
<h3 class="title is-5 has-top-bottom-margin">How root rotation will work</h3>
<p class="has-text-grey">
<ol class="has-left-margin-m has-bottom-margin-s">
<li>The new root will be generated using defaults from the old one that you can customize.</li>
<li>You will identify intermediates, which Vault will then cross-sign.</li>
</ol>
Then, you can begin re-issuing leaf certs and phase out the old root.
</p>
<div class="has-top-margin-l has-tall-padding">
<img src={{img-path "~/pki-rotate-root.png"}} alt="pki root rotation diagram" />
</div>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button
type="button"
class="button is-primary"
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.issuers.issuer.rotate-root")}}
data-test-root-rotate-step-one
>
Generate new root
</button>
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.showRotationModal) false)}}>
Cancel
</button>
</footer>
</Modal>
{{#if this.showRotationModal}}
<Hds::Modal id="pki-rotate-root-modal" @size="large" @onClose={{fn (mut this.showRotationModal) false}} as |M|>
<M.Header @icon="rotate-cw">
Rotate this root
</M.Header>
<M.Body>
<h3 class="title is-5">Root rotation</h3>
<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
end-users trust stores. You can also do this manually by
<DocLink @path="/vault/docs/secrets/pki/rotation-primitives#suggested-root-rotation-procedure">
following our documentation.
</DocLink>
</p>
<h3 class="title is-5 has-top-bottom-margin">How root rotation will work</h3>
<p class="has-text-grey">
<ol class="has-left-margin-m has-bottom-margin-s">
<li>The new root will be generated using defaults from the old one that you can customize.</li>
<li>You will identify intermediates, which Vault will then cross-sign.</li>
</ol>
Then, you can begin re-issuing leaf certs and phase out the old root.
</p>
<div class="has-top-margin-l has-tall-padding">
<img src={{img-path "~/pki-rotate-root.png"}} alt="pki root rotation diagram" />
</div>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button @text="Generate new root" @route="issuers.issuer.rotate-root" data-test-root-rotate-step-one />
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}

View File

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

View File

@@ -19,14 +19,13 @@
{{/if}}
{{#if @key.privateKey}}
<DownloadButton
class="toolbar-link"
class="toolbar-button"
@color="secondary"
@filename="{{@key.backend}}-{{or @key.keyName 'private-key'}}"
@data={{@key.privateKey}}
@extension="pem"
>
Download private key
<Chevron @isButton={{true}} />
</DownloadButton>
@text="Download private key"
/>
{{/if}}
{{#if @canEdit}}
<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}}>
<:actions>
{{#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
</ToolbarLink>
{{/if}}

View File

@@ -14,15 +14,15 @@
Perform manual tidy
</ToolbarLink>
{{else}}
<button
type="button"
class="toolbar-link"
<Hds::Button
class="toolbar-button"
@color="secondary"
@icon="chevron-right"
@iconPosition="trailing"
{{on "click" (fn (mut this.tidyOptionsModal) true)}}
data-test-pki-tidy-options-modal
>
Tidy
<Icon @name="chevron-right" />
</button>
@text="Tidy"
/>
{{/if}}
</ToolbarActions>
</Toolbar>
@@ -101,104 +101,75 @@
@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."
>
<button
type="button"
class="link"
<Hds::Button
@color="tertiary"
@icon="chevron-right"
@iconPosition="trailing"
{{on "click" (fn (mut this.tidyOptionsModal) true)}}
data-test-tidy-empty-state-configure
>
Tidy
</button>
@text="Tidy"
/>
</EmptyState>
{{/if}}
{{! TIDY OPTIONS MODAL }}
<Modal
@title="Tidy this mount"
@onClose={{fn (mut this.tidyOptionsModal) false}}
@isActive={{this.tidyOptionsModal}}
@showCloseButton={{true}}
>
<section aria-label="tidy-options-modal-content" class="modal-card-body">
<h3 class="title is-5">How tidying will work</h3>
<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
buffer period beyond their expiration time.
<DocLink @path="/vault/docs/secrets/pki/considerations#automate-crl-building-and-tidying">
Documentation.
</DocLink>
</p>
<p class="has-text-grey">
<ol class="has-left-margin-m has-bottom-margin-s">
<li>Select a tidy operation:</li>
<ul class="bullet has-bottom-margin-xs">
<li><strong>Automatic tidy</strong>
periodically runs a tidy operation with saved configuration settings after waiting the specified interval
duration between tidies
</li>
<li><strong>Manual tidy</strong> runs a tidy operation once</li>
</ul>
<li>Configure the parameters that determine how to tidy and run the operation.</li>
</ol>
</p>
<div class="has-top-margin-l has-padding">
<img src={{img-path "~/pki-tidy.png"}} alt="tidy operation diagram" />
</div>
</section>
<footer aria-label="tidy-option-buttons" class="modal-card-foot modal-card-foot-outlined">
<button
type="button"
class="button is-primary"
{{on "click" (transition-to "vault.cluster.secrets.backend.pki.tidy.auto.configure")}}
data-test-tidy-modal-auto-button
>
Automatic tidy
</button>
<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>
{{#if this.tidyOptionsModal}}
<Hds::Modal id="pki-tidy-modal" @size="large" @onClose={{fn (mut this.tidyOptionsModal) false}} as |M|>
<M.Header>
Tidy this mount
</M.Header>
<M.Body>
<h3 class="title is-5">How tidying will work</h3>
<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
buffer period beyond their expiration time.
<DocLink @path="/vault/docs/secrets/pki/considerations#automate-crl-building-and-tidying">
Documentation.
</DocLink>
</p>
<p class="has-text-grey">
<ol class="has-left-margin-m has-bottom-margin-s">
<li>Select a tidy operation:</li>
<ul class="bullet has-bottom-margin-xs">
<li><strong>Automatic tidy</strong>
periodically runs a tidy operation with saved configuration settings after waiting the specified interval
duration between tidies
</li>
<li><strong>Manual tidy</strong> runs a tidy operation once</li>
</ul>
<li>Configure the parameters that determine how to tidy and run the operation.</li>
</ol>
</p>
<div class="has-top-margin-l has-padding">
<img src={{img-path "~/pki-tidy.png"}} alt="tidy operation diagram" />
</div>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button @text="Automatic tidy" @route="tidy.auto.configure" data-test-tidy-modal-auto-button />
<Hds::Button @text="Manual tidy" @route="tidy.manual" data-test-tidy-modal-manual-button />
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-tidy-modal-cancel-button />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}
{{! CANCEL TIDY CONFIRMATION MODAL }}
{{#if this.confirmCancelTidy}}
<Modal
@type="warning"
@title="Cancel tidy?"
@onClose={{fn (mut this.confirmCancelTidy) false}}
@isActive={{this.confirmCancelTidy}}
@showCloseButton={{true}}
>
<section aria-label="confirm-cancel-modal-content" class="modal-card-body">
<Hds::Modal id="pki-cancel-tidy-modal" @color="warning" @onClose={{fn (mut this.confirmCancelTidy) false}} as |M|>
<M.Header @icon="alert-triangle">
Cancel tidy?
</M.Header>
<M.Body>
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.
<p class="has-top-margin-s">Click “Confirm” to cancel the running tidy operation.</p>
</section>
<footer aria-label="confirm-cancel-buttons" class="modal-card-foot modal-card-foot-outlined">
<button
type="button"
class="button is-primary"
{{on "click" (perform this.cancelTidy)}}
data-test-tidy-modal-cancel-button
>
Confirm
</button>
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.confirmCancelTidy) false)}}>
Cancel
</button>
</footer>
</Modal>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button @text="Confirm" {{on "click" (perform this.cancelTidy)}} data-test-tidy-modal-cancel-button />
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet>
</M.Footer>
</Hds::Modal>
{{/if}}

View File

@@ -26,11 +26,11 @@ const DEFAULTS = {
export default Controller.extend(copy(DEFAULTS, true), {
isModalActive: false,
isTokenCopied: false,
expirationDate: null,
store: service(),
rm: service('replication-mode'),
replicationMode: alias('rm.mode'),
flashMessages: service(),
submitError(e) {
if (e.errors) {
@@ -121,25 +121,13 @@ export default Controller.extend(copy(DEFAULTS, true), {
onSubmit(/*action, mode, data, event*/) {
return this.submitHandler(...arguments);
},
copyClose(successMessage) {
// 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);
}
closeTokenModal() {
this.toggleProperty('isModalActive');
this.transitionToRoute('mode.secondaries');
this.set('isTokenCopied', false);
},
toggleModal(successMessage) {
if (!!successMessage && typeof successMessage === 'string') {
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');
onCopy() {
this.set('isTokenCopied', true);
},
clear() {
this.reset();

View File

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

View File

@@ -65,7 +65,6 @@
"@ember/test-waiters": "^3.0.0",
"@glimmer/component": "^1.1.2",
"@glimmer/tracking": "^1.1.2",
"@hashicorp/ember-flight-icons": "^3.0.9",
"@hashicorp/structure-icons": "^1.3.0",
"@icholy/duration": "^5.1.0",
"@tsconfig/ember": "^1.0.1",
@@ -166,7 +165,6 @@
"ember-test-selectors": "6.0.0",
"ember-tether": "^2.0.1",
"ember-truth-helpers": "3.0.0",
"ember-wormhole": "0.6.0",
"escape-string-regexp": "^2.0.0",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
@@ -248,7 +246,8 @@
]
},
"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",
"highlight.js": "^10.4.1",
"node-notifier": "^8.0.1",

View File

@@ -390,9 +390,9 @@ module('Acceptance | client counts dashboard tab', function (hooks) {
.dom(SELECTORS.emptyStateTitle)
.includesText('start date found', 'Empty state shows no billing start date');
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(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);
assert
.dom(SELECTORS.emptyStateTitle)

View File

@@ -250,11 +250,16 @@ module('Acceptance | Enterprise | replication', function (hooks) {
await pollCluster(this.owner);
await settled();
const modalDefaultTtl = document.querySelector('[data-test-row-value="TTL"]').innerText;
// 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');
// 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
await click('[data-test-secondary-add]');
@@ -269,7 +274,8 @@ module('Acceptance | Enterprise | replication', function (hooks) {
await settled();
const modalTtl = document.querySelector('[data-test-row-value="TTL"]').innerText;
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
assert.strictEqual(

View File

@@ -432,7 +432,7 @@ module('Acceptance | pki workflow', function (hooks) {
.dom(SELECTORS.issuerDetails.configure)
.hasAttribute('href', `/ui/vault/secrets/${this.mountPath}/pki/issuers/${issuerId}/edit`);
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);
assert.strictEqual(
currentURL(),

View File

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

View File

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

View File

@@ -37,14 +37,14 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: false,
encodeContext: false,
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(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext`
);
},
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
.dom('[data-test-transit-input="context"]')
.hasValue(
@@ -52,9 +52,8 @@ const testConvergentEncryption = async function (assert, keyName) {
`${key}: the ui shows the base64-encoded context`
);
},
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(
find('[data-test-encrypted-value="plaintext"]').innerText,
'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=',
@@ -69,20 +68,20 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: false,
encodeContext: false,
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(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext`
);
},
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
.dom('[data-test-transit-input="context"]')
.hasValue(encodeString('context'), `${key}: the ui shows the input context`);
},
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(
find('[data-test-encrypted-value="plaintext"]').innerText,
'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=',
@@ -97,20 +96,20 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: false,
encodeContext: false,
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(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext`
);
},
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
.dom('[data-test-transit-input="context"]')
.hasValue(encodeString('context'), `${key}: the ui shows the input context`);
},
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(
find('[data-test-encrypted-value="plaintext"]').innerText,
encodeString('This is the secret'),
@@ -125,20 +124,20 @@ const testConvergentEncryption = async function (assert, keyName) {
encodePlaintext: true,
encodeContext: true,
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(
/vault:/.test(find('[data-test-encrypted-value="ciphertext"]').innerText),
`${key}: ciphertext shows a vault-prefixed ciphertext`
);
},
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
.dom('[data-test-transit-input="context"]')
.hasValue(encodeString('secret 2'), `${key}: the ui shows the encoded context`);
},
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(
find('[data-test-encrypted-value="plaintext"]').innerText,
encodeString('There are many secrets 🤐'),
@@ -161,7 +160,7 @@ const testConvergentEncryption = async function (assert, keyName) {
if (testCase.encodeContext) {
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]');
if (testCase.assertAfterEncrypt) {
@@ -170,9 +169,9 @@ const testConvergentEncryption = async function (assert, keyName) {
}
// store ciphertext for decryption step
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"]');
if (testCase.assertBeforeDecrypt) {
@@ -187,9 +186,9 @@ const testConvergentEncryption = async function (assert, 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]',
filterBar: '[data-test-clients-filter-bar]',
rangeDropdown: '[data-test-calendar-widget-trigger]',
monthDropdown: '[data-test-popup-menu-trigger="month"]',
yearDropdown: '[data-test-popup-menu-trigger="year"]',
monthDropdown: '[data-test-toggle-month]',
yearDropdown: '[data-test-toggle-year]',
dateDropdownSubmit: '[data-test-date-dropdown-submit]',
runningTotalMonthStats: '[data-test-running-total="single-month-stats"]',
runningTotalMonthlyCharts: '[data-test-running-total="monthly-charts"]',

View File

@@ -22,7 +22,7 @@ export const PAGE = {
message: '[data-test-page-error] p',
},
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
// specific page selectors
backends: {

View File

@@ -12,12 +12,12 @@ export const SELECTORS = {
hdsAlertButtonText: '[data-test-cancel-tidy-action] .hds-button__text',
timeStartedRow: '[data-test-value-div="Time started"]',
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]',
manualTidyToolbar: '[data-test-pki-manual-tidy-config]',
autoTidyToolbar: '[data-test-pki-auto-tidy-config]',
tidyConfigureModal: {
configureTidyModal: '[data-test-modal-background="Tidy this mount"]',
configureTidyModal: '.hds-modal#pki-tidy-modal',
tidyModalAutoButton: '[data-test-tidy-modal-auto-button]',
tidyModalManualButton: '[data-test-tidy-modal-manual-button]',
tidyModalCancelButton: '[data-test-tidy-modal-cancel-button]',

View File

@@ -5,7 +5,7 @@
export const SELECTORS = {
issuerLink: '[data-test-delete-all-issuers-link]',
deleteAllIssuerModal: '[data-test-modal-background="Delete All Issuers?"]',
deleteAllIssuerInput: '[data-test-confirmation-modal-input="Delete All Issuers?"]',
deleteAllIssuerButton: '[data-test-confirm-button="Delete All Issuers?"]',
deleteAllIssuerModal: '.hds-modal#confirmation-modal',
deleteAllIssuerInput: '[data-test-confirmation-modal-input="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]',
groupTitle: '[data-test-group-title]',
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]',
rotateRoot: '[data-test-pki-issuer-rotate-root]',
row: '[data-test-component="info-table-row"]',

View File

@@ -26,7 +26,6 @@
<div id="qunit-fixture">
<div id="ember-testing-container">
<div id="ember-testing"></div>
<div id="modal-wormhole"></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) {
await render(hbs`
<div id="modal-wormhole"></div>
<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) {
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution
@chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.totalClientAttribution}}
@@ -93,7 +91,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
this.start = formatRFC3339(subMonths(this.mockNow, 1));
this.end = formatRFC3339(subMonths(endOfMonth(this.mockNow), 1));
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution
@chartLegend={{this.chartLegend}}
@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) {
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution
@chartLegend={{this.chartLegend}}
@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) {
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution
@chartLegend={{this.chartLegend}}
@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) {
this.set('selectedNamespace', 'second');
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution
@chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.namespaceMountsData}}
@@ -225,7 +219,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
test('it renders modal', async function (assert) {
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Attribution
@chartLegend={{this.chartLegend}}
@totalClientAttribution={{this.namespaceMountsData}}
@@ -235,7 +228,9 @@ module('Integration | Component | clients/attribution', function (hooks) {
/>
`);
await click('[data-test-attribution-export-button]');
assert.dom('.modal.is-active .title').hasText('Export attribution data', 'modal appears to export csv');
assert.dom('.modal.is-active').includesText('June 2022 - December 2022');
assert
.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) {
assert.expect(13);
assert.expect(12);
this.server.put('/sys/internal/counters/config', (schema, req) => {
const { enabled, retention_months } = JSON.parse(req.requestBody);
@@ -64,7 +64,6 @@ module('Integration | Component | client count config', function (hooks) {
this.createModel('disable');
await render(hbs`
<div id="modal-wormhole"></div>
<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 click('[data-test-clients-config-save]');
assert.dom('.modal.is-active').exists('Modal renders');
assert
.dom('[data-test-modal-title] span')
.dom('[data-test-clients-config-modal="title"]')
.hasText('Turn usage tracking on?', 'Correct modal title 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-clients-config-save]');
assert.dom('.modal.is-active').exists('Modal renders');
assert.dom('[data-test-clients-config-modal]').exists('Modal renders');
assert
.dom('[data-test-modal-title] span')
.dom('[data-test-clients-config-modal="title"]')
.hasText('Turn usage tracking off?', 'Correct modal title renders');
assert.dom('[data-test-clients-config-modal="off"]').exists('Correct modal description block renders');
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) {
@@ -123,7 +121,6 @@ module('Integration | Component | client count config', function (hooks) {
this.createModel('enable', true, 24);
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Config @model={{this.model}} @mode="edit" />
`);
@@ -162,7 +159,6 @@ module('Integration | Component | client count config', function (hooks) {
this.createModel();
await render(hbs`
<div id="modal-wormhole"></div>
<Clients::Config @model={{this.model}} @mode="edit" />
`);
await fillIn('[data-test-input="retentionMonths"]', 24);

View File

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

View File

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

View File

@@ -18,8 +18,7 @@ module('Integration | Component | confirmation-modal', function (hooks) {
this.set('onConfirm', confirmAction);
this.set('onClose', closeAction);
await render(hbs`
<div id="modal-wormhole"></div>
<ConfirmationModal
<ConfirmationModal
@title="Confirmation Modal"
@isActive={{true}}
@onConfirm={{this.onConfirm}}
@@ -30,11 +29,11 @@ module('Integration | Component | confirmation-modal', function (hooks) {
`);
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-modal-title]')
.hasStyle({ color: 'rgb(160, 125, 2)' }, 'title exists with warning header');
.dom('[data-test-confirmation-modal-title] [data-test-icon="alert-triangle"]')
.exists('title has with warning icon');
await fillIn('[data-test-confirmation-modal-input="Confirmation Modal"]', 'Destructive Thing');
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';
const SELECTORS = {
monthDropdown: '[data-test-popup-menu-trigger="month"]',
monthDropdown: '[data-test-toggle-month]',
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}"]`,
submitButton: '[data-test-date-dropdown-submit]',
cancelButton: '[data-test-date-dropdown-cancel]',
monthOptions: '[data-test-month-list] button',
monthOptions: '[data-test-dropdown-month]',
};
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) {
await render(hbs`
<div class="is-flex-align-baseline">
<div class="has-padding-l">
<DateDropdown/>
</div>
`);
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) {
@@ -74,7 +56,7 @@ module('Integration | Component | date-dropdown', function (hooks) {
this.set('parentAction', parentAction);
await render(hbs`
<div class="is-flex-align-baseline">
<div class="has-padding-l">
<DateDropdown
@handleSubmit={{this.parentAction}}
@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) {
assert.expect(5);
await render(hbs`
<div class="is-flex-align-baseline">
<div class="has-padding-l">
<DateDropdown/>
</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) {
assert.expect(5);
await render(hbs`
<div class="is-flex-align-baseline">
<div class="has-padding-l">
<DateDropdown/>
</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) {
assert.expect(12);
await render(hbs`
<div class="is-flex-align-baseline">
<div class="has-padding-l">
<DateDropdown/>
</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) {
assert.expect(12);
await render(hbs`
<div class="is-flex-align-baseline">
<div class="has-padding-l">
<DateDropdown/>
</div>
`);

View File

@@ -6,7 +6,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render, resetOnerror, setupOnerror } from '@ember/test-helpers';
import { isPresent } from 'ember-cli-page-object';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
@@ -28,25 +27,23 @@ module('Integration | Component | download button', function (hooks) {
test('it renders', async function (assert) {
await render(hbs`
<DownloadButton class="button">
<Icon @name="download" />
Download
</DownloadButton>
`);
assert.dom(SELECTORS.button).hasClass('button');
assert.ok(isPresent(SELECTORS.icon), 'renders yielded icon');
assert.dom(SELECTORS.button).hasTextContaining('Download', 'renders yielded text');
<DownloadButton /> `);
assert.dom(SELECTORS.icon).exists('renders download icon');
assert.dom(SELECTORS.button).hasText('Download', 'renders default text');
});
test('it renders passed args', async function (assert) {
await render(hbs`
<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) {
assert.expect(3);
await render(hbs`
<DownloadButton class="button"
@data={{this.data}}
>
Download
</DownloadButton>
<DownloadButton @data={{this.data}} />
`);
await click(SELECTORS.button);
const [filename, content, extension] = this.downloadSpy.getCall(0).args;
@@ -59,16 +56,13 @@ module('Integration | Component | download button', function (hooks) {
assert.expect(3);
await render(hbs`
<DownloadButton class="button"
<DownloadButton
@data={{this.data}}
@filename={{this.filename}}
@mime={{this.mime}}
@extension={{this.extension}}
>
Download
</DownloadButton>
/>
`);
await click(SELECTORS.button);
const [filename, content, extension] = this.downloadSpy.getCall(0).args;
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);
this.fetchData = () => 'this is fetched data from a parent function';
await render(hbs`
<DownloadButton class="button" @fetchData={{this.fetchData}} >
Download
</DownloadButton>
<DownloadButton @fetchData={{this.fetchData}} />
`);
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';
await render(hbs`
<DownloadButton class="button" @data={{this.data}} @fetchData={{this.fetchData}} />
<DownloadButton @data={{this.data}} @fetchData={{this.fetchData}} />
`);
resetOnerror();
});

View File

@@ -45,9 +45,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
// TODO: Add capabilities tests
test('it renders show view as default', async function (assert) {
assert.expect(8);
await render(
hbs`<Keymgmt::KeyEdit @model={{this.model}} @tab={{this.tab}} /><div id="modal-wormhole" />`
);
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @tab={{this.tab}} />`);
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-tab="Details"]').exists('Details tab exists');
@@ -71,9 +69,7 @@ module('Integration | Component | keymgmt/key-edit', function (hooks) {
this.set('mode', 'edit');
this.set('model', model);
await render(
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
);
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
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-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('model', model);
await render(
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
);
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
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-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');
this.model = store.createRecord('keymgmt/key');
this.set('mode', 'create');
await render(
hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} /><div id="modal-wormhole" />`
);
await render(hbs`<Keymgmt::KeyEdit @model={{this.model}} @mode={{this.mode}} />`);
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(
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 }
);
await click('[data-test-config-save]');
assert
.dom('.modal-card-body')
.dom('[data-test-edit-config-body]')
.hasText(
'Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are you sure?',
'Confirm modal renders'

View File

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

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